Resolve a Collection of Customized Class in UnityConfig

Unity supports three ways of registering types:

  • Instance registration
  • Type registration
  • Factory registration

Typically, Instance registration and Type registration resolve dependencies through ResolvedParameter<T>, while Factory registration resolve dependencies by a factory delegate. In practice, when you want to resolve a List<T> or Dictionary<T1, T2>, Factory registration is what you want. Let's go through how to resolve a collection of customized classes.

Animal Example

Start with example, assume we have an IAnimal interface which has two implementations. There is a Zoo class which accept List<IAnimal> as the parameter:

public interface IAnimal
{
    void Run();
}

public class Tiger : IAnimal
{
    public void Run()
    {
        Console.WriteLine("Tiger runs away");
    }
}

public class Deer : IAnimal
{
    public void Run()
    {
        Console.WriteLine("Deer runs away");
    }
}

public class Zoo
{
    private readonly List<IAnimal> _animals;

    public Zoo(List<IAnimal> animals)
    {
        _animals = animals;
    }

    public void Play()
    {
        foreach (var animal in _animals)
        {
            animal.Run();
        }
    }
}

Here comes the quesiton, how to register List<IAnimal>? A straightforward approach might look like this (WRONG):

container.RegisterType<List<IAnimal>>(
    "AnimalList",
    new InjectionConstructor(
        new List<IAnimal>
        {
            new ResolvedParameter<IAnimal>("Tiger"),
            new ResolvedParameter<IAnimal>("Deer")
        })
);

Compile error:

Argument 1: cannot convert from 'Unity.Injection.ResolvedParameter<ConsoleApp1.IAnimal>' to 'ConsoleApp1.IAnimal'

Because in List<IAnimal>, the type is IAnimal, while ResolvedParameter<IAnimal> is a type of Unity.Injection.ResolvedParameter<IAnimal>, there is a type mismatching. Therefore, we cannot dynamically resolve a collection of customized class by InjectionConstructor().

Directly Resolve Collection

Resolving collections of Objects of a Particular Type

Unity supports creating multiple registration of the same type by adding a name to the registration.

When you want to obtain a list of all the registered objects of a specific type, IPrinter in this case, you can use the array T[] or IEnumerable<T> of that type. The difference between array and enumerable resolution is that array only returns named (nondefault name) registrations where enumerable always returns all, named and unnamed registrations.

We can modify the parameter of Zoo from List<IAnimal> to IEnumerable<IAnimal> to solve the problem:

public class Zoo
{
    private readonly IEnumerable<IAnimal> _animals;

    public Zoo(IEnumerable<IAnimal> animals)
    {
        _animals = animals;
    }

    public void Play()
    {
        foreach (var animal in _animals)
        {
            animal.Run();
        }
    }
}

Then register Zoo like this:

container.RegisterType<Zoo, Zoo>("Zoo",
    new InjectionConstructor(
        new ResolvedParameter<List<IAnimal>>()
    ));

However, such directly resolving collection approach has a drawback: it cannot resolve a subset of registered types, it always resolves all registered types. For instance, if you register Tiger and Deer as IAnimal but want to resolve a List<IAnimal> which only contains Tiger, it cannot be done.

RegisterFactory is what you need.

Resolve Collection by RegisterFactory

RegisterFactory register a function delegate to resolve a collection of IAnimal:

container.RegisterFactory<List<IAnimal>>(
    "AnimalList",
    m => new List<IAnimal>
    {
        m.Resolve<IAnimal>("Tiger"),
        // m.Resolve<IAnimal>("Deer"),
    }
);

RegisterFactory accepts Func<IUnityContainer, object> as parameter and resolves an IAnimal instance during initialization. It's able to resolve two IAnimal instances by simply uncommenting m.Resolve<IAnimal>("Deer").

In summary, RegisterFactory is a more flexible way than RegisterType to resolve dependencies. Just a little bit complex.

Full Code

using System;
using System.Collections.Generic;
using Unity;
using Unity.Injection;

namespace ConsoleApp1
{
    public interface IAnimal
    {
        void Run();
    }

    public class Tiger : IAnimal
    {
        public void Run()
        {
            Console.WriteLine("Tiger runs away");
        }
    }

    public class Deer : IAnimal
    {
        public void Run()
        {
            Console.WriteLine("Deer runs away");
        }
    }

    public class Zoo
    {
        private readonly IEnumerable<IAnimal> _animals;

        public Zoo(IEnumerable<IAnimal> animals)
        {
            _animals = animals;
        }

        public void Play()
        {
            foreach (var animal in _animals)
            {
                animal.Run();
            }
        }
    }

    public class SimpleZoo
    {
        private readonly IEnumerable<IAnimal> _animals;

        public SimpleZoo(IEnumerable<IAnimal> animals)
        {
            _animals = animals;
        }

        public void Play()
        {
            foreach (var animal in _animals)
            {
                animal.Run();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Registration
            var container = new UnityContainer();
            container.RegisterType<IAnimal, Deer>("Deer", new InjectionConstructor());
            container.RegisterType<IAnimal, Tiger>("Tiger", new InjectionConstructor());
            container.RegisterFactory<List<IAnimal>>(
                "AnimalList",
                m => new List<IAnimal>
                {
                    m.Resolve<IAnimal>("Tiger")
                }
            );
            container.RegisterType<Zoo, Zoo>("Zoo",
                new InjectionConstructor(
                    new ResolvedParameter<List<IAnimal>>()
                ));

            container.RegisterType<SimpleZoo, SimpleZoo>("SimpleZoo",
                new InjectionConstructor(
                    new ResolvedParameter<List<IAnimal>>("AnimalList")
                ));

            // Resolve the instances
            Console.WriteLine("Zoo: ");
            var zoo = container.Resolve<Zoo>("Zoo");
            zoo.Play();

            Console.WriteLine();
            Console.WriteLine("SimpleZoo: ");
            var simpleZoo = container.Resolve<SimpleZoo>("SimpleZoo");
            simpleZoo.Play();

            Console.ReadKey();
        }
    }
}

Reference

C# Unity register a list of registered types and resolve by adding N instances

Registering Types and Object Instances

Dependency Injection with Unity

Resolving collections of Objects of a Particular Type