Skip to content
On this page

Working with Enumerable Types

WARNING

Be aware that the Lamar registrations and execution plans for enumerable types that are not explicitly registered are created on the first usage and will not appear in the WhatDoIHave() output until they are used as either a dependency for another service or directly through a service location call for the first time. This is normal, as expected behavior.

While you can certainly use any IEnumerable type as a service type with your own explicit configuration, Lamar has some built in support for these specific enumerable types:

  1. IEnumerable<T>
  2. IList<T>
  3. List<T>
  4. ICollection<T>
  5. T[]

Specifically, if you request one of these types either directly with GetInstance<IList<IWidget>>() or as a declared dependency in a constructor or setter (new WidgetUser(IList<IWidgets> widgets) for example) and you have no specific registration for the enumerable types, Lamar has a built in policy to return all the registered instances of IWidget in the exact order that the registrations were made to Lamar.

Note, if there are not any registrations for whatever T is, you'll get an empty enumeration.

Here's an acceptance test from the Lamar codebase that demonstrates this:

cs
[Fact]
public void collection_types_are_all_possible_by_default()
{
    // NOTE that we do NOT make any explicit registration of
    // IList<IWidget>, IEnumerable<IWidget>, ICollection<IWidget>, or IWidget[]
    var container = new Container(_ =>
    {
        _.For<IWidget>().Add<AWidget>();
        _.For<IWidget>().Add<BWidget>();
        _.For<IWidget>().Add<CWidget>();
    });

    // IList<T>
    container.GetInstance<IList<IWidget>>()
        .Select(x => x.GetType())
        .ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));

    // ICollection<T>
    container.GetInstance<ICollection<IWidget>>()
        .Select(x => x.GetType())
        .ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));

    // Array of T
    container.GetInstance<IWidget[]>()
        .Select(x => x.GetType())
        .ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));
}

snippet source | anchor

And another showing how you can override this behavior with explicit configuration:

cs
[Fact]
public void override_enumeration_behavior()
{
    var container = new Container(_ =>
    {
        _.For<IWidget>().Add<AWidget>();
        _.For<IWidget>().Add<BWidget>();
        _.For<IWidget>().Add<CWidget>();

        // Explicit registration should have precedence over the default
        // behavior
        _.For<IWidget[]>().Use(new IWidget[] { new DefaultWidget() });
    });

    container.GetInstance<IWidget[]>()
        .Single().ShouldBeOfType<DefaultWidget>();
}

snippet source | anchor

Sample Usage: Validation Rules

One of the ways that I have used the built in IEnumerable handling is for extensible validation rules. Say that we are building a system to process IWidget objects. As part of processing a widget, we first need to validate that widget with a series of rules that we might model with the IWidgetValidator interface shown below and used within the main WidgetProcessor class:

cs
public interface IWidgetValidator
{
    IEnumerable<string> Validate(IWidget widget);
}

public class WidgetProcessor
{
    private readonly IEnumerable<IWidgetValidator> _validators;

    public WidgetProcessor(IEnumerable<IWidgetValidator> validators)
    {
        _validators = validators;
    }

    public void Process(IWidget widget)
    {
        var validationMessages = _validators.SelectMany(x => x.Validate(widget))
            .ToArray();

        if (validationMessages.Any())
        {
            // don't process the widget
        }
    }
}

snippet source | anchor

We could simply configure all of the IWidgetValidator rules in one place with an explicit registration of IEnumerable<IWidgetValidator>, but what if we need to have an extensibility to add more validation rules later? What if we want to add these additional rules in addon packages? Or we just don't want to continuously break into the centralized Registry class every single time we add a new validation rule?

By relying on Lamar's IEnumerable behavior, we're able to split our IWidgetValidatior registration across multiple Registry classes and that's not infrequently useful to do.