2009-07-30 16 views
9

Sto cercando di prendere una serie di oggetti, diciamo che ci sono 3 oggetti vivi al momento, che implementano un'interfaccia comune, e poi avvolgono quegli oggetti all'interno di una quarta oggetto, implementando anche la stessa interfaccia.Creazione di una classe per un'interfaccia in fase di esecuzione, in C#

Le implementazioni di metodi e proprietà del quarto oggetto chiamerebbero semplicemente i bit rilevanti su questi 3 oggetti sottostanti. So che qui ci saranno casi in cui non avrà senso farlo, ma questo è per un'architettura multicast di servizi, quindi c'è già un buon set di limitazioni.

La mia domanda è da dove cominciare. La generazione di quel quarto oggetto dovrebbe essere fatta in memoria, in fase di esecuzione, quindi sto pensando a Reflection.Emit, sfortunatamente non ho abbastanza esperienza per sapere da dove cominciare.

Devo costruire un assieme in memoria? Di certo sembra così, ma vorrei solo un rapido puntatore a dove dovrei iniziare.

Fondamentalmente sto considerando un'interfaccia e un elenco di istanze di oggetti che implementano quell'interfaccia e che costruiscono un nuovo oggetto, implementando anche quell'interfaccia, che dovrebbe "multicastare" tutte le chiamate di metodo e l'accesso a tutte le proprietà oggetti sottostanti, almeno il più possibile. Ci saranno un mucchio di problemi con eccezioni e simili, ma affronterò quei bit quando li raggiungerò.

Questo è per un'architettura orientata ai servizi, in cui mi piacerebbe avere il codice esistente che prende, ad esempio, un servizio di logger, per accedere ora a più servizi di logger, senza dover cambiare il codice che utilizza i servizi . Invece, mi piacerebbe runtime-generare un logger-service-wrapper che internamente chiama semplicemente i metodi rilevanti su più oggetti sottostanti.

Questo è per .NET 3.5 e C#.

+0

realtà ho scritto un esempio che ha fatto questo (qui su SO) qualche mese fa ... Vedrò se riesco a trovarlo ... –

+2

Come così? http://stackoverflow.com/questions/847809/how-can-i-write-a-generic-container-class-that-implements-a-given-interface-in-c/847975#847975 –

+0

Marc, scegli un per risolvere questo problema, contrassegna la domanda come duplicata o posta una risposta reale che posso accettare. –

risposta

5

