3

だから私は、、そして他の多くのクラスで実装されていると呼ばれるインターフェースを持っていますIWorkItemWorkAWorkB

public interface IWorker<T> where T : IWorkItem
{
    void Process(T item);
}

IWorker<T>インターフェイスはWorkerAIWorker<WorkA>)、WorkerBIWorker<WorkB>)および他の多くのクラスに実装されています。

public static void ProcessWorkItem(IWorkItem item)
{
    (/* find the right worker */).Process(item);
}

今私の問題は:与えられたものを処理できるワーカーオブジェクトをどのように見つけるのIWorkItemですか?

私の最初の試みは次のように見えますが、ジェネリック型の引数には問題があります。

public static class WorkerRepository
{
    private static Dictionary<Type, IWorker<???>> RegisteredWorkers =
        new Dictionary<Type, IWorker<???>>();

    public static void RegisterWorker(IWorker<???> worker)
    {
        var handled = from iface in worker.GetType().GetInterfaces()
                      where iface.IsGenericType
                      where iface.GetGenericTypeDefinition() == typeof(IWorker<>)
                      select iface.GetGenericArguments()[0];
        foreach (var type in handled)
            if (!RegisteredWorkers.ContainsKey(type))
                RegisteredWorkers[type] = worker;
    }

    public static void ProcessWorkItem(IWorkItem item)
    {
        RegisteredWorkers[item.getType()].Process(item);
    }
}

だから私はDictionary労働者を含むを持っています。ここではどの型の引数が必要ですか?Javaでは使用できます? extends IWorkItemが、C#でも使用できますか?次にありますRegisterWorker。おそらく、メソッド全体のジェネリック型引数を提案するでしょうRegisterWorker<T>(IWorker<T> worker)。ただし、ワーカーを動的にロード、インスタンス化、および登録したいので、これも機能しません。

これは正しいアプローチでさえありますか、それともこれを達成するためのより良い方法がありますか?

4

2 に答える 2

2

私はいくつかの変更を加えましたが、(sを使用する代わりに)物事を一般的に保つことができる解決策を得ましたobject。あなたが気にかけているかどうかはわかりませんが、答えとしてそれを追加してあなたに決めさせることを考えました。

また、実際に機能するかどうかを確認するためのテストを作成しました。コピーして貼り付けることができるはずです。

[TestFixture]
public class WorkerThing
{
    [Test]
    public void RegisterAndRetrieveWorkers()
    {
        var repo = new WorkerRepository();
        repo.RegisterWorker(new WorkerA());
        var workerA = repo.RetrieveWorkerForWorkItem(new WorkItemA());
        Assert.IsTrue(workerA is WorkerA);

        repo.RegisterWorker(new WorkerB());
        var workerB = repo.RetrieveWorkerForWorkItem(new WorkItemB());
        Assert.IsTrue(workerB is WorkerB);
    }
}

WorkerRepositoryクラス。

public class WorkerRepository
{
    private readonly Dictionary<Type, IWorker<IWorkItem>> _registeredWorkers =
        new Dictionary<Type, IWorker<IWorkItem>>();

    public void RegisterWorker(IWorker<IWorkItem> worker)
    {
        var type = (from iface in worker.GetType().GetInterfaces()
                      where iface.IsGenericType
                      where iface.GetGenericTypeDefinition() == typeof(IWorker<>)
                      select iface.GetGenericArguments()[0]).First();

        if (!_registeredWorkers.ContainsKey(type))
        {
            _registeredWorkers[type] = worker;
        }
    }

    // You don't need this method, just added it to check if I indeed retrieved the correct type
    //
    public IWorker<IWorkItem> RetrieveWorkerForWorkItem(IWorkItem item)
    {
        var type = item.GetType();
        var registeredWorker = _registeredWorkers[type];
        return registeredWorker;
    }

    public void ProcessWorkItem(IWorkItem item)
    {
        var type = item.GetType();
        var registeredWorker = _registeredWorkers[type];
        registeredWorker.Process(item);
    }
}

作業項目のインターフェースとクラス。

public interface IWorkItem
{
}

public class WorkItemA : IWorkItem
{
}

public class WorkItemB : IWorkItem
{
}

そして、ここでoutキーワードを追加して、インターフェースでの共分散入力を可能にしました。そうすれば、に変換できWorkerAますIWorker<IWorkItem>。(単体テストの例のように)

public interface IWorker<out T> where T : IWorkItem
{
    void Process(IWorkItem workItem);
}

public class WorkerA : IWorker<WorkItemA>
{
    public void Process(IWorkItem item)
    {
    }
}

public class WorkerB : IWorker<WorkItemB>
{
    public void Process(IWorkItem item)
    {
    }
}

object辞書はありません。反射なし。この例がお役に立てば幸いです。

乾杯(そしてクールな質問のためにthx、それは私をしばらく忙しくさせました:))

于 2013-03-16T22:37:08.340 に答える
1

次のようなものが必要なようです。

private static Dictionary<Type, object> RegisteredWorkers = new Dictionary<Type, object>();

public static void RegisterWorker(object worker)
{
    var handled = from iface in worker.GetType().GetInterfaces()
                  where iface.IsGenericType
                  where iface.GetGenericTypeDefinition() == typeof(Worker<>)
                  select iface.GetGenericArguments()[0];
    foreach (var type in handled)
        if (!RegisteredWorkers.ContainsKey(type))
            RegisteredWorkers[type] = worker;
}

public static void ProcessWorkItem(WorkItem item)
{
    object handler = RegisteredWorkers[item.getType()];
    Type workerType = typeof(Worker<>).MakeGenericType(item.GetType());
    MethodInfo processMethod = workerType.GetMethod("Process");
    processMethod.Invoke(handler, new object[] { item });
}

Action<IWorkItem>ハンドラーを登録するときにハンドラーを生成できるたびにリフレクションを介してハンドラーを呼び出したくない場合は、次のようにします。

public void RegisterHandler(object handler)
{
    var handled = from iface in handler.GetType().GetInterfaces()
                  where iface.IsGenericType
                  where iface.GetGenericTypeDefinition() == typeof(IWorker<>)
                  select iface.GetGenericArguments()[0];

    foreach (var type in handled)
    {
        if (!RegisteredWorkers.ContainsKey(type))
        {
            Action<IWorkItem> handleAction = HandlerAction(type, handler);
            RegisteredWorkers[type] = handleAction;
        }
    }   
}

public void Process(IWorkItem item)
{
    Action<IWorkItem> handleAction = RegisteredWorkers[item.GetType()];
    handleAction(item);
}

private static Action<IWorkItem> HandlerAction(Type itemType, object handler)
{
    var paramExpr = Expression.Parameter(typeof(IWorkItem));
    var castExpr = Expression.Convert(paramExpr, itemType);
    MethodInfo processMethod = typeof(IWorker<>).MakeGenericType(itemType).GetMethod("Process");
    var invokeExpr = Expression.Call(Expression.Constant(handler), processMethod, castExpr);

    var lambda = Expression.Lambda<Action<IWorkItem>>(invokeExpr, paramExpr);
    return lambda.Compile();
}
于 2013-03-16T20:57:26.247 に答える