RegisterFactory(InjectionFactory)的应用

Unity是一个.NET Framework用于依赖注入的容器。依赖注入一般分为三个阶段:Register, Resolve和Dispose。Unity支持三种方式的Register:

  • Instance registration
  • Type registration
  • Factory registration

那这三种方式分别用于什么场景呢?简单来说,Instance registration和Type registration较为常见,直接通过ResolvedParameter解析依赖的对象,Factory registration更灵活,可以通过调用方法创建对象。举个例子,如果注入对象所依赖的参数是一个List<T>Dictionary<T1, T2>,而T又是自定义的类,那么就必须使用Factory registration。本文主要介绍的就是如何解析一个自定义类的List或Dictionary。

RegisterType示例

从例子切入,定义IAnimal Interface,并有Tiger和Deer两个实现,还有一个Zoo类,聚合了Tiger和Deer:

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 IAnimal _tiger;
    private readonly IAnimal _deer;

    public Zoo(IAnimal tiger, IAnimal deer)
    {
        _tiger = tiger;
        _deer = deer;
    }

    public void Play()
    {
        _tiger.Run();
        _deer.Run();
    }
}

注入并调用:

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

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

        Console.ReadKey();
    }
}

运行结果:

Tiger runs away
Deer runs away

RegisterFactory示例

RegisterFactory (已淘汰的InjectionFactory)可以用Unity创建的Factory创建对象,这是它与RegisterInstance和RegisterType的主要区别。

继续上面的例子,为了更灵活,考虑让Zoo类接受animal list作为参数:

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();
        }
    }
}

那么如何注入List?比较直观的想法是这样(错误代码):

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

遇到编译错误:

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

报错原因在于List中是IAnimal类型,而ResolvedParameter是Unity.Injection.ResolvedParameter类型,二者不匹配,编译错误。在List内部ResolveParameter是运行时在List的构造函数中创建对象,而InjectionConstructor()中只能静态Resolve对象。所以RegisterType并不能达到在List中Resolve对象的目的。

RegisterFactory可以用Factory通过执行一段代码解决问题:

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

RegisterFactory接受Func<IUnityContainer, object>为一个Factory,在List初始化时Resolve两个IAnimal对象。

直接解析自定义类Collection

不用RegisterFactory也可直接解析自定义类Collection,但有两个条件:

  • 待解析的Collection必须是array []或IEnumerable<T>
  • 所有被注册为该自定义类的类型都会被加入Collection中,不能解析出所有自定义类的一个子集。听起来很绕,可以看下面实例。

先修改Zoo的参数,由List<IAnimal>变为IEnumerable<IAnimal>

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();
        }
    }
}

然后可通过下面语句注册Zoo:

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

但这种注册方式会把所有已注册的IAnimal都加入List<IAnimal>,如果只想把Tiger加入其中则不行,必须使用RegisterFactory的方式。

RegisterFactory vs RegisterType

考虑Zoo(IAnimal tiger, IAnimal deer)的两种不同注入方式:

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

container.RegisterFactory<Zoo>("Zoo",
    m => new Zoo(
        m.Resolve<IAnimal>("Tiger"),
        m.Resolve<IAnimal>("Deer"))
    );

在container.Resolve("Zoo")时,RegisterType直接通过container创建对象,而RegisterFactory是通过Zoo Factory调用委托new Zoo()的方式创建对象(在“m => new Zoo(”处可设置断点查看)。

简而言之,RegisterFactory是一种比RegisterType更为灵活的注入方式,可以通过注入一个Factory的委托执行代码进行对象创建,但RegisterFactory用起来更为复杂。

完整代码

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