2009-05-11 22 views
24

Bối cảnh: .NET 3.5, VS2008. Tôi không chắc chắn về tiêu đề của câu hỏi này, vì vậy bạn cũng có thể tự do nhận xét về tiêu đề này :-)Làm thế nào tôi có thể viết một lớp container chung mà thực hiện một giao diện nhất định trong C#?

Đây là kịch bản: Tôi có một số lớp, nói Foo và Bar, tất cả đều thực hiện giao diện sau :

public interface IStartable 
{ 
    void Start(); 
    void Stop(); 
} 

Và bây giờ tôi muốn có một lớp container, mà được một IEnumerable <IStartable> như một tham số trong constructor của nó. Lớp này, đến lượt mình, cũng nên triển khai giao diện IStartable:

public class StartableGroup : IStartable // this is the container class 
{ 
    private readonly IEnumerable<IStartable> startables; 

    public StartableGroup(IEnumerable<IStartable> startables) 
    { 
     this.startables = startables; 
    } 

    public void Start() 
    { 
     foreach (var startable in startables) 
     { 
      startable.Start(); 
     } 
    } 

    public void Stop() 
    { 
     foreach (var startable in startables) 
     { 
      startable.Stop(); 
     } 
    } 
} 

Vì vậy, câu hỏi của tôi là: làm thế nào tôi có thể làm điều đó mà không cần viết mã theo cách thủ công và không cần tạo mã? Nói cách khác, tôi muốn có một vài thứ như sau.

var arr = new IStartable[] { new Foo(), new Bar("wow") }; 
var mygroup = GroupGenerator<IStartable>.Create(arr); 
mygroup.Start(); // --> calls Foo's Start and Bar's Start 

ràng buộc:

  • Không sinh mã (có nghĩa là, không có mã văn bản thực tại thời gian biên dịch)
  • Giao diện chỉ có khoảng trống phương pháp, có hoặc không có đối số

Động lực:

  • Tôi có một ứng dụng khá lớn, với rất nhiều plugin của các giao diện khác nhau. Viết thủ công lớp "nhóm chứa" cho từng giao diện "quá tải" của dự án với các lớp
  • Viết mã theo cách thủ công là dễ bị lỗi
  • Mọi bổ sung hoặc cập nhật chữ ký cho giao diện IStartable sẽ dẫn đến thay đổi (thủ công) trong " chứa nhóm" class
  • Học

tôi hiểu rằng tôi phải sử dụng phản ánh ở đây, nhưng tôi muốn sử dụng một khuôn khổ mạnh mẽ (như Lâu đài của DynamicProxy hoặc RunSharp) để thực hiện hệ thống dây điện cho tôi.

Mọi suy nghĩ?

+0

Vì vậy, bạn * không * muốn có một lớp StartableGroup? Có gì sai với nó? – Noldorin

+0

Tôi có thể hỏi: tại sao? Vấn đề mà điều này cần phải giải quyết là gì? (điều này có thể ảnh hưởng đến câu trả lời ...). –

+0

@Noldorin, @Marc Gravell, động lực được thêm vào câu hỏi ban đầu. –

Trả lời

26

Đây không phải là đẹp, nhưng có vẻ như để làm việc:

