2009-05-11 10 views
24

प्रसंग: .NET 3.5, VS2008। मुझे इस सवाल के शीर्षक के बारे में निश्चित नहीं है, इसलिए शीर्षक के बारे में भी टिप्पणी करने में संकोच न करें :-)मैं एक सामान्य कंटेनर वर्ग है कि सी # में दिए गए इंटरफ़ेस लागू करता है कैसे लिख सकता है?

यहां परिदृश्य है: मेरे पास कई कक्षाएं हैं, फू और बार कहें, उनमें से सभी निम्नलिखित इंटरफेस को लागू करते हैं :

public interface IStartable 
{ 
    void Start(); 
    void Stop(); 
} 

और अब मैं एक कंटेनर वर्ग है, जो अपने निर्माता में एक तर्क के रूप एक IEnumerable <IStartable> हो जाता है करने के लिए करना चाहते हैं। इस वर्ग, बारी में, भी IStartable इंटरफ़ेस को लागू करना चाहिए:

public class StartableGroup : IStartable // this is the container class 
{ 
    private readonly IEnumerable<IStartable> startables; 

    public StartableGroup(IEnumerable<IStartable> startables) 
    { 
     this.startables = startables; 
    } 

    public void Start() 
    { 
     foreach (var startable in startables) 
     { 
      startable.Start(); 
     } 
    } 

    public void Stop() 
    { 
     foreach (var startable in startables) 
     { 
      startable.Stop(); 
     } 
    } 
} 

तो मेरे सवाल है: कैसे मैं इसे मैन्युअल कोड लिखने के बिना कर सकते हैं, और कोड पीढ़ी के बिना? दूसरे शब्दों में, मैं निम्नलिखित की तरह somethig करना चाहते हैं।

var arr = new IStartable[] { new Foo(), new Bar("wow") }; 
var mygroup = GroupGenerator<IStartable>.Create(arr); 
mygroup.Start(); // --> calls Foo's Start and Bar's Start 

प्रतिबंध:

  • कोई कोड पीढ़ी (जो है, संकलन समय पर कोई वास्तविक शाब्दिक कोड)
  • इंटरफेस सिर्फ शून्य विधियों, के साथ या तर्कों के बिना है

प्रेरणा:

  • मैं विभिन्न इंटरफेस के प्लगइन्स की एक बहुत कुछ के साथ, एक बहुत बड़ी आवेदन किया है। प्रत्येक इंटरफ़ेस के लिए "समूह कंटेनर" कक्षा को मैन्युअल रूप से लिखना
  • मैन्युअल रूप से कोड लिखना त्रुटि प्रवण
  • आईटर्टेबल इंटरफ़ेस में कोई भी जोड़ या हस्ताक्षर अपडेट "मैन्युअल) में" समूह कंटेनर "वर्ग
  • सीखना

मैं समझता हूँ कि मैं यहाँ प्रतिबिंब का उपयोग करना होगा, लेकिन मैं नहीं बल्कि मेरे लिए तारों करने के लिए एक मजबूत ढांचा (महल के DynamicProxy या RunSharp की तरह) का उपयोग करेंगे।

किसी भी विचार?

+0

तो क्या आप * स्टार्ट करने योग्य समूह वर्ग नहीं चाहते हैं? इसके साथ गलत क्या है? – Noldorin

+0

क्या मैं पूछ सकता हूं: क्यों? समस्या को हल करने की क्या समस्या है? (यह जवाब को प्रभावित कर सकता है ...)। –

+0

@ नोल्डोरिन, @ मर्क ग्रेवेल, प्रेरणा मूल प्रश्न में जोड़ा गया। –

उत्तर

26

यह सुंदर नहीं है, लेकिन यह काम करने के लिए लगता है:

