2016-04-05 21 views
6

Tôi đoán không có cách nào để làm một cái gì đó như sau với Autofac, để ctor tiêm một bộ sưu tập đầy đủ các loại generic mở? Các loại Xử lý khác nhau có các phụ thuộc, nếu không tôi sẽ tự động xây dựng những thứ đó.autofac Giải quyết tất cả các loại kiểu generic mở?

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 > 'sẽ cần phải được giải quyết chính xác 1 loại bê tông . Nếu bạn có một loại có thể tự động xây dựng chính nó (xem mẫu Builder), bạn có thể xác định và đăng ký nó với AutoFac như đăng ký cho 'IEnumerable > '. Bạn cũng có thể tiêm container DI trong loại đó nếu bạn cần lấy thông tin bổ sung để xây dựng nó. Tóm lại, tôi nghĩ bạn cần một người xây dựng hoặc mẫu nhà máy ở đây để giải quyết cá thể 'IEnumerable >' của bạn. – Igor

+1

Cảm ơn. Tôi đã xem xét các nhà máy ủy quyền autofac doc. Có vẻ như trong trường hợp này, thùng chứa sẽ được tiêm vào nhà máy để giải quyết các loại IHandleEvent khác nhau <>. – Suedeuno

+0

Hãy thử chaning 'IHandleEvent 'của bạn' IHandleEvent '. Autofac có một số hỗ trợ cho phương sai và có thể tự động đăng ký khi bạn làm điều đó. Nhưng tôi luôn luôn quên liệu Autofac có hỗ trợ hiệp phương sai hay contravariance, vì vậy bạn sẽ phải thử nó. – Steven

Trả lời

7

Như đã giải thích trong các ý kiến, những gì bạn muốn là không thể đạt được trong C# và với lý do chính đáng. Nếu bạn có thể truyền một số IHandleEvent<EventOne> đến một số IHandleEvent<IEvent>, điều này cũng sẽ cho phép một số EventTwo được chuyển vào, điều này sẽ không thành công khi chạy.

Vì vậy, những gì bạn cần là một trừu tượng hòa giải cho phép nhận tất cả các trình xử lý sự kiện tương thích và gọi cho họ. Hòa giải viên như vậy thường được gọi là IEventPublisher và có thể trông giống như sau:

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

Bây giờ bạn có thể tạo triển khai cụ thể cho vùng chứa. Ví dụ, đối với Autofac này sẽ trông như sau: Bây giờ

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

tiêu dùng có thể phụ thuộc vào sự trừu tượng mới này:

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

Vì vậy, cách duy nhất để làm điều này với autofac là với mô hình định vị để giải quyết các loại. Tôi có thể chỉ cần đóng loại và sử dụng các phương pháp chung để autofac tự nhiên có thể xử lý tiêm. – Suedeuno

+0

@Suedeuno: Bạn không chính xác. Những gì tôi đang hiển thị là * not * a * Service Locator *, miễn là 'AutofacEventPublisher' là một phần của [Root Root] của bạn (http://blog.ploeh.dk/2011/07/28/CompositionRoot/), vì Service Locator là về [vai trò chứ không phải cơ chế] (http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/). Nhưng bạn có quyền trong đó có một trừu tượng trung gian là cách duy nhất; không chỉ trong Autofac, nhưng với bất kỳ container hoặc thậm chí không có một container. Đó là vì những trừu tượng bạn sử dụng. – Steven

+0

Vì vậy, nó không quan trọng là AutofacEventPublisher cư trú bên ngoài mô-đun khởi động (mvc ui lắp ráp ví dụ như được ghi trong bài đăng blog bạn tham chiếu)? – Suedeuno

0

Bạn sẽ không thể bỏ IHandleEvent<EventThree>-IHandleEvent<IEvent>IHandleEvent<T> không phải là một hiệp biến bạn có thể thêm nó bằng cách thêm các modifier out.

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

Thật không may Autofac không hỗ trợ loại hiệp biến nhưng chỉ loại contravariant. Bằng cách này, bạn có thể tạo triển khai IRegistrationSource tùy chỉnh để có hành vi được yêu cầu. Một cái gì đó như thế này:

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

Với IRegistrationSource này bạn có thể có điều này:

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 thực sự là những gì OP cần; không hiệp phương sai. Một 'IHandleEvent ' sẽ luôn chấp nhận một 'T' làm đối số đầu vào (một trình xử lý không thể trả về một sự kiện). Nói cách khác: 'IHandleEvent '. – Steven

+0

@Steven bạn nói đúng. Dựa trên tên của giao diện, có vẻ như 'IHandleEvent ' sẽ có phương thức 'Xử lý (T e)', do đó cần có sự đối xứng.Trong trường hợp này, ngay cả khi không có 'Autofac' thì không có cách nào C# để xây dựng một' IHandleEvent ',' AService' phải là chung chung và có một sự phụ thuộc vào 'IHandleEvent '. –

+1

Có, bạn đã đúng. Để có thể truyền một tập hợp 'IHandleEvent ' sang 'IHandleEvent ', bạn cần hiệp phương sai (nghĩa là áp dụng từ khóa 'out'), nhưng trình xử lý sự kiện rõ ràng là cần phải contravariant (tức là sử dụng từ khoá' in'). Vì vậy, những gì OP muốn là không thể. – Steven

Các vấn đề liên quan