(che sto giustificando una risposta qui con l'aggiunta di ulteriore contesto/info)

Sì, in questo momento Reflection.Emit è l'unico modo per risolvere questo problema.

In .NET 4.0, la classe Expression è stato esteso per supportare entrambi i loop e blocchi di dichiarazione, così per singolo metodo utilizzo, un compilato Expression sarebbe una buona idea. Ma anche questo non supporterà interfacce multi-metodo (solo delegati a metodo singolo).

Fortunatamente, l'ho già fatto in precedenza; vedi How can I write a generic container class that implements a given interface in C#?

1

Hai davvero bisogno di creare l'assembly in fase di esecuzione?

Probabilmente non ne hai bisogno.

C# si d'azione <T> danno, l'operatore is e lambda/delegati ...

+0

Il codice per un sistema IoC, quindi purtroppo alcune delle definizioni di quali servizi invocare sono contenute nei file di configurazione, che possono essere modificati. Quindi ho davvero bisogno che sia in runtime, sì. –

+0

Ho già fatto qualcosa di simile prima, passando il servizio che verrà chiamato nella configurazione, perché anche se si sta iniettando l'oggetto in fase di esecuzione, si conosce già l'interfaccia che estendono. Ricordare che è possibile passare i metodi alla classe e richiamare gli oggetti di passaggio in Invoke ... – kentaromiura

5

Pubblicherò la mia implementazione qui, se qualcuno è interessato.

Questo è fortemente influenzato e copiato dalla risposta di Marc, che ho accettato.

Il codice può essere utilizzato per avvolgere un insieme di oggetti, tutti implementando un'interfaccia comune, all'interno di un nuovo oggetto, implementando anche detta interfaccia. Quando si accede a metodi e proprietà sull'oggetto restituito, si accede ai metodi e alle proprietà corrispondenti sugli oggetti sottostanti allo stesso modo.

Qui ci sono i draghi: Questo è per un uso specifico. C'è del potenziale per problemi dispari con questo, in particolare dal momento che il codice non garantisce che tutti gli oggetti sottostanti ricevano esattamente gli stessi oggetti che il callee sta passando (o piuttosto, non proibisce a uno degli oggetti sottostanti di fare scherzi con gli argomenti) e per i metodi che restituiscono un valore, verrà restituito solo l'ultimo valore restituito. Per quanto riguarda gli argomenti out/ref, non ho nemmeno provato come funziona, ma probabilmente no. Sei stato avvisato.

#region Using 

using System; 
using System.Linq; 
using System.Diagnostics; 
using System.Reflection; 
using System.Reflection.Emit; 
using LVK.Collections; 

#endregion 

namespace LVK.IoC 
{ 
    /// <summary> 
    /// This class implements a service wrapper that can wrap multiple services into a single multicast 
    /// service, that will in turn dispatch all method calls down into all the underlying services. 
    /// </summary> 
    /// <remarks> 
    /// This code is heavily influenced and copied from Marc Gravell's implementation which he 
    /// posted on Stack Overflow here: http://stackoverflow.com/questions/847809 
    /// </remarks> 
    public static class MulticastService 
    { 
     /// <summary> 
     /// Wrap the specified services in a single multicast service object. 
     /// </summary> 
     /// <typeparam name="TService"> 
     /// The type of service to implement a multicast service for. 
     /// </typeparam> 
     /// <param name="services"> 
     /// The underlying service objects to multicast all method calls to. 
     /// </param> 
     /// <returns> 
     /// The multicast service instance. 
     /// </returns> 
     /// <exception cref="ArgumentNullException"> 
     /// <para><paramref name="services"/> is <c>null</c>.</para> 
     /// <para>- or -</para> 
     /// <para><paramref name="services"/> contains a <c>null</c> reference.</para> 
     /// </exception> 
     /// <exception cref="ArgumentException"> 
     /// <para><typeparamref name="TService"/> is not an interface type.</para> 
     /// </exception> 
     public static TService Wrap<TService>(params TService[] services) 
      where TService: class 
     { 
      return (TService)Wrap(typeof(TService), (Object[])services); 
     } 

     /// <summary> 
     /// Wrap the specified services in a single multicast service object. 
     /// </summary> 
     /// <param name="serviceInterfaceType"> 
     /// The <see cref="Type"/> object for the service interface to implement a multicast service for. 
     /// </param> 
     /// <param name="services"> 
     /// The underlying service objects to multicast all method calls to. 
     /// </param> 
     /// <returns> 
     /// The multicast service instance. 
     /// </returns> 
     /// <exception cref="ArgumentNullException"> 
     /// <para><paramref name="serviceInterfaceType"/> is <c>null</c>.</para> 
     /// <para>- or -</para> 
     /// <para><paramref name="services"/> is <c>null</c>.</para> 
     /// <para>- or -</para> 
     /// <para><paramref name="services"/> contains a <c>null</c> reference.</para> 
     /// </exception> 
     /// <exception cref="ArgumentException"> 
     /// <para><typeparamref name="TService"/> is not an interface type.</para> 
     /// </exception> 
     /// <exception cref="InvalidOperationException"> 
     /// <para>One or more of the service objects in <paramref name="services"/> does not implement 
     /// the <paramref name="serviceInterfaceType"/> interface.</para> 
     /// </exception> 
     public static Object Wrap(Type serviceInterfaceType, params Object[] services) 
     { 
      #region Parameter Validation 

      if (Object.ReferenceEquals(null, serviceInterfaceType)) 
       throw new ArgumentNullException("serviceInterfaceType"); 
      if (!serviceInterfaceType.IsInterface) 
       throw new ArgumentException("serviceInterfaceType"); 
      if (Object.ReferenceEquals(null, services) || services.Length == 0) 
       throw new ArgumentNullException("services"); 
      foreach (var service in services) 
      { 
       if (Object.ReferenceEquals(null, service)) 
        throw new ArgumentNullException("services"); 
       if (!serviceInterfaceType.IsAssignableFrom(service.GetType())) 
        throw new InvalidOperationException("One of the specified services does not implement the specified service interface"); 
      } 

      #endregion 

      if (services.Length == 1) 
       return services[0]; 

      AssemblyName assemblyName = new AssemblyName(String.Format("tmp_{0}", serviceInterfaceType.FullName)); 
      String moduleName = String.Format("{0}.dll", assemblyName.Name); 
      String ns = serviceInterfaceType.Namespace; 
      if (!String.IsNullOrEmpty(ns)) 
       ns += "."; 

      var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, 
       AssemblyBuilderAccess.RunAndSave); 
      var module = assembly.DefineDynamicModule(moduleName, false); 
      var type = module.DefineType(String.Format("{0}Multicast_{1}", ns, serviceInterfaceType.Name), 
       TypeAttributes.Class | 
       TypeAttributes.AnsiClass | 
       TypeAttributes.Sealed | 
       TypeAttributes.NotPublic); 
      type.AddInterfaceImplementation(serviceInterfaceType); 

      var ar = Array.CreateInstance(serviceInterfaceType, services.Length); 
      for (Int32 index = 0; index < services.Length; index++) 
       ar.SetValue(services[index], index); 

      // Define _Service0..N-1 private service fields 
      FieldBuilder[] fields = new FieldBuilder[services.Length]; 
      var cab = new CustomAttributeBuilder(
       typeof(DebuggerBrowsableAttribute).GetConstructor(new Type[] { typeof(DebuggerBrowsableState) }), 
       new Object[] { DebuggerBrowsableState.Never }); 
      for (Int32 index = 0; index < services.Length; index++) 
      { 
       fields[index] = type.DefineField(String.Format("_Service{0}", index), 
        serviceInterfaceType, FieldAttributes.Private); 

       // Ensure the field don't show up in the debugger tooltips 
       fields[index].SetCustomAttribute(cab); 
      } 

      // Define a simple constructor that takes all our services as arguments 
      var ctor = type.DefineConstructor(MethodAttributes.Public, 
       CallingConventions.HasThis, 
       Sequences.Repeat(serviceInterfaceType, services.Length).ToArray()); 
      var generator = ctor.GetILGenerator(); 

      // Store each service into its own fields 
      for (Int32 index = 0; index < services.Length; index++) 
      { 
       generator.Emit(OpCodes.Ldarg_0); 
       switch (index) 
       { 
        case 0: 
         generator.Emit(OpCodes.Ldarg_1); 
         break; 

        case 1: 
         generator.Emit(OpCodes.Ldarg_2); 
         break; 

        case 2: 
         generator.Emit(OpCodes.Ldarg_3); 
         break; 

        default: 
         generator.Emit(OpCodes.Ldarg, index + 1); 
         break; 
       } 
       generator.Emit(OpCodes.Stfld, fields[index]); 
      } 
      generator.Emit(OpCodes.Ret); 

      // Implement all the methods of the interface 
      foreach (var method in serviceInterfaceType.GetMethods()) 
      { 
       var args = method.GetParameters(); 
       var methodImpl = type.DefineMethod(method.Name, 
        MethodAttributes.Private | MethodAttributes.Virtual, 
        method.ReturnType, (from arg in args select arg.ParameterType).ToArray()); 
       type.DefineMethodOverride(methodImpl, method); 

       // Generate code to simply call down into each service object 
       // Any return values are discarded, except the last one, which is returned 
       generator = methodImpl.GetILGenerator(); 
       for (Int32 index = 0; index < services.Length; index++) 
       { 
        generator.Emit(OpCodes.Ldarg_0); 
        generator.Emit(OpCodes.Ldfld, fields[index]); 
        for (Int32 paramIndex = 0; paramIndex < args.Length; paramIndex++) 
        { 
         switch (paramIndex) 
         { 
          case 0: 
           generator.Emit(OpCodes.Ldarg_1); 
           break; 

          case 1: 
           generator.Emit(OpCodes.Ldarg_2); 
           break; 

          case 2: 
           generator.Emit(OpCodes.Ldarg_3); 
           break; 

          default: 
           generator.Emit((paramIndex < 255) 
            ? OpCodes.Ldarg_S 
            : OpCodes.Ldarg, 
            paramIndex + 1); 
           break; 
         } 

        } 
        generator.Emit(OpCodes.Callvirt, method); 
        if (method.ReturnType != typeof(void) && index < services.Length - 1) 
         generator.Emit(OpCodes.Pop); // discard N-1 return values 
       } 
       generator.Emit(OpCodes.Ret); 
      } 

      return Activator.CreateInstance(type.CreateType(), services); 
     } 
    } 
} 
+0

Che cos'è questa chiamata "Sequences.Repeat"? E dov'è la classe 'Sequences'? A proposito, grazie per questa fantastica risposta! – m1o2

Problemi correlati