public static class GroupGenerator 
{ 
    public static T Create<T>(IEnumerable<T> items) where T : class 
    { 
     return (T)Activator.CreateInstance(Cache<T>.Type, items); 
    } 
    private static class Cache<T> where T : class 
    { 
     internal static readonly Type Type; 
     static Cache() 
     { 
      if (!typeof(T).IsInterface) 
      { 
       throw new InvalidOperationException(typeof(T).Name 
        + " is not an interface"); 
      } 
      AssemblyName an = new AssemblyName("tmp_" + typeof(T).Name); 
      var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(
       an, AssemblyBuilderAccess.RunAndSave); 
      string moduleName = Path.ChangeExtension(an.Name,"dll"); 
      var module = asm.DefineDynamicModule(moduleName, false); 
      string ns = typeof(T).Namespace; 
      if (!string.IsNullOrEmpty(ns)) ns += "."; 
      var type = module.DefineType(ns + "grp_" + typeof(T).Name, 
       TypeAttributes.Class | TypeAttributes.AnsiClass | 
       TypeAttributes.Sealed | TypeAttributes.NotPublic); 
      type.AddInterfaceImplementation(typeof(T)); 

      var fld = type.DefineField("items", typeof(IEnumerable<T>), 
       FieldAttributes.Private); 
      var ctor = type.DefineConstructor(MethodAttributes.Public, 
       CallingConventions.HasThis, new Type[] { fld.FieldType }); 
      var il = ctor.GetILGenerator(); 
      // store the items 
      il.Emit(OpCodes.Ldarg_0); 
      il.Emit(OpCodes.Ldarg_1); 
      il.Emit(OpCodes.Stfld, fld); 
      il.Emit(OpCodes.Ret); 

      foreach (var method in typeof(T).GetMethods()) 
      { 
       var args = method.GetParameters(); 
       var methodImpl = type.DefineMethod(method.Name, 
        MethodAttributes.Private | MethodAttributes.Virtual, 
        method.ReturnType, 
        Array.ConvertAll(args, arg => arg.ParameterType)); 
       type.DefineMethodOverride(methodImpl, method); 
       il = methodImpl.GetILGenerator(); 
       if (method.ReturnType != typeof(void)) 
       { 
        il.Emit(OpCodes.Ldstr, 
         "Methods with return values are not supported"); 
        il.Emit(OpCodes.Newobj, typeof(NotSupportedException) 
         .GetConstructor(new Type[] {typeof(string)})); 
        il.Emit(OpCodes.Throw); 
        continue; 
       } 

       // get the iterator 
       var iter = il.DeclareLocal(typeof(IEnumerator<T>)); 
       il.Emit(OpCodes.Ldarg_0); 
       il.Emit(OpCodes.Ldfld, fld); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerable<T>) 
        .GetMethod("GetEnumerator"), null); 
       il.Emit(OpCodes.Stloc, iter); 
       Label tryFinally = il.BeginExceptionBlock(); 

       // jump to "progress the iterator" 
       Label loop = il.DefineLabel(); 
       il.Emit(OpCodes.Br_S, loop); 

       // process each item (invoke the paired method) 
       Label doItem = il.DefineLabel(); 
       il.MarkLabel(doItem); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator<T>) 
        .GetProperty("Current").GetGetMethod(), null); 
       for (int i = 0; i < args.Length; i++) 
       { // load the arguments 
        switch (i) 
        { 
         case 0: il.Emit(OpCodes.Ldarg_1); break; 
         case 1: il.Emit(OpCodes.Ldarg_2); break; 
         case 2: il.Emit(OpCodes.Ldarg_3); break; 
         default: 
          il.Emit(i < 255 ? OpCodes.Ldarg_S 
           : OpCodes.Ldarg, i + 1); 
          break; 
        } 
       } 
       il.EmitCall(OpCodes.Callvirt, method, null); 

       // progress the iterator 
       il.MarkLabel(loop); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator) 
        .GetMethod("MoveNext"), null); 
       il.Emit(OpCodes.Brtrue_S, doItem); 
       il.Emit(OpCodes.Leave_S, tryFinally); 

       // dispose iterator 
       il.BeginFinallyBlock(); 
       Label endFinally = il.DefineLabel(); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.Emit(OpCodes.Brfalse_S, endFinally); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IDisposable) 
        .GetMethod("Dispose"), null); 
       il.MarkLabel(endFinally); 
       il.EndExceptionBlock(); 
       il.Emit(OpCodes.Ret); 
      } 
      Cache<T>.Type = type.CreateType(); 
#if DEBUG  // for inspection purposes... 
      asm.Save(moduleName); 
#endif 
     } 
    } 
} 
+0

मुझे कुछ टाइपिंग बचाया :-) –

+0

मुझे लगता है कि आपके पास मामूली गलती है (संकलित नहीं होगी): इसके बजाय: कैश । टाइप = टाइप। क्रेट टाइप(); यह होना चाहिए: टाइप = type.CreateType(); –

+0

मैंने सुझाए गए कोड की कोशिश की, और ऐसा लगता है कि आपका उत्तर तर्कों के साथ विधियों को कवर नहीं करता है (बाधा देखें "इंटरफ़ेस में केवल शून्य विधियां हैं, तर्क के साथ या बिना")। वर्तमान में एक अपवाद है जब इंटरफेस में एक तर्क के साथ एक विधि शामिल है। –

2

आप उपवर्ग सकता List<T> या कुछ अन्य संग्रह वर्ग और केवल IStartable कक्षाएं होने के लिए T प्रकार सीमित करने के लिए where सामान्य प्रकार बाधा का उपयोग करें।

class StartableList<T> : List<T>, IStartable where T : IStartable 
{ 
    public StartableList(IEnumerable<T> arr) 
     : base(arr) 
    { 
    } 

    public void Start() 
    { 
     foreach (IStartable s in this) 
     { 
      s.Start(); 
     } 
    } 

    public void Stop() 
    { 
     foreach (IStartable s in this) 
     { 
      s.Stop(); 
     } 
    } 
} 

आप इस तरह की कक्षा भी घोषित कर सकते हैं यदि आप नहीं चाहते थे कि यह एक सामान्य वर्ग हो, जो एक प्रकार पैरामीटर की आवश्यकता हो।

