代码之家  ›  专栏  ›  技术社区  ›  Ron Klein Noa Kuperberg

如何编写在C#中实现给定接口的通用容器类?

  •  24
  • Ron Klein Noa Kuperberg  · 技术社区  · 16 年前

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

    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
    

    限制条件:

    • 没有代码生成(也就是说,编译时没有真正的文本代码)
    • 接口只有void方法, 有无争论

    动机:

    • 对IStartable接口的任何添加或签名更新都将导致(手动)更改“组容器”类
    • 学习

    我知道我必须在这里使用反射,但我宁愿使用健壮的框架(比如Castle的框架) DynamicProxy RunSharp )帮我接线。

    有什么想法吗?

    7 回复  |  直到 16 年前
        1
  •  28
  •   Marc Gravell    16 年前

    这并不漂亮,但似乎有效:

    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
            }
        }
    }
    
        2
  •  4
  •   ICR    16 年前

    它不像基于反射的解决方案那样干净,但一个非常简单和灵活的解决方案是创建一个ForAll方法,如下所示:

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

    可以这样称呼:

    arr.ForAll(x => x.Start());
    
        3
  •  3
  •   Scott Stanchfield    16 年前

    您可以创建子类 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
    
        4
  •  2
  •   Maslow    15 年前

    Automapper 这是一个很好的解决方案。它依赖于 LinFu 作者声称它实际上比以前轻得多,速度也快得多 Castle Proxy

        5
  •  0
  •   TheSoftwareJedi jac    16 年前

    这是一个好主意——我已经在好几个场合为IDisposable实现了它;当我想处理很多事情的时候。但是要记住的一件事是如何处理错误。是否应该记录并继续启动其他程序等。。。你需要一些选项来给这个班上课。

    我不熟悉DynamicProxy以及它在这里的使用方法。

        6
  •  0
  •   TcKs    16 年前

    您可以使用“List”类及其方法“ForEach”。

    var startables = new List<IStartable>( array_of_startables );
    startables.ForEach( t => t.Start(); }
    
        7
  •  0
  •   Robert Venables    16 年前

    如果我理解正确,您要求实现“GroupGenerator”。