2016-04-05 15 views
6

私はAutofacで次のようなことをする方法がないと思っています。開いているジェネリック型の列挙可能なコレクションを挿入する必要がありますか?さまざまなHandle型は依存関係があります。そうでなければ、動的にそれらを構築します。autofac開いているジェネリックタイプのすべてのタイプを解決しますか?

class EventOne : IEvent {...} 
    class EventTwo : IEvent {...} 
    class EventThree : IEvent {...} 
    interface IHandleEvent<T> where T : IEvent {...} 
    class HandleEventOne : IHandleEvent<EventOne> {...} 
    class HandleEventTwo : IHandleEvent<EventTwo> {...} 
    class HandleEventThree : IHandleEvent<EventThree> {...} 

    builder.RegisterAssemblyTypes(myAssembies).AsClosedTypesOf(typeof(IHandleEvent<>)); 
    builder.RegisterType<AService>().As<IAService>(); 


    class AService : IAService 
    { 
     public AService(IEnumerable<IHandleEvent<IEvent>> handles) 
     {...} 
    } 
+0

'のIEnumerable >'丁度1コンクリートの型に解決する必要があるでしょう。動的にビルドできるタイプ(Builderパターンを参照)があれば、それを定義し、 'IEnumerable >'の登録としてAutoFacで登録することができます。また、DIコンテナをビルドするために追加の情報を取得する必要がある場合は、そのタイプのDIコンテナを挿入することもできます。つまり、IEnumerable >インスタンスを解決するには、ビルダーまたは工場パターンが必要です。 – Igor

+1

ありがとうございます。私はデリゲート工場のautofac docを見ました。この場合、コンテナは工場に注入され、さまざまな種類のIHandleEvent <>が解決されます。 – Suedeuno

+0

'IHandleEvent 'を 'IHandleEvent 'に書き換えようとしてください。 Autofacは分散のためのサポートをいくつか持っていて、そうするときに自動的に登録を受け取るかもしれません。しかし、私はいつもAutofacが共分散または反共をサポートしているかどうか忘れているので、試してみる必要があります。 – Steven

答えて

7

としては、コメントで説明し、何が欲しいのはC#で正当な理由で達成することは不可能です。 IHandleEvent<EventOne>IHandleEvent<IEvent>にキャストできた場合は、EventTwoも渡すことができ、実行時には失敗します。

したがって、互換性のあるすべてのイベントハンドラを取得して呼び出すことができるメディエータの抽象化が必要です。このような仲介者はしばしばIEventPublisherと呼ばれ、次のようになります。

public interface IEventPublisher { 
    void Publish(IEvent e); 
} 

あなたは今、コンテナ特定の実装を作成することができます。例えば、これはなりAutofacために、次のように:

public class AutofacEventPublisher : IEventPublisher { 
    private readonly IComponentContext container; 

    public AutofacBusinessRuleValidator(IComponentContext container) { 
     this.container = container; 
    } 

    public void Publish(IEvent e) { 
     foreach (dynamic handler in this.GetHandlers(e.GetType())) { 
      handler.Handle((dynamic)e); 
     } 
    } 

    private IEnumerable GetHandlers(Type eventType) => 
     (IEnumerable)this.container.Resolve(
      typeof(IEnumerable<>).MakeGenericType(
       typeof(IHandleEvent<>).MakeGenericType(eventType))); 
} 

消費者は今、この新しい抽象化に依存することができます

class AService : IAService 
{ 
    public AService(IEventPublisher publisher) {...} 
} 
+0

これを行うには、autofacでタイプを解決するためのロケータパターンを使用するしかありません。私はautofacが自然に注射を処理できるように、型を閉じて一般的なメソッドを使うことがあります。 – Suedeuno

+0

@Suedeuno:あなたは間違っています。私が示しているのは、 'AutofacEventPublisher'があなたの[Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot/)の一部である限り、サービスロケータ*ではありません。サービスロケータは[機能ではなく役割です](http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/)です。しかし、仲介抽象化が唯一の方法であるという点であなたは正しいです。オートファックだけでなく、コンテナやコンテナがなくても。それは、あなたが使用する抽象概念のためです。 – Steven

+0

したがって、AutofacEventPublisherは、起動したモジュール(例えば、あなたが参照したブログ投稿に記載されているようなmvc UIアセ​​ンブリ)の外側に存在していても問題ありませんか? – Suedeuno

0

IHandleEvent<T>あなたはout修飾子を追加することによって、それを追加することができます共変ではありませんので、あなたはIHandleEvent<IEvent>IHandleEvent<EventThree>をキャストすることはできません。

public interface IHandleEvent<out TEvent> 
    where TEvent : IEvent 
{ } 

残念ながらAutofacは共変タイプだけ反変タイプをサポートしていません。 ところで、カスタムのIRegistrationSource実装を作成して、要求された動作をさせることができます。このような何か:

public class CovariantHandleEventRegistrationSource : IRegistrationSource 
{ 
    public bool IsAdapterForIndividualComponents 
    { 
    get 
    { 
     return false; 
    } 
    } 

    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, 
           Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
    { 
    IServiceWithType typedService = service as IServiceWithType; 
    if (typedService == null) 
    { 
     yield break; 
    } 

    if (typedService.ServiceType.IsGenericType && typedService.ServiceType.GetGenericTypeDefinition() == typeof(IHandleEvent<>)) 
    { 
     IEnumerable<IComponentRegistration> eventRegistrations = registrationAccessor(new TypedService(typeof(IEvent))); 

     foreach (IComponentRegistration eventRegistration in eventRegistrations) 
     { 
     Type handleEventType = typeof(IHandleEvent<>).MakeGenericType(eventRegistration.Activator.LimitType); 
     IComponentRegistration handleEventRegistration = RegistrationBuilder.ForDelegate((c, p) => c.Resolve(handleEventType, p)) 
              .As(service) 
              .CreateRegistration(); 

     yield return handleEventRegistration; 
     } 
    } 
    } 
} 

このIRegistrationSourceでは、あなたがこれを持つことができます。

ContainerBuilder builder = new ContainerBuilder(); 
builder.RegisterType<EventOne>().As<IEvent>(); 
builder.RegisterType<EventTwo>().As<IEvent>(); 
builder.RegisterType<EventThree>().As<IEvent>(); 
builder.RegisterAssemblyTypes(typeof(Program).Assembly) 
     .AsClosedTypesOf(typeof(IHandleEvent<>)); 

builder.RegisterSource(new CovariantHandleEventRegistrationSource()); 

IContainer container = builder.Build(); 

var x = container.Resolve<IEnumerable<IHandleEvent<IEvent>>>(); 
+1

Contravarianceは実際にOPが必要とするものです。共分散ではありません。 'IHandleEvent 'は常に入力引数として 'T'を受け入れます(ハンドラはイベントを返すことはありません)。言い換えれば: 'IHandleEvent '。 – Steven

+0

@スティーブンあなたは正しいです。インターフェースの名前に基づいて、 'IHandleEvent 'は 'Handle(T e)'メソッドを持つようになるので、反変が必要です。この場合、 'Autofac'がなくても、' IHandleEvent 'を構築するC#の方法はなく、' AService'は汎用であり、 'IHandleEvent 'に依存するはずです。 –

+1

はい、そうです。 'IHandleEvent 'のコレクションを 'IHandleEvent 'にキャストできるようにするには、共分散が必要です(つまり、 'out'キーワードを適用する)が、イベントハンドラは明らかに反変する必要があります。だからOPが望んでいることは不可能です。 – Steven

関連する問題