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 arrayT[]
orIEnumerable<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