簡體   English   中英

如何編寫在C#中實現給定接口的通用容器類?

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

上下文:.NET 3.5,VS2008。 我不確定這個問題的標題,所以也可以自由評論標題:-)

這是場景:我有幾個類,比如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();
        }
    }
}

所以我的問題是:如果不手動編寫代碼,並且沒有代碼生成,我怎么能這樣做呢? 換句話說,我想要像以下一樣。

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接口的任何添加或簽名更新都將導致“組容器”類中的(手動)更改
  • 學習

我明白,我要在這里使用反射,但我寧願用一個強有力的框架(如城堡的DynamicProxyRunSharp )做配線我。

有什么想法嗎?

這不是很好,但它似乎工作:

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

它不像基於反射的解決方案那樣干凈,但是一個非常簡單靈活的解決方案是創建一個ForAll方法,如下所示:

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

並且可以像這樣調用:

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

Automapper是一個很好的解決方案。 它依賴於下面的LinFu來創建一個實現接口的實例,但是它會處理一些水合作用,並在一些流暢的api下進行混合。 LinFu作者聲稱它實際上比CastleProxy更輕巧,更快。

您可以繼承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

您可以等待C#4.0並使用動態綁定。

這是個好主意 - 我必須多次為IDisposable實現這個功能; 當我想要處理許多事情的時候。 但要記住的一件事是如何處理錯誤。 它應該記錄並繼續啟動其他人等等......你需要一些選擇來給這個課程。

我不熟悉DynamicProxy以及它如何在這里使用。

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

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

如果我理解正確,您要求實施“GroupGenerator”。

沒有任何CastleProxy的實際經驗,我的建議是使用GetMethods()來獲取界面中列出的初始方法,然后使用Reflection.Emit動態創建一個新類型,新方法枚舉對象並調用每個對應的方法。 表現應該不會太糟糕。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM