2009-07-30 29 views
9

Tôi đang xem xét việc tập hợp các đối tượng, giả sử có 3 đối tượng đang hoạt động tại thời điểm này, tất cả đều triển khai giao diện chung và sau đó bao bọc các đối tượng đó đối tượng, cũng thực hiện cùng một giao diện.Tạo lớp cho giao diện trong thời gian chạy, trong C#

Việc triển khai các phương thức và thuộc tính của đối tượng thứ tư chỉ đơn giản là gọi các bit có liên quan trên 3 đối tượng bên dưới đó. Tôi biết rằng sẽ có trường hợp ở đây, nơi nó sẽ không có ý nghĩa để làm điều đó, nhưng điều này là dành cho một kiến ​​trúc dịch vụ multicast vì vậy đã có một bộ tốt các hạn chế tại chỗ.

Câu hỏi của tôi là nơi bắt đầu. Việc tạo ra đối tượng thứ tư đó nên được thực hiện trong bộ nhớ, trong thời gian chạy, vì vậy tôi nghĩ rằng Reflection.Emit, tiếc là tôi không có đủ kinh nghiệm với điều đó để thậm chí biết bắt đầu từ đâu.

Tôi có phải xây dựng một bộ nhớ trong không? Nó chắc chắn trông như vậy, nhưng tôi chỉ muốn một con trỏ nhanh đến nơi tôi nên bắt đầu. Về cơ bản tôi đang xem xét một giao diện, và một danh sách các cá thể đối tượng đều triển khai giao diện đó, và xây dựng một đối tượng mới, cũng triển khai giao diện đó, "multicast" tất cả các cuộc gọi phương thức và truy cập thuộc tính tới tất cả các đối tượng cơ bản, ít nhất là càng nhiều càng tốt. Sẽ có rất nhiều vấn đề với ngoại lệ và như vậy nhưng tôi sẽ giải quyết các bit đó khi tôi nhận được chúng.

Đây là kiến ​​trúc hướng dịch vụ, nơi tôi muốn có mã hiện có, ví dụ như dịch vụ ghi nhật ký, để truy cập nhiều dịch vụ nhật ký, mà không phải thay đổi mã sử dụng dịch vụ . Thay vào đó, tôi muốn chạy runtime-tạo ra một trình bao bọc-dịch vụ-trình bao mà nội bộ đơn giản gọi các phương thức có liên quan trên nhiều đối tượng bên dưới.

Điều này dành cho .NET 3.5 và C#.

+0

Tôi thực sự đã viết một ví dụ mà đã làm điều này (ở đây trên SO) một vài tháng trước ... Tôi sẽ xem liệu tôi có thể tìm thấy nó ... –

+2

Giống như vậy? http://stackoverflow.com/questions/847809/how-can-i-write-a-generic-container-class-that-implements-a-given-interface-in-c/847975#847975 –

+0

Marc, chọn một cách để giải quyết vấn đề này, đánh dấu câu hỏi là trùng lặp hoặc đăng câu trả lời thực sự mà tôi có thể chấp nhận. –

Trả lời

5

(tôi biện minh một câu trả lời ở đây bằng cách thêm bối cảnh/info)

Vâng, vào lúc này Reflection.Emit là cách duy nhất để giải quyết vấn đề này.

Trong .NET 4.0, lớp Expression đã được mở rộng để hỗ trợ cả hai vòng và khối tuyên bố, vì vậy cho đơn sử dụng phương pháp, một biên soạn Expression sẽ là một ý tưởng tốt. Nhưng ngay cả điều này cũng không hỗ trợ các giao diện đa phương thức (chỉ các ủy nhiệm một phương thức).

May mắn thay, tôi đã thực hiện việc này trước đây; xem How can I write a generic container class that implements a given interface in C#?

1

Bạn có thực sự cần tạo assembly khi chạy không?

Có thể bạn không cần đến nó.

C# cung cấp cho bạn hành động <T>, các nhà điều hành và lambda/đại biểu ...

+0

Mã cho một hệ thống IoC, do đó, một số định nghĩa của những dịch vụ để gọi là chứa trong tập tin cấu hình, có thể được thay đổi. Vì vậy, tôi thực sự cần nó để được trong thời gian chạy, có. –

+0

Tôi đã làm một cái gì đó như thế này trước đây, chuyển dịch vụ sẽ được gọi trong cấu hình, bởi vì ngay cả khi bạn đang tiêm đối tượng trong thời gian chạy, bạn đã biết giao diện mà chúng mở rộng. Hãy nhớ rằng bạn có thể chuyển các Phương thức cho lớp học của bạn và gọi các đối tượng chuyển đó sang Gọi ... – kentaromiura

5

Tôi sẽ đăng triển khai của chính mình ở đây, nếu có ai quan tâm.

Điều này bị ảnh hưởng nặng nề và được sao chép từ câu trả lời của Marc mà tôi đã chấp nhận.

Mã này có thể được sử dụng để bọc một bộ đối tượng, tất cả triển khai một giao diện chung, bên trong một đối tượng mới, cũng triển khai giao diện đã nói. Khi các phương thức và thuộc tính trên đối tượng trả về được truy cập, các phương thức và thuộc tính tương ứng trên các đối tượng bên dưới được truy cập theo cùng một cách.

Ở đây có con rồng: Đây là cách sử dụng cụ thể. Có tiềm năng cho các vấn đề lẻ với điều này, đặc biệt vì mã không đảm bảo rằng tất cả các đối tượng bên dưới được cung cấp chính xác các đối tượng giống như callee đang truyền (hoặc đúng hơn, nó không cấm một trong các đối tượng bên dưới gây rối với các đối số) và đối với các phương thức trả về một giá trị, chỉ giá trị trả về cuối cùng mới được trả về. Đối với các đối số ra/ref, tôi thậm chí không thử nghiệm cách thức hoạt động, nhưng nó có thể không. Bạn đã được cảnh báo.

#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

Cuộc gọi 'Sequences.Repeat' này là gì? Và đâu là lớp 'Chuỗi'? BTW, cảm ơn câu trả lời tuyệt vời này! – m1o2

Các vấn đề liên quan