public class StartableList : List<IStartable>, IStartable 
{ ... } 

आपका नमूना उपयोग कोड तो कुछ इस तरह दिखेगा:

var arr = new IStartable[] { new Foo(), new Bar("wow") }; 
var mygroup = new StartableList<IStartable>(arr); 
mygroup.Start(); // --> calls Foo's Start and Bar's Start 
+1

के साथ प्राप्त करना बहुत आसान है, मुझे लगता है कि यह प्रश्न का उत्तर नहीं देता है। – DonkeyMaster

+0

@ डोंकीमास्टर - नहीं, यह सटीक प्रश्न का उत्तर नहीं देता है, लेकिन मुझे लगता है कि अगर मैं सही तरीके से सवाल को कम करता हूं तो यह एक संभावित विकल्प है। मेरी पोस्ट मैन्युअल रूप से लिखित समाधान प्रदान करती है, मार्क ग्रेवेल का उत्कृष्ट नमूना एक (रनटाइम) कोड जनरेशन समाधान प्रदान करता है। मैं इसे बिना किसी तरीके से करने के लिए हाथ से बाहर नहीं जानता: मूल पोस्टर ने "कोड मैन्युअल रूप से लिखने के बिना, और कोड जनरेशन के बिना" समाधान के लिए कहा। –

+0

वास्तव में, @DonkeyMaster के रूप में, यह सवाल का जवाब नहीं देता है। यह कोड को स्पष्ट और शायद अधिक सुरुचिपूर्ण बनाता है, लेकिन सवाल बनी हुई है: मैं इसे समय पर लिखने के बिना रनटाइम पर कैसे बना सकता हूं (या इसे उत्पन्न कर सकता हूं)? –

0

आप के लिए सी # 4.0 इंतजार और गतिशील बंधन इस्तेमाल कर सकते हैं।

यह एक बहुत अच्छा विचार है - मैं कई अवसरों पर IDisposable के लिए इस लागू करने के लिए मिला है; जब मैं चाहता हूँ बहुत सी बातें निपटारा करने के लिए।ध्यान में रखना एक बात यह है कि त्रुटियों को कैसे संभाला जाएगा। क्या इसे लॉग इन करना चाहिए और दूसरों को शुरू करना चाहिए, आदि ... आपको कक्षा देने के लिए कुछ विकल्प चाहिए।

मैं डायनामिक प्रॉक्सी से परिचित नहीं हूं और इसका उपयोग कैसे किया जा सकता है।

+0

सी # 4.0 कुछ समय के लिए यहां नहीं होगा। अभी तक एक सीटीपी भी नहीं है! – DonkeyMaster

0

आप "सूची" कक्षा और उनकी विधि "ForEach" का उपयोग कर सकते हैं।

var startables = new List<IStartable>(array_of_startables); 
startables.ForEach(t => t.Start(); } 
+0

यह पहली बात है जो मेरे दिमाग में भी आई - लेकिन वह उपरोक्त "ग्रुप जेनरेटर" वर्ग के कार्यान्वयन के लिए कह रहा है। –

0

यदि मैं सही ढंग से समझता हूं, तो आप "ग्रुप जेनरेटर" के कार्यान्वयन के लिए पूछ रहे हैं।

CastleProxy के साथ किसी भी वास्तविक अनुभव के बिना मेरी सिफारिश इंटरफ़ेस में सूचीबद्ध प्रारंभिक विधियों को प्राप्त करने के लिए GetMethods() का उपयोग करना होगा और फिर प्रतिबिंब का उपयोग करके फ्लाई पर एक नया प्रकार बनाएं। ऑब्जेक्ट्स के माध्यम से बताए गए नए तरीकों के साथ स्वीकार करें और प्रत्येक संबंधित विधि को कॉल करें। प्रदर्शन बहुत बुरा नहीं होना चाहिए।

4

यह रूप में प्रतिबिंब आधारित समाधान के रूप में स्वच्छ एक इंटरफेस नहीं है, लेकिन एक बहुत ही सरल और लचीला समाधान तो जैसे एक forall विधि बनाने के लिए है :

static void ForAll<T>(this IEnumerable<T> items, Action<T> action) 
{ 
    foreach (T item in items) 
    { 
     action(item); 
    } 
} 

और इसलिए की तरह कहा जा सकता है:

arr.ForAll(x => x.Start()); 
2

Automapper इसका एक अच्छा समाधान है। यह एक उदाहरण बनाने के लिए LinFu पर निर्भर करता है जो एक इंटरफ़ेस लागू करता है, लेकिन यह कुछ हाइड्रेशन और मिश्रणों को कुछ हद तक धाराप्रवाह एपीआई के तहत ख्याल रखता है। LinFu लेखक का दावा है कि यह वास्तव में CastleProxy से अधिक हल्का और तेज है।

+0

टिप के लिए धन्यवाद, जब मेरे पास समय है तो मैं इसे देख लूंगा। –

संबंधित मुद्दे

 संबंधित मुद्दे