Bootstrap

.NET IoC 容器(二)Unity

.NET IoC 容器(二)Unity

Unity

Unity Application Block(Unity)是Microsoft模式和实践团队(Patterns & Practices team)推出的一个开源依赖注入容器。它是.NET Framework的一个组件,旨在简化应用程序的构建过程,提高代码的可测试性和可维护性。
Unity Application Block提供了以下功能:

  • 依赖注入(DI):Unity容器允许开发人员将对象的创建和管理交给容器,从而实现对象之间的解耦。通过依赖注入,对象不再负责自己的依赖关系,而是由容器在需要时提供。
  • 对象生命周期管理:Unity容器支持管理对象的生命周期,包括瞬时(transient)、单例(singleton)和线程本地(thread-local)等选项。这使得开发人员可以更精细地控制对象的生存期。
  • 配置灵活性:Unity允许开发人员通过配置文件或代码来定义对象之间的依赖关系和容器的行为。这种灵活性使得应用程序的配置和修改变得更加容易。
  • 支持多种解析方式:Unity容器支持构造函数注入、属性注入和方法注入等多种依赖注入方式,使开发人员可以根据需要选择最适合的解析方式。
  • 可扩展性:Unity容器是可扩展的,开发人员可以通过自定义扩展来增强其功能,以满足特定的需求。

总的来说,Unity Application Block是一个强大的依赖注入容器,可以帮助.NET开发人员构建可测试、可维护和灵活的应用程序。

Nuget 安装

image-20240410174934927

实现DI

定义接口

internal interface IComputer
{
}
internal interface IKeyboard
{
}
internal interface IMouse
{
}
internal interface IPerson
{
    IComputer Computer { get; set; }
    IKeyboard Keyboard { get; set; }
    IMouse Mouse { get; set; }
    void Work();
}

定义实现类

internal class ConstructBase
{
    public ConstructBase() 
    {
        Console.WriteLine($"{this.GetType().Name} - 被构造了");
    }
}
internal class LenovoComputer : ConstructBase, IComputer
{
    public LenovoComputer() : base() { }
}
internal class TogarKeyboard : ConstructBase, IKeyboard
{
    public TogarKeyboard() : base() { }
}
internal class LogitechMouse : ConstructBase, IMouse
{
    public LogitechMouse() : base() { }
}
internal class Programmer : ConstructBase, IPerson
{
    public IComputer Computer { get; set; }
    public IKeyboard Keyboard { get; set; }
    public IMouse Mouse { get; set; }

    public Programmer(IComputer computer, IKeyboard keyboard, IMouse mouse) : base()
    {
        Computer = computer;
        Keyboard = keyboard;
        Mouse = mouse;
    }

    public void Work()
    {
        Console.WriteLine("The programmer is distributing software...");
    }
}

依赖注入

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(); 
container.RegisterType<IKeyboard, TogarKeyboard>(); 
container.RegisterType<IMouse, LogitechMouse>(); 
container.RegisterType<IPerson, Programmer>(); 
IPerson programmer = container.Resolve<IPerson>(;
programmer.Work();

输出

LenovoComputer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
Programmer - 被构造了
The programmer is distributing software...
注入方式
构造函数注入

使用[InjectionConstructor]特性标注依赖注入时使用构造函数,默认使用参数最多的构造函数

[InjectionConstructor]
public Gamer(IComputer computer)
{
    Computer = computer;
}
属性注入

使用[Dependency]标注需要注入的属性

[Dependency]
public IKeyboard Keyboard { get; set; }
方法注入

方法注入是为了给方法传递参数,与构造函数注入的代码相似

[InjectionMethod]
public void Inject(IMouse mouse)
{
    this.Mouse = mouse;
}
注入顺序

定义类

internal class Gamer : IPerson
{
    public IComputer Computer { get; set; }
    [Dependency]
    public IKeyboard Keyboard { get; set; }
    public IMouse Mouse { get; set; }

    [InjectionConstructor]
    public Gamer(IComputer computer)
    {
        Computer = computer;
    }

    [InjectionMethod]
    public void Inject(IMouse mouse)
    {
        this.Mouse = mouse;
    }

    public void Work()
    {
        Console.WriteLine("The player is playing...");
    }
}

调用

IUnityContainer container = new UnityContainer();
container.RegisterType<IPerson, Gamer>(); 
container.RegisterType<IComputer, LenovoComputer>(); 
container.RegisterType<IKeyboard, TogarKeyboard>(); 
container.RegisterType<IMouse, LogitechMouse>(); 
IPerson person = container.Resolve<IPerson>();
person.Work();

输出

LenovoComputer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
The player is playing...

结果

注入顺序:构造函数注入 > 属性注入 > 方法注入

接口注册
重复注册
IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(); 
container.RegisterType<IKeyboard, TogarKeyboard>(); 
container.RegisterType<IMouse, LogitechMouse>(); 
container.RegisterType<IPerson, Gamer>(); 
container.RegisterType<IPerson, Programmer>(); 
IPerson person = container.Resolve<IPerson>();
person.Work();

输出

LenovoComputer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
Programmer - 被构造了
The programmer is distributing software...

调换Gamer和Programmer的注册顺序:

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(); 
container.RegisterType<IKeyboard, TogarKeyboard>(); 
container.RegisterType<IMouse, LogitechMouse>(); 
container.RegisterType<IPerson, Programmer>(); 
container.RegisterType<IPerson, Gamer>(); 
IPerson person = container.Resolve<IPerson>();
person.Work();

输出

LenovoComputer - 被构造了
Gamer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
The player is playing...

结论

同一接口多次注册时,后注册的会覆盖前面注册的,若需要实现多重注册,需要指定名称

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(); 
container.RegisterType<IKeyboard, TogarKeyboard>(); 
container.RegisterType<IMouse, LogitechMouse>(); 
container.RegisterType<IPerson, Programmer>("Programmer"); 
container.RegisterType<IPerson, Gamer>("Gamer"); 
IPerson programmer = container.Resolve<IPerson>("Programmer");
programmer.Work();
IPerson gamer = container.Resolve<IPerson>("Gamer");
gamer.Work();

输出

LenovoComputer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
Programmer - 被构造了
The programmer is distributing software...
LenovoComputer - 被构造了
Gamer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
The player is playing...
指定参数注册

修改接口

internal interface IMouse
{
    string Type { get; set; }
}

修改实现类

internal class LogitechMouse : ConstructBase, IMouse
{
    public LogitechMouse() : base() { }

    public LogitechMouse(string type) : base()
    {
        Type = type;
    }

    public string Type { get; set; }
}

调用

IUnityContainer container = new UnityContainer();
container.RegisterType<IMouse, LogitechMouse>(new InjectionConstructor("502"));
IMouse mouse = container.Resolve<IMouse>();
Console.WriteLine("Type: " + mouse.Type);

输出

LogitechMouse - 被构造了
Type: 502

多次注册同样覆盖

IUnityContainer container = new UnityContainer();
container.RegisterType<IMouse, LogitechMouse>(new InjectionConstructor("502"));
IMouse mouse_502 = container.Resolve<IMouse>();
Console.WriteLine("Type: " + mouse_502.Type);
container.RegisterType<IMouse, LogitechMouse>(new InjectionConstructor("304"));
IMouse mouse_304 = container.Resolve<IMouse>();
Console.WriteLine("Type: " + mouse_304.Type);

输出

LogitechMouse - 被构造了
Type: 502
LogitechMouse - 被构造了
Type: 304
生命周期

生命周期类型:

  • ContainerControlledLifetimeManager:每次调用Resolve(…)方法或依赖机制将实例注入其他类时,Unity都会返回相同的实例。
    • 每个容器生命周期允许将一个已存在或已解决的对象注册为创建或注册它的容器中的作用域单例。换句话说,这个实例在it war注册的容器中是唯一的。子容器或父容器可以为相同的契约注册自己的实例。当ContainerControlledLifetimeManager被释放时,实例也会被释放。
  • ExternallyControlledLifetimeManager:一个LifetimeManager,保存了一个指向它管理的实例的弱引用。
  • HierarchicalLifetimeManager:一个特殊的生命周期管理器,它的工作方式类似于ContainerControlledLifetimeManager,除了在有子容器的情况下,每个子容器都有自己的对象实例,而不是在公共父容器中共享一个。
    • Unity容器允许创建子容器的层次结构。这个生命周期为层次结构的每一层创建局部单例。所以,当你解析一个类型,而这个容器没有该类型的实例时,容器会创建新的实例。类型解析后返回相同的实例。如果创建了一个子容器并要求解析该类型,子容器将创建一个新实例并存储它以供后续解析。下次子容器请求解析类型时,它将返回存储的实例。如果你有多个子节点,每个子节点将解析自己的实例。
  • PerResolveLifetimeManager:这是一个自定义的生命周期管理器,作用类似于TransientLifetimeManager,但它也为默认构建计划提供信号,标记类型,以便在构建对象图中重用实例。
  • PerThreadLifetimeManager:一个LifetimeManager,它会为每个线程创建一个注册类型的新实例。
    • 每个线程生命周期意味着每个线程将创建一个已注册类型的新实例。换句话说,如果第一次在线程上调用Resolve{T}()方法,它将返回一个新对象。每次后续调用Resolve{T}(),或者依赖机制将该类型的实例注入同一线程上的其他类时,容器将返回相同的对象。这个LifetimeManager不会释放它持有的实例。
  • SingletonLifetimeManager:单例生命周期创建全局唯一的单例。任何Unity容器树(父容器和所有子容器)都保证只有一个注册类型的全局单例。
    • 使用单例生命周期注册类型总是将注册放在容器树的根位置,并使该容器的所有子节点都可以全局使用它。无论注册是否发生在子容器的根节点,目标始终是根节点。在任何具有单例生命周期的子节点上重复注册将始终覆盖根注册。当SingletonLifetimeManager被释放时,它持有的实例也会被释放。
  • SynchronizedLifetimeManager:生命周期管理器的基类,需要同步调用GetValue(ILifetimeContainer)。
    • 该类的目的是提供生命周期管理器同步模式的基本实现。SynchronizedLifetimeManager实例的GetValue(ILifetimeContainer)方法会获得一个锁,如果实例还没有初始化一个值,那么这个锁只会在调用SetValue(Object, ILifetimeContainer)方法进行初始化时释放,或者在调用GetValue方法的构建请求失败时释放。
  • TransientLifetimeManager:一个不做任何操作的LifetimeManager实现,从而确保每次都创建新的实例。
    • Transient lifetime是Unity容器的默认生命周期。顾名思义,它持续的时间很短,实际上,根本没有时间。在Unity容器术语中,拥有短暂的生命周期等同于没有生命周期管理器。

默认生命周期

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>();
IComputer computer0 = container.Resolve<IComputer>();
IComputer computer1 = container.Resolve<IComputer>();
Console.WriteLine("computer0: " + computer0.GetHashCode());
Console.WriteLine("computer1: " + computer1.GetHashCode());
Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");

输出

LenovoComputer - 被构造了
LenovoComputer - 被构造了
computer0: 15368010
computer1: 4094363
computer0 == computer1: False

单例生命周期

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(new SingletonLifetimeManager());
IComputer computer0 = container.Resolve<IComputer>();
IComputer computer1 = container.Resolve<IComputer>();
Console.WriteLine("computer0: " + computer0.GetHashCode());
Console.WriteLine("computer1: " + computer1.GetHashCode());
Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");

输出

LenovoComputer - 被构造了
computer0: 15368010
computer1: 15368010
computer0 == computer1: True

每个线程一个生命周期

IUnityContainer container = new UnityContainer();
container.RegisterType<IComputer, LenovoComputer>(new PerThreadLifetimeManager());
IComputer computer0 = container.Resolve<IComputer>();
IComputer computer1 = container.Resolve<IComputer>();
IComputer computer2 = null;
Task.Run(() =>
{
    computer2 = container.Resolve<IComputer>();
}).Wait();
Console.WriteLine("computer0: " + computer0.GetHashCode());
Console.WriteLine("computer1: " + computer1.GetHashCode());
Console.WriteLine("computer2: " + computer2.GetHashCode());
Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");
Console.WriteLine($"computer0 == computer2: {computer0 == computer2}");

输出

LenovoComputer - 被构造了
LenovoComputer - 被构造了
computer0: 15368010
computer1: 15368010
computer2: 4094363
computer0 == computer1: True
computer0 == computer2: False
依赖配置

Nuget

image-20240410174842834

配置文件

<configuration>
	<configSections>
		<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
	</configSections>
	<unity>
		<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
		<containers>
			<container name="Unity_Container">
				<!-- type="接口完全限定名, DLL文件名";mapTo="实现类完全限定名, DLL文件名" -->
				<register type="IoCStudy.Interfaces.IKeyboard, IoCStudy" mapTo="IoCStudy.Entities.TogarKeyboard, IoCStudy"/>
				<register type="IoCStudy.Interfaces.IMouse, IoCStudy" mapTo="IoCStudy.Entities.LogitechMouse, IoCStudy"/>
				<register type="IoCStudy.Interfaces.IComputer, IoCStudy" mapTo="IoCStudy.Entities.LenovoComputer, IoCStudy"/>
				<register type="IoCStudy.Interfaces.IPerson, IoCStudy" mapTo="IoCStudy.Entities.Programmer, IoCStudy" name="Programmer"/>
				<register type="IoCStudy.Interfaces.IPerson, IoCStudy" mapTo="IoCStudy.Entities.Gamer, IoCStudy" name="Gamer"/>
			</container>
		</containers>
	</unity>
</configuration>

调用

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "DI-Unity.config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
IUnityContainer container = new UnityContainer();
section.Configure(container, "Unity_Container");
IPerson programmer = container.Resolve<IPerson>("Programmer");
programmer.Work();
IPerson gamer = container.Resolve<IPerson>("Gamer");
gamer.Work();

输出

LenovoComputer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
Programmer - 被构造了
The programmer is distributing software...
LenovoComputer - 被构造了
Gamer - 被构造了
TogarKeyboard - 被构造了
LogitechMouse - 被构造了
The player is playing...

参考资料

IOC容器:Unity_51CTO博客_unity navmesh

IOC容器Unity三种注入总结_unity容器-CSDN博客

IOC容器之Unity与AutoFac_unity autofac-CSDN博客

控制容器的反转和依赖关系注入 模式 (martinfowler.com)

unitycontainer/unity at release/6.0.0 (github.com)

Unity 容器介绍 |Unity 容器 (unitycontainer.org)

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;