Search code examples
c#dependency-injectionexpression-treessimple-injector

Simple Injector Inject IEnumerable<Func<T>>


How Can I inject IEnumerable<Func<T>> using Simple Injector?

Just to add some context, I'm trying to create all EventHandlers that knows how to handle one specific Event. So here is my Container registration:

container.RegisterCollection(typeof(IHandleDomainEvent<>),
    AppDomain.CurrentDomain.GetAssemblies());

And here Are two classes that implement the IHandleEvent<T> interface for the same Event:

public class Reservation : IHandleDomainEvent<OrderConfirmed>{}
public class Order: IHandleDomainEvent<OrderConfirmed>{}

So when I call the Simple Injector:

var handlers = _container.GetAllInstances<Func<IHandleDomainEvent<OrderConfirmed>>>();

I would like to Receive IEnumerable<Func<IHandleDomainEvent<OrderConfirmed>>>

Just to clarify, I know that if I call:

var handlers = _container.GetAllInstances<IHandleDomainEvent<OrderConfirmed>>();

I would get an IEnumerable<IHandleDomainEvent<OrderConfirmed>>.


For interfaces that have only one implementation, registering using:

container.Register(typeof(IHandleDomainEvent<>),
    AppDomain.CurrentDomain.GetAssemblies(), Lifestyle.Scoped);

And adding the following ResolveUnregisteredType to the end of the registration:

container.ResolveUnregisteredType += (o, args) => ResolveFuncOfT(o, args, container);

// Function
private static void ResolveFuncOfT(object s, UnregisteredTypeEventArgs e, Container container)
{
    var type = e.UnregisteredServiceType;
    if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(Func<>)) return;
    Type serviceType = type.GetGenericArguments().First();
    InstanceProducer producer = container.GetRegistration(serviceType, true);
    Type funcType = typeof(Func<>).MakeGenericType(serviceType);
    var factoryDelegate = Expression.Lambda(funcType, producer.BuildExpression()).Compile();
    e.Register(Expression.Constant(factoryDelegate));
}

Will allow calling:

var handler = _container.GetInstance<Func<IHandleDomainEvent<TEvent>>>();

Solution

  • This is the beauty of Simple Injector: You'll never have to resolve an IEnumerable<Func<T>> because any IEnumerable<T> resolved by Simple Injector already functions as a stream.

    This means that when you resolve an IEnumerable<T>, none of the elements of the stream will be resolved. They get only resolved when iterating the enumerable, and are resolved one-by-one.

    When iterating the stream, elements will be resolved according to their lifestyle. This means that when the elements in the collection are Transient iterating the stream twice will cause the creation of new transient instances.

    Example:

    // Only resolves the enumerable, not the contained handlers.
    // This enumerable itself is a singleton, you can reference it forever.
    var collection = container.GetInstances<IEventHandler<OrderConfirmed>>();
    
    // Calls back into the container to get the first element, but nothing more
    var first = collection.First();
    
    // Since the stream that Simple Injector returns is a IList<T>, getting the last
    // element is an O(1) operation, meaning that only the last element is resolved;
    // not the complete collection.
    var last = collection.Last();
    
    // Calling .ToArray(), however, will obviously resolve all registrations that are 
    // part of the collection.
    var all = collection.ToArray();
    
    // Iterating a stream will always call back into the container, which ensures
    // that the stream adheres to the elements lifestyles. Transients will be
    // created on each iteration, while singletons will only be created once.
    container.Register<Apple>(Lifestyle.Transient);
    container.Register<Banana>(Lifestyle.Singleton);
    container.RegisterCollection<IFruit>(typeof(Apple), typeof(Banana));
    var fruits = container.GetAllInstances<IFruit>();
    Assert.AreNotSame(fruits.First(), fruits.First());
    Assert.AreSame(fruits.Last(), fruits.Last());
    
    // Even other collection types such as IReadOnlyCollection<T> behave as streams
    var col = container.GetInstance<IReadOnlyCollection<IEventHandler<OrderConfirmed>>();
    
    // This gives you the possibility to get a particular item by its index.
    var indexedItem = col[3];
    

    You can find more information on working with collections in Simple Injector here: