简体   繁体   English

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

[英]How can I write a generic container class that implements a given interface in C#?

Context: .NET 3.5, VS2008. 上下文:.NET 3.5,VS2008。 I'm not sure about the title of this question, so feel free to comment about the title, too :-) 我不确定这个问题的标题,所以也可以自由评论标题:-)

Here's the scenario: I have several classes, say Foo and Bar, all of them implement the following interface: 这是场景:我有几个类,比如Foo和Bar,它们都实现了以下接口:

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

And now I'd like to have a container class, which gets an IEnumerable<IStartable> as an argument in its constructor. 现在我想要一个容器类,它在构造函数中获取一个IEnumerable <IStartable>作为参数。 This class, in turn, should also implement the IStartable interface: 反过来,这个类也应该实现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();
        }
    }
}

So my question is: how can I do it without manually writing the code, and without code generation? 所以我的问题是:如果不手动编写代码,并且没有代码生成,我怎么能这样做呢? In other words, I'd like to have somethig like the following. 换句话说,我想要像以下一样。

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

Constraints: 约束:

  • No code generation (that is, no real textual code at compile time) 没有代码生成(也就是说,编译时没有真正的文本代码)
  • The interface has only void methods, with or without arguments 接口只有void方法, 有或没有参数

Motivation: 动机:

  • I have a pretty large application, with a lot of plugins of various interfaces. 我有一个非常大的应用程序,有很多各种接口的插件。 Manually writing a "group container" class for each interface "overloads" the project with classes 手动为每个接口编写一个“组容器”类,用类“重载”项目
  • Manually writing the code is error prone 手动编写代码很容易出错
  • Any additions or signature updates to the IStartable interface will lead to (manual) changes in the "group container" class 对IStartable接口的任何添加或签名更新都将导致“组容器”类中的(手动)更改
  • Learning 学习

I understand that I have to use reflection here, but I'd rather use a robust framework (like Castle's DynamicProxy or RunSharp ) to do the wiring for me. 我明白,我要在这里使用反射,但我宁愿用一个强有力的框架(如城堡的DynamicProxyRunSharp )做配线我。

Any thoughts? 有什么想法吗?

This isn't pretty, but it seems to work: 这不是很好,但它似乎工作:

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

It's not as clean an interface as the reflection based solution, but a very simple and flexible solution is to create a ForAll method like so: 它不像基于反射的解决方案那样干净,但是一个非常简单灵活的解决方案是创建一个ForAll方法,如下所示:

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

And can be called like so: 并且可以像这样调用:

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

Automapper is a good solution to this. Automapper是一个很好的解决方案。 It relies on LinFu underneath to create an instance that implements an interface, but it takes care of some of the hydration, and mixins under a somewhat fluent api. 它依赖于下面的LinFu来创建一个实现接口的实例,但是它会处理一些水合作用,并在一些流畅的api下进行混合。 The LinFu author claims it is actually much more lightweight and faster than Castle 's Proxy . LinFu作者声称它实际上比CastleProxy更轻巧,更快。

You could subclass List<T> or some other collection class and use the where generic type constraint to limit the T type to be only IStartable classes. 您可以继承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();
        }
    }
}

You could also declare the class like this if you didn't want it to be a generic class requiring a type parameter. 如果你不希望它是一个需要类型参数的泛型类,你也可以像这样声明这个类。

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

Your sample usage code would then look something like this: 您的示例使用代码将如下所示:

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

You could wait for C# 4.0 and use dynamic binding. 您可以等待C#4.0并使用动态绑定。

This is a great idea - I've had to implement this for IDisposable on several occasions; 这是个好主意 - 我必须多次为IDisposable实现这个功能; when I want many things to be disposed. 当我想要处理许多事情的时候。 One thing to keep in mind though is how errors will be handled. 但要记住的一件事是如何处理错误。 Should it log and keep starting others, etc... You'd need some options to give the class. 它应该记录并继续启动其他人等等......你需要一些选择来给这个课程。

I'm not familiar with DynamicProxy and how it could be used here. 我不熟悉DynamicProxy以及它如何在这里使用。

You can use the "List" class and their method "ForEach". 您可以使用“List”类及其方法“ForEach”。

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

If I understand correctly, you are asking for an implementation of the "GroupGenerator". 如果我理解正确,您要求实施“GroupGenerator”。

Without any real experience with CastleProxy my recommendation would be to use GetMethods() to get the initial methods listed in the interface and then create a new type on the fly using Reflection.Emit with the new methods that enumerate through the objects and call each corresponding method. 没有任何CastleProxy的实际经验,我的建议是使用GetMethods()来获取界面中列出的初始方法,然后使用Reflection.Emit动态创建一个新类型,新方法枚举对象并调用每个对应的方法。 The performance shouldn't be too bad. 表现应该不会太糟糕。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM