C#_深入理解Unity容器

一、背景

**DIP是依赖倒置原则:**一种软件架构设计的原则(抽象概念)。依赖于抽象不依赖于细节

**IOC即为控制反转(Inversion of Control):**传统开发,上端依赖(调用/指定)下端对象,会有依赖,把对下端对象的依赖转移到第三方容器(工厂+配置文件+反射),能够程序拥有更好的扩展性,是DIP的具体实现方式,可以用来减低计算机代码之间的耦合度。

DI 即为依赖注入(Dependency Injection):

  1. 是实现IOC的手段和方法,就是能做到构造某个对象时,将依赖的对象自动初始化并注入 :
  2. 有三种注入方式:构造函数注入–属性注入–方法注入(按时间顺序):
  3. 构造函数注入用的最多,默认找参数最多的构造函数,可以不用特性,可以去掉对容器的依赖

**Unity容器:**是微软推出的IOC框架,使用这个框架,可以实现AOP面向切面编程,便于代码的后期维护,此外,这套框架还自带单例模式,可以提高程序的运行效率。

二、Nuget下载

Nuget搜索Unity,名称为Unity的

三、简单使用

使用Unity来管理对象与对象之间的关系可以分为以下几步:

  1. 创建一个UnityContainer对象
  2. 通过UnityContainer对象的RegisterType方法来注册对象与对象之间的关系
  3. 通过UnityContainer对象的Resolve方法来获取指定对象关联的对象
IUnityContainer container = new UnityContainer();

后面我们用到的方法,其实大多数是IUnityContainer接口的拓展方法。

1、Register and Resolve

public interface ICar
    {
        int Run();
    }

    public class BMW : ICar
    {
        private int _miles = 0;

        public int Run()
        {
            return ++_miles;
        }
    }

    public class Ford : ICar
    {
        private int _miles = 0;

        public int Run()
        {
            return ++_miles;
        }
    }

    public class Audi : ICar
    {
        private int _miles = 0;

        public int Run()
        {
            return ++_miles;
        }

    }
    
    public class Driver
    {
        private ICar _car = null;
        public Driver(ICar car)
        {
            _car = car;
        }

        public void RunCar()
        {
            Console.WriteLine($"Running {_car.GetType().Name} - {_car.Run()} mile ");
        }
    }

上面代码中,我们可以看到Driver类依赖ICar接口,我们一个简单的做法就是实现一个ICar接口的对象,通过构造函数传递给Driver。

如果使用Unity容器呢?

container.RegisterType<ICar, BMW>();
            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:Running BMW - 1 mile

上例中,我们使用Driver driver = container.Resolve()创造了一个Driver对象,但是Driver对象依赖于ICar。幸运的是,我们提前注册了ICar的实现类型为BWM:container.RegisterType<ICar, BMW>()。所以,在创建Driver对象的时候自动将BWM注入其中了。

1)Multiple Registration
container.RegisterType<ICar, BMW>();
            container.RegisterType<ICar, Ford>();
            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:Running Ford - 1 mile

同一个接口ICar注册多次的时候,以最后一次注册的类型为准,前面均会被覆盖。

2)Register Named Type
//注册ICar的时候给了一个字符串作为name
            container.RegisterType<ICar, BMW>("ACar");
            container.RegisterType<ICar, Ford>();

            //这里一般会找那个没有name的ICar注入。
            Driver driver1 = container.Resolve<Driver>();
            driver1.RunCar();
            //注册Driver的时候,根据name取到了注册的ICar,通过构造函数注入给了Driver
            container.RegisterType<Driver>(new InjectionConstructor(container.Resolve<ICar>("ACar")));

            //如果不通过ICar注册Driver,直接在这里使用ICar的name来获取Driver是不行的
            Driver driver = container.Resolve<Driver>();

            driver.RunCar();
output:Running Ford - 1 mile
        Running BMW - 1 mile

相同接口的不同实现可以通过一个字符串命名做区分。

3)Register Instance

可以将已经存在的对象注册到容器中,每一次使用的时候都会是那一个对象不会创建新的对象。

将Driver修改为:

public class Driver
    {
        private ICar _car = null;
        private Guid guid= Guid.NewGuid();
        public Driver(ICar car)
        {
            _car = car;
        }

        public void RunCar()
        {
            Console.WriteLine($"{guid.ToString()} Running {_car.GetType().Name} - {_car.Run()} mile ");
        }
    }
container.RegisterInstance<ICar>(new Audi());

            Driver driver = container.Resolve<Driver>();
            driver.RunCar();

            Driver driver1 = container.Resolve<Driver>();
            driver1.RunCar();

            Driver driver2 = container.Resolve<Driver>();
            driver2.RunCar();
            Console.ReadKey();
output:d771409d-1ab5-4a59-b48b-e6b6224e3cd2 Running Audi - 1 mile
        cce5e4ed-4acd-4111-a94f-b2f39ac622a6 Running Audi - 2 mile
        23db5edc-7fb9-4ab7-ba25-172480b4b500 Running Audi - 3 mile

上例中,每一次Resolve时,注入的ICar都是同一个对象(new Audi()),但是Driver对象不是同一个。

2、Constructor Injection

我们知道依赖注入的方式有三种,并且从上面的例子中我们可以看出,Unity的Resolve方法默认是构造函数注入的方式。

container.RegisterType<ICar, Audi>();

            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:58b8aee6-18c5-4ad9-9a50-055ebdcb9980 Running Audi - 1 mile
1)Multiple Parameters

添加一个接口和它的一些实现:

public interface ICarKey
    {
    
    }
    
    public class BMWKey : ICarKey
    {
    
    }
    
    public class AudiKey : ICarKey
    {
    
    }
    
    public class FordKey : ICarKey
    {
    
    }

再将Driver类修改为:

public class Driver
    {
        private ICar _car = null;
        private ICarKey _key = null;
        private Guid guid= Guid.NewGuid();
        public Driver(ICar car,ICarKey key)
        {
            _car = car;
            _key = key;
        }
    
        public void RunCar()
        {
            Console.WriteLine($"{guid.ToString()} Running {_car.GetType().Name} with {_key.GetType().Name} - {_car.Run()} mile ");
        }
    }
container.RegisterType<ICar, Audi>();
            container.RegisterType<ICarKey, AudiKey>();
    
            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:e76d9df2-a2aa-45fd-8721-e9fbe607544e Running Audi with AudiKey - 1 mile
2)Multiple Constructors

如果Driver有多个构造函数

public class Driver
    {
        private ICar _car = null;
        private ICarKey _key = null;
        private Guid guid= Guid.NewGuid();
        public Driver(ICar car,ICarKey key)
        {
            _car = car;
            _key = key;
        }
    
        public Driver(ICar car)
        {
            _car = car;
        }
    
        public void RunCar()
        {
            Console.WriteLine($"{guid.ToString()} Running {_car?.GetType().Name} with {_key?.GetType().Name} - {_car?.Run()} mile ");
        }
    }
container.RegisterType<ICar, Audi>();
            container.RegisterType<ICarKey, AudiKey>();

            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:6681c7e2-0235-495e-b52f-2457f58dc6d7 Running Audi with AudiKey - 1 mile

那么在默认情况下,使用的就是参数较多的那一个构造函数。

想要指定一个构造函数,则有两种方式:

A、InjectionConstructorAttribute

在想要使用的构造函数上使用InjectionConstructorAttribute特性

[InjectionConstructor]
        public Driver(ICar car)
        {
            _car = car;
        }
output:c2cb415e-9d65-47bb-a895-ed0a16a9c8c6 Running Audi with  - 1 mile

则使用的就是被特性标记的构造函数。

B、使用RegisterType()的重载函数

RegisterType()方法中params InjectionMember[] injectionMembers参数就是传递构造函数的参数。

container.RegisterType<ICar, Audi>();
            container.RegisterType<ICarKey, AudiKey>();
    
            //注册Driver时,使用一个参数为ICar类型的构造函数
            container.RegisterType<Driver>(new InjectionConstructor(container.Resolve<ICar>()));
    
            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
output:3488a2a1-2cff-45a9-99a6-0fb5dfdc6fa2 Running Audi with  - 1 mile

3、Property Injection

暂不做介绍

4、Method Injection

暂不做介绍

5、Overrides

上面的例子可以看到,我们在Resolve的时候,Unity都会自动使用已经注册了的依赖创建对象。但是如果不想使用注册的依赖呢?

就需要用到ResolverOverride,这是一个抽象类,有三个派生类:

  1. ParameterOverride: Used to override constructor parameters.
  2. PropertyOverride: Used to override the value of a specified property.
  3. DependencyOverride: Used to override the type of dependency and its value

举例一个override构造函数的参数:

container.RegisterType<ICar, Audi>();
                container.RegisterType<ICarKey, AudiKey>();
    
                Driver driver = container.Resolve<Driver>();
                driver.RunCar();
    
                Driver driver1 = container.Resolve<Driver>(new ResolverOverride[]
                {
                    new ParameterOverride("car",new Ford()),
                    new ParameterOverride("key",new FordKey()),
                });
                driver1.RunCar();
output:bb8f06bc-6fed-4b32-9c7d-03d33ebd1b19 Running Audi with AudiKey - 1 mile
        ca39ec80-f227-4e76-94be-67ff20d56a21 Running Ford with FordKey - 1 mile

6、Lifetime Managers

Unity容器还可以管理依赖的生命周期,通过RegisterType()方法中的ITypeLifetimeManager参数。

Lifetime Manager

Description

TransientLifetimeManager

Creates a new object of the requested type every time you call the Resolve or ResolveAll method.

ContainerControlledLifetimeManager

Creates a singleton object first time you call the Resolve or ResolveAll method and then returns the same object on subsequent Resolve or ResolveAll calls.

HierarchicalLifetimeManager

Same as the ContainerControlledLifetimeManager, the only difference is that the child container can create its own singleton object. The parent and child containers do not share the same singleton object.

PerResolveLifetimeManager

Similar to the TransientLifetimeManager, but it reuses the same object of registered type in the recursive object graph.

PerThreadLifetimeManager

Creates a singleton object per thread. It returns different objects from the container on different threads.

ExternallyControlledLifetimeManager

It maintains only a weak reference of the objects it creates when you call the Resolve or ResolveAll method. It does not maintain the lifetime of the strong objects it creates, and allows you or the garbage collector to control the lifetime of the objects. It enables you to create your own custom lifetime manager.

默认情况下是TransientLifetimeManager,每一次都会Resolve的时候都会创建新的依赖。

container.RegisterType<ICar, Audi>(new TransientLifetimeManager());
            container.RegisterType<ICarKey, AudiKey>();
    
            Driver driver = container.Resolve<Driver>();
            driver.RunCar();
    
            Driver driver1 = container.Resolve<Driver>();
            driver1.RunCar();
output:275eb0e4-b76b-4ac2-a15e-cb899adefb03 Running Audi with AudiKey - 1 mile
        9dd3a879-07ed-4508-a061-b5fc85ea40e3 Running Audi with AudiKey - 1 mile

使用ContainerControlledLifetimeManager注册时,会注册一个单例的依赖。

```csharp
            container.RegisterType<ICar, Audi>(new ContainerControlledLifetimeManager());
            container.RegisterType<ICarKey, AudiKey>();

            Driver driver = container.Resolve<Driver>();
            driver.RunCar();

            Driver driver1 = container.Resolve<Driver>();
            driver1.RunCar();

output:4b9a8be6-1e20-42f9-942b-32919c4f55b4 Running Audi with AudiKey - 1 mile
6bb64e7d-c85a-4bc7-a03a-67f937877753 Running Audi with AudiKey - 2 mile

其他的暂不做解释