18

まず、あいまいな質問タイトルで申し訳ありません。より正確なものを思いつくことができませんでした。

これらのタイプを考えると:

                                                     { TCommand : ICommand }
       «interface»                   «interface»    /
      +-----------+         +----------------------/----+
      | ICommand  |         | ICommandHandler<TCommand> |
      +-----------+         +---------------------------+
            ^               | Handle(command: TCommand) |
            |               +---------------------------+
            |                              ^
            |                              |
      +------------+            +-------------------+
      | FooCommand |            | FooCommandHandler |
      +------------+            +-------------------+
            ^
            |
   +-------------------+
   | SpecialFooCommand |
   +-------------------+

Dispatch任意のコマンドを受け入れて適切な .xml ファイルに送信するメソッドを作成したいと思いますICommandHandler<>。DI コンテナー (Autofac) を使用すると、コマンドの型からコマンド ハンドラーへのマッピングが大幅に簡素化される可能性があると考えました。

void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
{
    var handler = autofacContainer.Resolve<ICommandHandler<TCommand>>();
    handler.Handle(command);
}

DI コンテナーが上記のすべての型を認識しているとします。今私は呼んでいます:

Dispatch(new SpecialFooCommand(…));

実際には、これは Autofac が をスローする結果になりComponentNotRegisteredExceptionますICommandHandler<SpecialFooCommand>

SpecialFooCommandただし、理想的には、使用可能な最も近いコマンド ハンドラーによってa が処理されるようにしたいと考えています。FooCommandHandler上記の例のa によって。

おそらくカスタム登録ソースを使用して、Autofacをその目的に合わせてカスタマイズできますか?


PS: (次の例のように) 共分散/反分散が邪魔になるという根本的な問題がある可能性があること、および唯一の解決策はジェネリックをまったく使用しないことである可能性があることを理解しています...しかし、私はそうします可能であれば、ジェネリック型に固執したい。

ICommandHandler<FooCommand> fooHandler = new FooCommandHandler(…);
ICommandHandler<ICommand> handler = fooHandler;
//                                ^
//              doesn't work, types are incompatible
4

3 に答える 3

17

あなたが質問を投稿してからAutofacを拡張したので、本当に公正な答えではありません... :)

inダニエルの答えに従って、修飾子をのTCommandパラメータに追加する必要がありますICommandHandler

interface ICommandHandler<in TCommand>
{
    void Handle(TCommand command);
}

Autofac 2.5.2 には、IRegistrationSource反変Resolve()操作を有効にする が含まれるようになりました。

using Autofac.Features.Variance;

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());

このソースを登録すると、単一のinパラメーターを持つ汎用インターフェースで表されるサービスが、バリアント実装を考慮して検索されます。

builder.RegisterType<FooCommandHandler>()
   .As<ICommandHandler<FooCommand>>();

var container = builder.Build();
container.Resolve<ICommandHandler<FooCommand>>();
container.Resolve<ICommandHandler<SpecialFooCommand>>();

への呼び出しは両方とも、Resolve()を正常に取得しますFooCommandHandler

最新の Autofac パッケージにアップグレードできない場合は、 http:ContravariantRegistrationSource //code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.csから入手してください- コンパイルする必要があります最近の Autofac ビルドに対して。

于 2011-08-12T10:16:18.320 に答える
5

あなたが求めていることは、独自のコーディングなしでは不可能です。基本的に、あなたは次のことを求めています: 解決しようとした型が見つからない場合は、それに変換できる別の型を返します。たとえば、 にIEnumerable登録されている型を解決しようとする場合ICollection。これはサポートされていません。1 つの簡単な解決策は次のとおりです。FooCommandHandlerのハンドラとして登録しICommandHandler<SpecialFooCommand>ます。これが機能するには、ICommandHandler が反変である必要があります。

interface ICommand { }

class FooCommand : ICommand { }

class SpecialFooCommand : FooCommand { }

interface ICommandHandler<in T> where T : ICommand
{
    void Handle(T command);
}

class FooCommandHandler : ICommandHandler<FooCommand>
{
    public void Handle(FooCommand command)
    {
        // ...
    }
}

var builder = new ContainerBuilder();
builder.RegisterType<FooCommandHandler>()
       .As<ICommandHandler<SpecialFooCommand>>()
       .As<ICommandHandler<FooCommand>>();
var container = builder.Build();
var fooCommand = new FooCommand();
var specialCommand = new SpecialFooCommand();
container.Resolve<ICommandHandler<FooCommand>>().Handle(fooCommand);
container.Resolve<ICommandHandler<FooCommand>>().Handle(specialCommand);
container.Resolve<ICommandHandler<SpecialFooCommand>>().Handle(specialCommand);

ところで:コンテナを使用している方法では、Service locator anti-patternを適用します。これは避けるべきです。

于 2011-08-10T13:07:53.680 に答える
3

C# 4.0 バリアンス サポートなしでも機能する別のアプローチを追加したいと思います。

コマンドを基本型として実行できる特別なデコレータ/ラッパーを作成できます。

public class VarianceHandler<TSubCommand, TBaseCommand> 
    : ICommandHandler<TSubCommand>
    where TSubCommand : TBaseCommand
{
    private readonly ICommandHandler<TBaseCommand> handler;

    public VarianceHandler(ICommandHandler<TBaseCommand> handler)
    {
        this.handler = handler;
    }

    public void Handle(TSubCommand command)
    {
        this.handler.Handle(command);
    }
}

これを行うと、次のコード行によりSpecialFooCommand、その基本型として処理できるようになります。

builder.Register<FooCommandHandler>()
    .As<ICommandHandler<FooCommand>>();

builder.Register<VarianceHandler<SpecialFooCommand, FooCommand>>()
    .As<ICommandHandler<SpecialFooCommand>>();

このような使用はVarianceHandler、ほとんどの DI コンテナーで機能することに注意してください。

于 2011-08-14T12:47:14.700 に答える