2009-07-30 1 views
9

Я ищу набор объектов, предположим, что на данный момент есть 3 объекта, которые реализуют общий интерфейс, а затем обертывают эти объекты внутри четвертого объект, также реализующий один и тот же интерфейс.Создание класса для интерфейса во время выполнения, на C#

Реализация четвертого объекта методов и свойств просто вызовет соответствующие биты этих трех базовых объектов. Я знаю, что здесь будут случаи, когда это не имеет смысла делать это, но это для архитектуры многоадресной услуги, так что уже существует хороший набор ограничений.

Мой вопрос - с чего начать. Генерация этого четвертого объекта должна выполняться в памяти во время выполнения, поэтому я думаю, Reflection.Emit, к сожалению, у меня недостаточно опыта, чтобы даже знать, с чего начать.

Должен ли я построить сборку в памяти? Конечно, это выглядит так, но мне просто хотелось бы быстро указать, где я должен начинать.

В основном я ищу интерфейс и список экземпляров объектов, реализующих этот интерфейс, и создание нового объекта, а также реализацию этого интерфейса, который должен «многоадресный» вызов всех методов и доступ к свойствам для всех лежащих в основе объектов, по крайней мере, насколько это возможно. Будут куча проблем с исключениями и т. Д., Но я займусь этими битами, когда доберусь до них.

Это для сервис-ориентированной архитектуры, где я хотел бы иметь существующий код, который, например, использует службу регистрации, чтобы теперь обращаться к нескольким службам регистрации, не изменяя код, который использует службы , Вместо этого я бы хотел запустить среду выполнения - создать обертку для журнала, которая внутренне просто вызывает соответствующие методы для нескольких базовых объектов.

Это для .NET 3.5 и C#.

+0

Я на самом деле написал пример, который это сделал (здесь на SO) несколько месяцев назад ... Я посмотрю, смогу ли я найти его ... –

+2

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

+0

Марк, выберите способ решить это, либо пометить вопрос как дубликат, либо опубликовать реальный ответ, который я могу принять. –

ответ

5

(я оправдывая ответ здесь, добавив дополнительный контекст/данные)

Да, в данный момент Reflection.Emit - единственный способ решить эту проблему.

В .NET 4.0 Expression класс был расширен для поддержки обоих циклов и операторные блоки, так что для одноразового использования метода, скомпилированный Expression будет хорошей идеей. Но даже это не будет поддерживать интерфейсы нескольких методов (только делегаты из одного метода).

К счастью, я сделал это ранее; см. How can I write a generic container class that implements a given interface in C#?

1

Вам действительно нужно создать сборку во время выполнения?

Возможно, вам это не нужно.

C# дать вам действие <T>, то есть оператор и лямбда/делегаты ...

+0

Код для системы IoC, поэтому, к сожалению, некоторые определения того, какие службы для вызова содержатся в файлах конфигурации, которые можно изменить. Поэтому мне действительно нужно, чтобы это было во время выполнения, да. –

+0

Я уже делал что-то подобное раньше, передавая службу, которая будет вызываться в конфигурации, потому что даже если вы вводите объект во время выполнения, вы уже знаете интерфейс, который они распространяют. Помните, что вы можете передавать Методы в свой класс и вызывать этот объект для вызова Invoke ... – kentaromiura

5

Я отправлю свою собственную реализацию здесь, если кому-то это интересно.

Это сильно повлияло и скопировано из ответа Марка, который я принял.

Код может использоваться для обертывания набора объектов, реализующих общий интерфейс, внутри нового объекта, а также реализации указанного интерфейса. Когда доступны методы и свойства для возвращаемого объекта, аналогичным образом доступны соответствующие методы и свойства для базовых объектов.

Здесь есть драконы: Это для конкретного использования. Существует потенциал для нечетных проблем с этим, в частности, поскольку код не гарантирует, что всем базовым объектам предоставляются те же самые объекты, что и вызываемый (или, скорее, он не запрещает одному из базовых объектов входить в конфликт с аргументами) , а для методов, возвращающих значение, возвращается только последнее возвращаемое значение. Что касается аргументов out/ref, я даже не проверял, как это работает, но, вероятно, нет. Вы были предупреждены.

#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

Что это за вызов «Sequences.Repeat»? И где класс «Последовательности»? Кстати, спасибо за этот замечательный ответ! – m1o2