public static class GroupGenerator 
{ 
    public static T Create<T>(IEnumerable<T> items) where T : class 
    { 
     return (T)Activator.CreateInstance(Cache<T>.Type, items); 
    } 
    private static class Cache<T> where T : class 
    { 
     internal static readonly Type Type; 
     static Cache() 
     { 
      if (!typeof(T).IsInterface) 
      { 
       throw new InvalidOperationException(typeof(T).Name 
        + " is not an interface"); 
      } 
      AssemblyName an = new AssemblyName("tmp_" + typeof(T).Name); 
      var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(
       an, AssemblyBuilderAccess.RunAndSave); 
      string moduleName = Path.ChangeExtension(an.Name,"dll"); 
      var module = asm.DefineDynamicModule(moduleName, false); 
      string ns = typeof(T).Namespace; 
      if (!string.IsNullOrEmpty(ns)) ns += "."; 
      var type = module.DefineType(ns + "grp_" + typeof(T).Name, 
       TypeAttributes.Class | TypeAttributes.AnsiClass | 
       TypeAttributes.Sealed | TypeAttributes.NotPublic); 
      type.AddInterfaceImplementation(typeof(T)); 

      var fld = type.DefineField("items", typeof(IEnumerable<T>), 
       FieldAttributes.Private); 
      var ctor = type.DefineConstructor(MethodAttributes.Public, 
       CallingConventions.HasThis, new Type[] { fld.FieldType }); 
      var il = ctor.GetILGenerator(); 
      // store the items 
      il.Emit(OpCodes.Ldarg_0); 
      il.Emit(OpCodes.Ldarg_1); 
      il.Emit(OpCodes.Stfld, fld); 
      il.Emit(OpCodes.Ret); 

      foreach (var method in typeof(T).GetMethods()) 
      { 
       var args = method.GetParameters(); 
       var methodImpl = type.DefineMethod(method.Name, 
        MethodAttributes.Private | MethodAttributes.Virtual, 
        method.ReturnType, 
        Array.ConvertAll(args, arg => arg.ParameterType)); 
       type.DefineMethodOverride(methodImpl, method); 
       il = methodImpl.GetILGenerator(); 
       if (method.ReturnType != typeof(void)) 
       { 
        il.Emit(OpCodes.Ldstr, 
         "Methods with return values are not supported"); 
        il.Emit(OpCodes.Newobj, typeof(NotSupportedException) 
         .GetConstructor(new Type[] {typeof(string)})); 
        il.Emit(OpCodes.Throw); 
        continue; 
       } 

       // get the iterator 
       var iter = il.DeclareLocal(typeof(IEnumerator<T>)); 
       il.Emit(OpCodes.Ldarg_0); 
       il.Emit(OpCodes.Ldfld, fld); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerable<T>) 
        .GetMethod("GetEnumerator"), null); 
       il.Emit(OpCodes.Stloc, iter); 
       Label tryFinally = il.BeginExceptionBlock(); 

       // jump to "progress the iterator" 
       Label loop = il.DefineLabel(); 
       il.Emit(OpCodes.Br_S, loop); 

       // process each item (invoke the paired method) 
       Label doItem = il.DefineLabel(); 
       il.MarkLabel(doItem); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator<T>) 
        .GetProperty("Current").GetGetMethod(), null); 
       for (int i = 0; i < args.Length; i++) 
       { // load the arguments 
        switch (i) 
        { 
         case 0: il.Emit(OpCodes.Ldarg_1); break; 
         case 1: il.Emit(OpCodes.Ldarg_2); break; 
         case 2: il.Emit(OpCodes.Ldarg_3); break; 
         default: 
          il.Emit(i < 255 ? OpCodes.Ldarg_S 
           : OpCodes.Ldarg, i + 1); 
          break; 
        } 
       } 
       il.EmitCall(OpCodes.Callvirt, method, null); 

       // progress the iterator 
       il.MarkLabel(loop); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator) 
        .GetMethod("MoveNext"), null); 
       il.Emit(OpCodes.Brtrue_S, doItem); 
       il.Emit(OpCodes.Leave_S, tryFinally); 

       // dispose iterator 
       il.BeginFinallyBlock(); 
       Label endFinally = il.DefineLabel(); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.Emit(OpCodes.Brfalse_S, endFinally); 
       il.Emit(OpCodes.Ldloc, iter); 
       il.EmitCall(OpCodes.Callvirt, typeof(IDisposable) 
        .GetMethod("Dispose"), null); 
       il.MarkLabel(endFinally); 
       il.EndExceptionBlock(); 
       il.Emit(OpCodes.Ret); 
      } 
      Cache<T>.Type = type.CreateType(); 
#if DEBUG  // for inspection purposes... 
      asm.Save(moduleName); 
#endif 
     } 
    } 
} 
+0

Đã lưu cho tôi một số cách gõ :-) –

+0

Tôi nghĩ rằng bạn có một lỗi nhỏ ở đó (sẽ không biên dịch): Thay vì: Bộ nhớ cache .Type = type.CreateType(); Phải là: Loại = type.CreateType(); –

+0

Tôi đã thử mã gợi ý, và có vẻ như câu trả lời của bạn không bao gồm các phương thức với các đối số (xem ràng buộc "Giao diện chỉ có các phương thức void, có hoặc không có đối số"). Hiện tại có một ngoại lệ khi giao diện bao gồm một phương thức với một đối số duy nhất. –

2

Bạn có thể phân lớp List<T> hoặc một số loại bộ sưu tập khác và sử dụng ràng buộc loại chung where để giới hạn loại T chỉ là các lớp IStartable.

class StartableList<T> : List<T>, IStartable where T : IStartable 
{ 
    public StartableList(IEnumerable<T> arr) 
     : base(arr) 
    { 
    } 

