Autofac 隐式关系类型
Autofac 支持自动解析特定类型,隐式支持组件与服务间的特殊关系。要充分利用这些关系,只需正常注册你的组件,但是在使用服务的组件或调用Resolve()进行类型解析时,若要改变构造函数的参数,需要使用特定关系类型。 例如,当Autofac注入一个IEnumerable<ITask>类型的构造函数参数时,它不会寻找提供了IEnumerable<ITask>的组件。相反,容器会发现所有的ITask实现且注入它们。 (别担心,下面有例子显示各种类型的用法和它们的意思)。 注意:对于override这种默认行为,在隐式关系类型中同样存在。1、支持的关系类型
下面的表格简要说明在Autofac中支持的关系类型和你可以使用的.net类型。每个关系类型有一个详细的描述和用例。关系 | 类型 | 含义 |
A needs B | B | Direct Dependency(直接依赖关系) |
A needs B at some point in the future | Lazy<B> | Delayed Instantiation(延迟实例化) |
A needs B until some point in the future | Owned<B> | Controlled Lifetime(控制生命周期) |
A needs to create instances of B | Func<B> | Dynamic Instantiation(动态实例) |
A provides parameters of types X and Y to B | Func<X,Y,B> |
Parameterized Instantiation(参数化实例化) |
A needs all the kinds of B |
IEnumerable<B>, IList<B>, ICollection<B> |
Enumeration(枚举) |
A needs to know X about B | Meta<B> and Meta<B,X> | Metadata Interrogation |
A needs to choose B based on X | IIndex<X,B> | Keyed Service Lookup(键控服务查找) |
1.1Direct Dependency (B)--直接依赖
直接依赖关系是最基本的关系--组件A需要服务B。它是通过标准的构造函数和属性注入自动处理的:public class A { public A(B dependency) { ... } }注册组件A和B,然后解析:
var builder = new ContainerBuilder(); builder.RegisterType<A>(); builder.RegisterType<B>(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) { // B 自动注入到 A 中 var a = scope.Resolve<A>(); }
1.2Delayed Instantiation (Lazy<B>)--惰性依赖
惰性依赖不会被实例化,直到第一次使用它的时候。它出现在依赖不常用或构造昂贵的地方。要充分利用这一优势,在构造函数 A 中使用一个Lazy<B>:public class A { Lazy<B> _b; public A(Lazy<B> b) { _b = b } public void M() { // 在第一次调用 M() 方法时,创建组件 B _b.Value.DoSomething(); } }如果你有一个惰性依赖,而你也需要元数据,可以使用Lazy<B,M>代替繁琐的Meta<Lazy<B>, M>。
1.3 Controlled Lifetime (Owned<B>)
一个 owned 依赖,当不在需要它的时候,可以被所有者释放。Owned 依赖关系通常相当于依赖组件执行的一些工作单元。 当与实现了IDisposable的组件一起使用时,这种关系类型是非常有趣的。Autofac 在生命周期结束时自动处理一次性组件,但是那也许意味着组件会存在很长时间;或者你也许只是想控制处理你自己的对象。在这种情况下,你应该使用Owned依赖。public class A { Owned<B> _b; public A(Owned<B> b) { _b = b; } public void M() { // _b is used for some task _b.Value.DoSomething(); // 这里 _b 不再需要,所以,释放他 _b.Dispose(); } }
在内部,Autofac创建一个微小的lifetime scope,其中对B服务解析,且当你调用它的Dispose()时,这个lifetime scope 将被处理。这也就意味着,释放B也将释放它的依赖关系,除非这些依赖是共享的。
这也意味着,如果你有一个InstancePerLifetimeScope() 注册和一个Owned<B>解析,在同一lifetime scope 不同的地方使用同一类型,你可能会得到不同的实例,这个例子展示了这个问题:var builder = new ContainerBuilder(); builder.RegisterType<A>().InstancePerLifetimeScope(); builder.RegisterType<B>().InstancePerLifetimeScope(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) { // 这里解析一个使用了 InstancePerLifetimeScope() 的 类型 B ; var b1 = scope.Resolve<B>(); b1.DoSomething(); // 这个和上面的 b1 一样。 var b2 = scope.Resolve<B>(); b2.DoSomething(); // 在类型 A 中使用的类型 B 和其他地方的 B 不是同一个 var a = scope.Resolve<A>(); a.M(); }
设计就是这样的,因为你不想让一个组件处理 B 下的一切事情。然而,如果你不知道,它可能会导致一些混乱。
如果你想要一直控制B的处理,可以使用ExternallyOwned()方法注册B。1.4 Dynamic Instantiation (Func<B>)--动态实例化
使用自动生成函数,可以让你有效的调用Resolve<T>,而不会捆绑你的组件到Autofac上。如果你需要创建一个给定服务的多个实例,可以使用这种关系类型,或者你不确定你是否会需要一个服务,或者希望在运行时决定。在WCF 集成的场景下这种关系也非常有用,此处你需要创建一个新的服务代理在断层通道之后。 使用这种关系类型推荐使用Lifetime scopes.如果您注册一个对象为InstancePerDependency(),并多次调用Func<B>,每次你将得到一个新实例。但是,如果你注册一个对象为SingleInstance(),并调用Func<B>来多次解析对象,你每次都会得到同一个对象实例。 这种关系的一个示例如下:public class A { Func<B> _b; public A(Func<B> b) { _b = b; } public void M() { var b = _b(); b.DoSomething(); } }
1.5Parameterized Instantiation (Func<X, Y, B>)
您也可以使用自动生成函数,来传递强类型的参数给解析功能。可以选择是在注册过程中传递参数还是手动解析过程中传递:public class A { Func<int, string, B> _b; public A(Func<int, string, B> b) { _b = b } public void M() { var b = _b(42, "http://hel.owr.ld"); b.DoSomething(); } }
在内部,Autofac将它们视为类型的参数。这也就意味着,自动生成函数工厂,不能在输入参数列表中有重复的类型。例如,你有这样一个类型:
public class DuplicateTypes { public DuplicateTypes(int a, int b, string c) { // ... } }你可能会想注册这种类型,它有一个自动生成的函数工厂。您将解析此函数,但是你将无法执行它。
var func = scope.Resolve<Func<int, int, string, DuplicateTypes>>(); // Throws a DependencyResolutionException: var obj = func(1, 2, "three");
在一个松耦合的场景下,其中的参数需与类型相匹配,你不了解特定对象的构造函数的参数顺序。如果你需要做这样的事情,你应该使用自定义的委托类型来代替:
public delegate DuplicateTypes FactoryDelegate(int a, int b, string c);然后使用
RegisterGeneratedFactory()注册委托
:
builder.RegisterType<DuplicateTypes>(); builder.RegisterGeneratedFactory<FactoryDelegate>(new TypedService(typeof(DuplicateTypes)));
现在这个函数将工作:
var func = scope.Resolve<FactoryDelegate>(); var obj = func(1, 2, "three");
你有另一种方式,就是使用使用委托工厂。如果您决定使用内置的自动生成函数行为(Func<X,Y,B>),并且每个类型只解析一个函数,它将可以工作,但同一类的所有构造函数参数你会得到相同的输入。
var func = container.Resolve<Func<int, string, DuplicateTypes>>(); // This works and is the same as calling // new DuplicateTypes(1, 1, "three") var obj = func(1, "three");
使用这种关系类型且使用委托工厂时推荐使用Lifetime scopes.如果您注册一个对象为InstancePerDependency(),并多次调用Func<X,Y,B>
,每次你将得到一个新实例。但是,如果你注册一个对象为SingleInstance(),并调用Func<X,Y,B>
多次解析对象,你每次都会得到同一个对象实例即使你传递不同的参数。仅仅传递不同的参数不能打破lifetime scope。
1.6 Enumeration (IEnumerable<B>, IList<B>, ICollection<B>)
枚举类型的依赖提供相同服务(接口)的多种实现。在类似消息处理程序的场景下是非常有帮助的,其中一个消息进来且注册多个处理程序来处理消息。 例如,有一个依赖接口的定义像这样:public interface IMessageHandler { void HandleMessage(Message m); }
此外,你有一个用户的依赖关系,你需要有多个注册和用户需要的所有注册的依赖关系:
public class MessageProcessor { private IEnumerable<IMessageHandler> _handlers; public MessageProcessor(IEnumerable<IMessageHandler> handlers) { this._handlers = handlers; } public void ProcessMessage(Message m) { foreach(var handler in this._handlers) { handler.HandleMessage(m); } } }
您可以使用隐枚举关系类型很容易地做到这一点。只需注册所有的依赖和用户,并且当你解析用户,它所匹配的所有依赖将被解析为一个枚举:
var builder = new ContainerBuilder(); builder.RegisterType<FirstHandler>().As<IMessageHandler>(); builder.RegisterType<SecondHandler>().As<IMessageHandler>(); builder.RegisterType<ThirdHandler>().As<IMessageHandler>(); builder.RegisterType<MessageProcessor>(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) { // When processor is resolved, it'll have all of the // registered handlers passed in to the constructor. var processor = scope.Resolve<MessageProcessor>(); processor.ProcessMessage(m); }
如果没有匹配的项目在容器中注册,枚举将返回一个空集。也就是说,使用上面的例子,如果你没有注册任何IMessageHandler 实现时,它将中断:
// This throws an exception - none are registered! scope.Resolve<IMessageHandler>();但是,此处将继续工作:
// This returns an empty list, NOT an exception: scope.Resolve<IEnumerable<IMessageHandler>>();
你可能认为,如果你使用这种关系注入东西,你会得到一个空值。相反,你会得到一个空列表。
1.7Metadata Interrogation (Meta<B>, Meta<B, X>)
Autofac元数据功能允许您将任意数据与您在解析时决定使用的服务关联起来。如果你想在使用组件时做出这些决定,使用Meta<B>关系,它将为您提供所有的对象元数据的字符串/对象 词典:public class A { Meta<B> _b; public A(Meta<B> b) { _b = b; } public void M() { if (_b.Metadata["SomeValue"] == "yes") { _b.Value.DoSomething(); } } }
你也可以使用 strongly-typed 元数据, 通过在 Meta<B,X>
关系中指定元数据类型:
public class A { Meta<B, BMetadata> _b; public A(Meta<B, BMetadata> b) { _b = b; } public void M() { if (_b.Metadata.SomeValue == "yes") { _b.Value.DoSomething(); } } }
如果你有一个惰性依赖也需要元数据,你可以使用Lazy<B,M> 代替Meta<Lazy<B>, M>
1.8 Keyed Service Lookup (IIndex<X, B>)
当你有很多的特定项目(如了IEnumerable<B>关系)时,但你想要选择一个基于服务的key,您可以使用IIndex<X,B>关系。首先,使用key注册您的服务:var builder = new ContainerBuilder(); builder.RegisterType<DerivedB>().Keyed<B>("first"); builder.RegisterType<AnotherDerivedB>().Keyed<B>("second"); builder.RegisterType<A>(); var container = builder.Build();
然后使用IIndex<X,B>得到一个keyd服务字典:
public class A { IIndex<string, B> _b; public A(IIndex<string, B> b) { _b = b; } public void M() { var b = this._b["first"]; b.DoSomething(); } }
2、组成关系类型
关系类型可以组成,所以:IEnumerable<Func<Owned<ITask>>>
正确的解释为:
- All implementations, of
- Factories, that return
- Lifetime-controlled
- ITask services
3、关系类型和容器独立
在Autofac中,自定义关系类型是基于标准的.NET类型的,不会强迫你绑定你的应用程序与Autofac紧密结合。他们为你的容器配置一个编程模型,它与你写的其他组件(相对于不必知道很多特定容器扩展点和API还可能集中配置)的方式一致。 例如,您仍然可以创建一个自定义的ITaskFactory在你的核心模型中,但是需要提供一个基于Func<Owned<ITask>>
的AutofacTaskFactory实现。
请注意,某些关系是基于Autofac中的类型的(例如,IIndex<X,B>)。利用这些关系类型你至少有一个Autofac的引用,即使您在实际解析服务时选择使用不同的IoC容器。
引用:https://www.cnblogs.com/chiyueqi/p/autofac_RelationTypes.html