2009-07-30 21 views
9

Estoy buscando tomar un conjunto de objetos, digamos que hay 3 objetos vivos en este momento, todos implementan una interfaz común, y luego envuelven esos objetos dentro de un cuarto objeto, también implementando la misma interfaz.Creando una clase para una interfaz en tiempo de ejecución, en C#

Las implementaciones de métodos y propiedades del cuarto objeto simplemente llamarían los bits relevantes en esos 3 objetos subyacentes. Sé que habrá casos aquí donde no tenga sentido hacerlo, pero esto es para una arquitectura de multidifusión de servicio, por lo que ya existe un buen conjunto de limitaciones.

Mi pregunta es por dónde empezar. La generación de ese cuarto objeto debería hacerse en la memoria, en tiempo de ejecución, así que estoy pensando en Reflection.Emit, desafortunadamente no tengo suficiente experiencia para saber por dónde empezar.

¿Debo construir un ensamblaje en memoria? Seguro que se ve de esa manera, pero solo me gustaría un rápido indicador de dónde debería comenzar.

Básicamente estoy buscando tomar una interfaz, y una lista de instancias de objetos implementando esa interfaz y construyendo un nuevo objeto, también implementando esa interfaz, que debe "multidifundir" todas las llamadas a métodos y el acceso a todas las propiedades objetos subyacentes, al menos tanto como sea posible. Habrá un montón de problemas con excepciones, pero abordaré esos bits cuando los encuentre.

Esto es para una arquitectura orientada a servicios, donde me gustaría tener un código existente que tenga, por ejemplo, un servicio logger, para acceder ahora a múltiples servicios de registrador, sin tener que cambiar el código que usa los servicios . En su lugar, me gustaría generar un wrapper de servicio de registrador que internamente llame a los métodos relevantes en múltiples objetos subyacentes.

Esto es para .NET 3.5 y C#.

+0

De hecho, me escribió un ejemplo que hizo esto (aquí en SO) hace unos meses ... Voy a ver si puedo encontrarlo ... –

+2

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

+0

Marc, elija una Para resolver esto, marque la pregunta como duplicada o publique una respuesta real que pueda aceptar. –

Respuesta

5

(estoy justificando la respuesta aquí mediante la adición extra de contexto/info)

Sí, en el momento Reflection.Emit es la única forma de resolver esto.

En .NET 4.0, la clase Expression se ha ampliado para soportar ambos bucles y bloques de instrucciones, por lo que para el uso sola método, un compilado Expression sería una buena idea. Pero incluso esto no admitirá interfaces de métodos múltiples (solo delegados de método único).

Afortunadamente, he hecho esto anteriormente; ver How can I write a generic container class that implements a given interface in C#?

1

¿De verdad necesitabas crear el ensamblado en tiempo de ejecución?

Probablemente no lo necesite.

C# Acción <T> dan, es el operador y/lambda delegados ...

+0

El código para un sistema IoC, así que desafortunadamente algunas de las definiciones de qué servicios invocar están contenidas en los archivos de configuración, que se pueden cambiar. Entonces realmente necesito que esté en tiempo de ejecución, sí. –

+0

Ya he hecho algo así antes, pasando el servicio que se llamará en la configuración, porque incluso si está inyectando el objeto en tiempo de ejecución, ya conoce la interfaz que extienden. Recuerde que puede pasar los Métodos a su clase e invocar a los objetos que pasan a Invocar ... – kentaromiura

5

Voy a publicar mi propia implementación aquí, si alguien está interesado.

Esto está muy influenciado y copiado de la respuesta de Marc, que acepté.

El código se puede usar para envolver un conjunto de objetos, todos implementando una interfaz común, dentro de un nuevo objeto, también implementando dicha interfaz. Cuando se accede a métodos y propiedades en el objeto devuelto, se accede a los métodos y propiedades correspondientes en los objetos subyacentes de la misma manera.

Aquí hay dragones: Esto es para un uso específico. Existe la posibilidad de problemas extraños con esto, en particular porque el código no garantiza que todos los objetos subyacentes reciban exactamente los mismos objetos que el destinatario de la llamada (o más bien, no prohíbe a uno de los objetos subyacentes interferir con los argumentos) y para los métodos que devuelven un valor, solo se devolverá el último valor devuelto. En cuanto a los argumentos out/ref, ni siquiera he probado cómo funciona, pero probablemente no. Usted ha sido advertido.

#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

¿Qué significa esta llamada 'Secuencias.Repetir'? ¿Y dónde está la clase 'Secuencias'? Por cierto, gracias por esta increíble respuesta! – m1o2

Cuestiones relacionadas