Open Generics and Autofac 2.3
Autofac 2.3 hit the shelves more than three months ago but as it coincided pretty closely with the birth of our daughter Vera I didn’t write much about it at the time.
One improvement worth mentioning is the much-expanded support for open generic components, contributed by Rikard Pavelic. I answered a recent question on the Autofac mailing list with “it can’t be done” only to find later that the scenario being discussed was already supported. Though fairly unusual, it is an interesting feature so I’ll describe it below, but first the less exotic cases.
Generic Parameter Constraints
C# allows the arguments of a generic type to apply constraints that parameters must match. For example, we might constrain an event handler implementation to a type of event:
interface IEventHandler {
void Handle(TEvent @event);
}
In this small example we have an interface that helps us dispatch event objects to handlers. Typically there would be one or more handlers for each event type, most of them specific to the kind of event. We also have a marker interface for events that should trigger some kind of auditing, and a handler that will make this happen.
interface IAuditableEvent { }
class AuditingEventHandler : IEventHandler
where TEvent : IAuditableEvent {
void Handle(TEvent @event) {
// Write an audit record…
}
}
Note that the handler uses a generic type constraint to select the kind of events it is interested in. Some events will be auditable, others not:
class ItemAddedToCartEvent { }
class CheckoutCompletedEvent : IAuditableEvent { }
The AuditingEventHandler
will be registered as an open generic type.
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(AuditingEventHandler<>))
.As(typeof(IEventHandler<>));
var container = builder.Build();
Assuming that we’ve registered all of the different event handlers for the above two events, what should happen when we resolve all of the handlers for ItemAddedToCartEvent
?
var handlers = container.Resolve>>();
The AuditingEventHandler
component is registered to provide the IEventHandler
service, but the generic type constraint doesn’t match. In earlier versions of Autofac, the above line would have raised an exception.
With support for open generic parameter constraints, Autofac will now instantiate only the types whose constraints match, so the handlers
collection above would be returned successfully without any attempt to instantiate the AuditingEventHandler
.
If instead an auditable event is to be handled, then the auditing handler would be included:
// Succeeds and returns an AuditingEventHandler
var handlers = container.Resolve>>();
Constraint Types
Autofac 2.3 handles all of the generic parameter constraints you’re likely to encounter, including:
-
new()
-
class
andstruct
-
Base type and implemented interfaces
Parameter Binding Solver
Open generic types are more subtle and complicated to support than they sometimes appear. Take this contrived but not unimaginable example:
class MonoDictionary : IDictionary { }
The generic parameter T
appears twice in the parameter list of the implemented IDictionary
! That means if we register MonoDictionary
:
builder.RegisterGeneric(typeof(MonoDictionary<>))
.As(typeof(IDictionary<,>));
Then we should be able to resolve IDictionary
but not IDictionary
. Tricky – but as of version 2.3, Autofac can handle it.
The example we discussed on the Autofac mailing list was something like:
interface IProducer { }
class NullableProducer : IProducer
where T : struct { }
The twist here is that the generic component NullableProducer
does not parameterise IProducer
directly with T
, but rather Nullable
(abbreviated to T?
).
This means that if we resolve IProducer
then NullableProducer
should be activated, while resolving IProducer
should ignore NullableProducer
altogether.
Autofac 2.3 also supports this case, with some terse but surprisingly succinct code. Hopefully this will open up some new, richer generic component possibilities.