Skip to main content

Open generics

A generic service like IRepository<T> usually has one generic implementation like Repository<T>. You do not want to register it once per entity type. Register the open generic once, and Awaiten closes it for each type your app actually uses.

Register the open type​

Use the typeof form with an unbound generic type.

[Container]
[Transient(typeof(Repository<>), typeof(IRepository<>))]
public static partial class CoffeeShop;

Resolve a closed type​

Ask for any closed form and Awaiten builds the matching implementation.

IRepository<Order> orders = shop.Resolve<IRepository<Order>>();
IRepository<Customer> customers = shop.Resolve<IRepository<Customer>>();

Each type argument gets its own closed implementation, and each keeps the declared lifetime. Awaiten works out which closed forms you need at compile time from what the graph actually asks for.

Self-registration​

You can register an open generic under itself, without a separate service type.

[Transient(typeof(Handler<>))]
// resolve: shop.Resolve<Handler<OrderPlaced>>()

Collections and keys​

Open registrations join collections. A request for IEnumerable<IHandler<OrderPlaced>> unions every open registration closed at OrderPlaced, alongside any explicit closed registrations. A Key on the open registration flows to every closed implementation, so you select with [FromKey] as usual.

[Transient(typeof(FastRepository<>), typeof(IRepository<>), Key = "fast")]

Decorate or compose the closings​

Decorators and composites take the same typeof form, so one behavior wraps every closing of an open generic service. This is the pipeline-behavior pattern:

[Transient(typeof(Handler<>), typeof(IHandler<>))]
[Decorate(typeof(LoggingBehavior<>), typeof(IHandler<>))]
[Composite(typeof(CompositeHandler<>), typeof(IHandler<>))]

See Decorators and Composites.

Note: the implementation and service must have the same arity (AWT125), and a closed type argument must satisfy the implementation's constraints (AWT126).

Where to go next​