    public void Start() 
    { 
     foreach (IStartable s in this) 
     { 
      s.Start(); 
     } 
    } 

    public void Stop() 
    { 
     foreach (IStartable s in this) 
     { 
      s.Stop(); 
     } 
    } 
} 

Bạn cũng có thể khai báo lớp như thế này nếu bạn không muốn lớp này là lớp chung yêu cầu tham số kiểu.

public class StartableList : List<IStartable>, IStartable 
{ ... } 

đang sử dụng mẫu của bạn sau đó sẽ giống như thế này:

var arr = new IStartable[] { new Foo(), new Bar("wow") }; 
var mygroup = new StartableList<IStartable>(arr); 
mygroup.Start(); // --> calls Foo's Start and Bar's Start 
+1

Tôi nghĩ rằng không trả lời được câu hỏi. – DonkeyMaster

+0

@DonkeyMaster - Không, nó không trả lời câu hỏi chính xác nhưng tôi nghĩ rằng nó là một lựa chọn có thể nếu tôi undestand câu hỏi một cách chính xác. Bài viết của tôi cung cấp một giải pháp được viết bằng tay, mẫu tuyệt vời của Marc Gravell cung cấp một giải pháp tạo mã (chạy). Tôi không biết cách nào để làm điều đó mà không có: poster ban đầu yêu cầu giải pháp "mà không cần viết mã một cách thủ công và không cần tạo mã". –

+0

Thật vậy, như @DonkeyMaster lưu ý, điều này không trả lời câu hỏi. Nó làm cho mã rõ ràng hơn và có lẽ thanh lịch hơn, nhưng câu hỏi vẫn là: làm thế nào tôi có thể tạo mã như vậy trong thời gian chạy, mà không cần phải viết nó (hoặc tạo ra nó) tại thời điểm thiết kế? –

0

Bạn có thể chờ đợi cho C# 4.0 và sử dụng năng động ràng buộc.

Đây là một ý tưởng tuyệt vời - Tôi đã phải thực hiện điều này cho IDisposable trong một số trường hợp; khi tôi muốn nhiều thứ được xử lý.Một điều cần ghi nhớ mặc dù là lỗi sẽ được xử lý như thế nào. Nên nó đăng nhập và tiếp tục bắt đầu những người khác, vv ... Bạn sẽ cần một số tùy chọn để cung cấp cho các lớp học.

Tôi không quen với DynamicProxy và cách nó có thể được sử dụng tại đây.

+0

C# 4.0 sẽ không có ở đây một thời gian. Thậm chí không có một CTP ra! – DonkeyMaster

0

Bạn có thể sử dụng lớp "Danh sách" và phương thức "ForEach" của họ.

var startables = new List<IStartable>(array_of_startables); 
startables.ForEach(t => t.Start(); } 
+0

Đây là điều đầu tiên tôi nghĩ đến - nhưng anh ấy yêu cầu thực hiện lớp "GroupGenerator" ở trên. –

0

Nếu tôi hiểu chính xác, bạn đang yêu cầu triển khai "GroupGenerator".

Nếu không có bất kỳ kinh nghiệm thực tế nào với CastleProxy, tôi sẽ sử dụng GetMethods() để lấy các phương thức ban đầu được liệt kê trong giao diện và sau đó tạo một kiểu mới bằng cách sử dụng Reflection.Emit với các phương thức mới liệt kê thông qua các đối tượng và gọi từng phương thức tương ứng. Hiệu suất không quá tệ.

4

Nó không phải là sạch một giao diện như các giải pháp phản ánh dựa, nhưng một giải pháp rất đơn giản và linh hoạt là tạo ra một phương pháp ForAll như vậy :

static void ForAll<T>(this IEnumerable<T> items, Action<T> action) 
{ 
    foreach (T item in items) 
    { 
     action(item); 
    } 
} 

Và có thể được gọi như vậy:

arr.ForAll(x => x.Start()); 
2

Automapper là một giải pháp tốt cho việc này. Nó dựa trên LinFu bên dưới để tạo ra một thể hiện thực hiện một giao diện, nhưng nó sẽ chăm sóc một số hydrat hóa, và mixin dưới một api hơi thông thạo. Tác giả LinFu tuyên bố rằng nó thực sự nhẹ hơn và nhanh hơn so với Proxy của Castle.

+0

Cảm ơn lời khuyên, tôi sẽ xem xét nó khi tôi có thời gian. –

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