C#_深入理解Unity容器

heater404發表於2020-12-15

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 ManagerDescription
TransientLifetimeManagerCreates a new object of the requested type every time you call the Resolve or ResolveAll method.
ContainerControlledLifetimeManagerCreates a singleton object first time you call the Resolve or ResolveAll method and then returns the same object on subsequent Resolve or ResolveAll calls.
HierarchicalLifetimeManagerSame 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.
PerResolveLifetimeManagerSimilar to the TransientLifetimeManager, but it reuses the same object of registered type in the recursive object graph.
PerThreadLifetimeManagerCreates a singleton object per thread. It returns different objects from the container on different threads.
ExternallyControlledLifetimeManagerIt 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註冊時,會註冊一個單例的依賴。

            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

其他的暫不做解釋

相關文章