2009-07-30 36 views
9

我期待在拍攝一組對象,讓我們說有3個對象活着的那一刻,所有實現共同的接口,然後包裹內第四那些對象對象,也實現相同的接口。創建在運行時的接口的類,在C#

第四個目的的方法和屬性的實現將簡單地請那些3個基礎對象中的相關位。我知道在這種情況下會出現這樣的情況,但這是針對服務多播體系結構的,因此已經存在一些很好的侷限性。

我的問題是從哪裏開始。第四個對象的生成應該在運行時在內存中完成,所以我在想着Reflection.Emit,不幸的是我沒有足夠的經驗來知道從哪裏開始。

我一定要建立在內存中的組裝?它看起來確實如此,但我只想快速指向我應該開始的地方。

基本上我正在尋找一個接口和一個實現該接口的對象實例列表,並構造一個新對象,同時實現該接口,該接口應該「組播」所有方法調用和所有屬性訪問潛在的對象,至少儘可能多。在例外情況下會遇到大量問題,但當我接觸到它們時我會解決這些問題。

這是針對面向服務的體系結構,我希望現有的代碼以一個記錄器服務爲例,現在可以訪問多個記錄器服務,而無需更改使用服務的代碼。相反,我想運行時生成一個記錄器服務包裝器,它在內部簡單地調用多個基礎對象上的相關方法。

這是針對.NET 3.5和C#的。

+0

我居然寫了一個例子這樣做(在這裏SO)幾個月前...我會看,如果我能找到它... – 2009-07-30 09:38:05

+2

像這樣嗎? http://stackoverflow.com/questions/847809/how-can-i-write-a-generic-container-class-that-implements-a-given-interface-in-c/847975#847975 – 2009-07-30 09:39:33

+0

馬克,挑一個解決這個問題的方法,或者將問題標記爲重複,或者發佈我能接受的真實答案。 – 2009-07-30 09:53:31

回答

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#給你行動<牛逼>的是運營商和lambda /代表...

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); 
     } 
    } 
}