2009-05-11 6 views
24

컨텍스트 : .NET 3.5, VS2008. 이 질문의 제목에 대해 잘 모르겠습니다. 제목에 대해서도 자유롭게 의견을 말하자면 :-)C#에서 주어진 인터페이스를 구현하는 일반 컨테이너 클래스를 작성하려면 어떻게해야합니까?

여기 시나리오가 있습니다 : Foo와 Bar처럼 여러 클래스가 있습니다. 모두 다음 인터페이스를 구현합니다. :

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

그리고 지금은 생성자의 인수로는 IEnumerable <IStartable>을 유도 할 수있는 컨테이너 클래스를 가지고 싶습니다. 이 클래스는, 차례로, 또한 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(); 
     } 
    } 
} 

그래서 내 질문은 : 나는 코드 생성하지 않고 코드를 작성 수동으로하지 않고 그것을 할 수 있는가? 즉, 나는 다음과 같이 somethig하고 싶습니다.

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 

제약 :

  • 없음 코드 생성 (즉, 컴파일시에 실제 텍스트 코드 없음)
  • 인터페이스만을 무효 방법, 또는 인수없이 있다

동기 부여 :

  • 저는 다양한 인터페이스의 많은 플러그인과 함께 꽤 큰 응용 프로그램을 가지고 있습니다. 각 인터페이스에 대한 "그룹 컨테이너"클래스를 수동으로 작성하면 클래스로 프로젝트가 "오버로드"됩니다.
  • 코드를 수동으로 작성하면 오류가 발생합니다.
  • IStartable 인터페이스에 대한 추가 또는 서명 업데이트로 인해 " 그룹 컨테이너 "클래스
  • 내가 여기 반사를 사용할 필요는 이해하지만 오히려 나를 위해 배선을 할 (성의 DynamicProxy 또는 RunSharp 같은) 강력한 프레임 워크를 사용하는 것

학습.

의견이 있으십니까?

+0

StartableGroup 클래스를 갖고 싶지 않으십니까? 그게 뭐가 잘못 됐어? – Noldorin

+0

나는 물을 수 있는가? 왜? 이것이 해결해야 할 문제는 무엇입니까? (이것은 대답에 영향을 줄 수 있습니다 ...). –

+0

@ Noldorin, @Marc Gravell, 동기 부여가 원래 질문에 추가되었습니다. –

답변

26

이 꽤 아니지만, 작동하는 것 같다 :

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

나에게 약간의 타이핑을 저장했다 :-) –

+0

나는 약간의 실수를 저기에 (컴파일하지 않을 것) 생각한다 : 대신 : 캐시 .Type = type.CreateType(); 다음과 같아야합니다. Type = type.CreateType(); –

+0

나는 제안 된 코드를 시도했는데, 당신의 대답은 인자를 가진 메소드를 다루지 않는 것 같다. ("인터페이스는 메소드를 무효로한다. 현재 인터페이스에 단일 인수가있는 메소드가 포함될 때 예외가 있습니다. –

2

List<T> 또는 다른 컬렉션 클래스를 서브 클래스 화하고 where 제네릭 형식 제약 조건을 사용하여 T 형식을 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(); 
     } 
    } 
} 

유형 매개 변수를 요구하는 일반 클래스가 아니길 원한다면 클래스를 선언 할 수도 있습니다.

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

샘플 사용 코드는 다음과 같이 보일 것입니다 :

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

을 사용하면 매우 쉽게 달성 할 수 있습니다. – DonkeyMaster

+0

@DonkeyMaster - 아니 정확한 질문에 대답하지 않지만 내가 그 질문을 올바르게 undestand 경우 가능한 대안을 생각합니다. 저의 게시물은 수작업으로 작성된 솔루션을 제공합니다. Marc Gravell의 우수한 샘플은 (런타임) 코드 생성 솔루션을 제공합니다. 나는 원래의 포스터가 "수동으로 코드를 작성하지 않고 코드 생성없이"해결책을 요구했다. –

+0

사실, @DonkeyMaster가 지적했듯이,이 질문에는 대답하지 않습니다. 코드가 명확하고 어쩌면 더 우아하지만 문제는 남아 있습니다. 런타임에 코드를 작성하거나 디자인 타임에 작성할 필요없이 어떻게 작성할 수 있습니까? –

0

당신은 C# 4.0 대기 및 동적 바인딩을 사용할 수 있습니다.

이것은 훌륭한 아이디어입니다. IDisposable을 여러 번 구현해야했습니다. 나는 많은 것들이 처형되기를 원할 때.그러나 염두에 두어야 할 한 가지는 오류를 처리하는 방법입니다. 로그를 남기고 다른 사람을 시작해야하는 등 ... 수업에 옵션이 필요합니다.

저는 DynamicProxy 및 여기에서 어떻게 사용할 수 있는지 잘 모르겠습니다.

+0

C# 4.0은 잠시 동안 여기에 없을 것입니다. 아직 CTP가 없습니다! – DonkeyMaster

0

"목록"클래스와 해당 메서드 인 "ForEach"를 사용할 수 있습니다.

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

이것이 내 마음에 가장 먼저 들었던 것입니다.하지만 그는 위의 "GroupGenerator"클래스의 구현을 요구하고 있습니다. –

0

올바르게 이해하면 "GroupGenerator"의 구현을 요청하는 것입니다.

CastleProxy에 대한 실제 경험이 없으면 GetMethods()를 사용하여 인터페이스에 나열된 초기 메서드를 가져온 다음 Reflection.Emit을 사용하여 해당 개체를 통해 열거하는 새 메서드를 사용하여 새 형식을 즉시 만들 수 있습니다 해당하는 각 메소드를 호출하십시오. 성능이 그렇게 나쁘지 않아야합니다.

4

그것은 반사 기반의 솔루션으로 깨끗한 인터페이스는 아니지만, 아주 간단하고 유연한 솔루션 그래서 같은 FORALL 방법을 만드는 것입니다 :

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

그래서 같이 호출 할 수 있습니다

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

Automapper 이것에 대한 좋은 해결책입니다. 그것은 인터페이스를 구현하는 인스턴스를 생성하기 위해 아래 LinFu에 의존하지만, 약간 유창한 API 아래에서 수화 및 믹스 인을 처리합니다. LinFu 작성자는 실제로는 CastleProxy보다 훨씬 가볍고 빠르다고 주장합니다.

+0

팁을 주셔서 감사합니다. 시간이 있으니 살펴 보겠습니다. –

관련 문제