Autofac
支持隐式自动解析特定类型,以支持组件和服务之间的特殊关系。要充分利用这些关系,只需简单地注册组件即可,但是在使用组件中更改构造函数参数或在Resolve()
调用中解析类型,以便使用指定的关系类型。
例如,当Autofac
注入IEnumerable<ITask>
类型的构造函数参数时,它将不会查找提供IEnumerable <ITask>
的组件。相反,容器会找到ITask
的所有实现并注入所有的实现。
(别担心 - 下面的示例显示了各种类型的用法以及它们的含义。)
注意:要覆盖这个默认行为,仍然可以注册这些类型的显式实现。
支持的关系类型
下表总结了Autofac中支持的每种关系类型,并显示了可用于使用这些关系类型的.NET类型。每种关系类型都有更详细的描述和用例。
Relationship | Type | Meaning |
---|---|---|
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 |
关系类型详情
Direct Dependency (B)
Delayed Instantiation (Lazy<B>)
Controlled Lifetime (Owned<B>)
Dynamic Instantiation (Func<B>)
Parameterized Instantiation (Func<X, Y, B>)
Enumeration (IEnumerable<B>, IList<B>, ICollection<B>)
Metadata Interrogation (Meta<B>, Meta<B, X>)
Keyed Service Lookup (IIndex<X, B>)
直接依赖(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>();
}
延迟实例化 (Lazy<B>
)
懒惰的依赖关系在第一次使用之前不会被实例化。 这出现在依赖不经常使用或构建成本高昂的地方。 要利用这一点,在A
的构造函数中使用Lazy <B>
public class A
{
Lazy<B> _b;
public A(Lazy<B> b) { _b = b }
public void M()
{
//实现B的组件是在第一次调用`M()`时创建的
_b.Value.DoSomething();
}
}
如果你有一个懒惰的依赖项,你也需要元数据,你可以使用Lazy<B,M>
而不是更长的Meta<Lazy<B>, M>
。
受控生命周期(Owned<B>
)
不再需要拥有者所有的依赖关系。 拥有的依赖通常对应于依赖组件执行的某个工作单元。
这种关系的类型在使用实现IDisposable
的组件时尤其有趣。 Autofac会在生命周期结束时自动处理一次性组件,但这可能意味着组件被搁置太久; 或者你可能只想控制自己处理对象。 在这种情况下,您将使用拥有的依赖项。
public class A
{
Owned<B> _b;
public A(Owned<B> b) { _b = b; }
public void M()
{
//_b用于某个任务
_b.Value.DoSomething();
//这里_b不再需要,所以它被释放
_b.Dispose();
}
}
在内部,Autofac
创建了一个小的生命周期范围,在这个范围中解析了B服务,当你调用Dispose()
时,生命周期范围被释放。 这意味着释放B也将处理它的依赖关系,除非这些依赖关系是共享的(例如,单例模式)。
这也意味着如果你有InstancePerLifetimeScope()
注册,并且你将其解析为Owned<B>
,那么你可能不会获得与在同一生命周期范围内的其他地方使用的相同的实例。 这个例子显示了这个问题:
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,则将B注册为ExternallyOwned()
。
动态实例化(Func<B>
)
使用自动生成的工厂可以让您有效地调用Resolve<T>()
,而无需将组件绑定到Autofac
。如果您需要创建给定服务的多个实例,或者您不确定是否需要服务并希望在运行时作出决定,请使用此关系类型。 这种关系在WCF集成的情况下也很有用,您需要在发生错误后创建新的服务代理。
生命周期作用域是看重使用这种关系类型。 如果将对象注册为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();
}
}
参数实例化(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>>();
//抛出一个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>>();
//这是一样的调用
// new DuplicateTypes(1, 1, "three")
var obj = func(1, "three");
您可以在高级主题部分阅读有关委托工厂和RegisterGeneratedFactory()
方法的更多信息。
使用此关系类型以及使用委托工厂时,生命周期作用域受到重视。如果将对象注册为InstancePerDependency()
并多次调用Func<X, Y, B>
,则每次都会得到一个新实例。但是,如果将一个对象注册为SingleInstance()
并调用Func<X, Y, B>
来多次解析对象,那么无论您传入哪个不同的参数,每次都会得到相同的对象实例。不同的参数不会打破生命周期作用域的重用。
枚举(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())
{
//处理器解析后,它将所有注册的处理程序传递给构造函数。
var processor = scope.Resolve<MessageProcessor>();
processor.ProcessMessage(m);
}
如果容器中没有注册匹配的项目,则枚举支持将返回一个空集。 也就是说,使用上面的例子,如果你没有注册任何IMessageHandler
实现,这将会中断:
//这会引发异常 - 没有任何注册!
scope.Resolve<IMessageHandler>();
但是,这是可以工作的:
//这将返回一个空列表,不是一个异常:
scope.Resolve<IEnumerable<IMessageHandler>>();
如果使用这种关系注入某些东西,这可能会创建一个“gotcha”,你可能会认为你会得到一个空值。 相反,你会得到一个空的列表。
元数据询问(Meta<B>, Meta<B, X>
)
Autofac
元数据功能使您可以将任意数据与可用于解决问题的服务相关联。 如果要在消费组件中做出这些决定,请使用Meta<B>
关系,它将为您提供所有对象元数据的string/object
字典:
public class A
{
Meta<B> _b;
public A(Meta<B> b) { _b = b; }
public void M()
{
if (_b.Metadata["SomeValue"] == "yes")
{
_b.Value.DoSomething();
}
}
}
您也可以使用强类型元数据,方法是在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>
。
带键服务查找(IIndex<X, B>
)
如果您有许多特定的项目(如IEnumerable<B>
关系),但您希望根据服务项选择一个项目,则可以使用IIndex<X, B>
关系。 首先,使用键注册您的服务:
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>
来获得带键服务的字典:
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();
}
}
组合关系类型
关系类型可以组合,所以:
IEnumerable<Func<Owned<ITask>>>
正确解释为:
所有的实现,
工厂,返回
生命周期控制
ITask服务
关系类型和容器独立性
基于标准.NET类型的Autofac
中的自定义关系类型不会强制您将应用程序更紧密地绑定到Autofac。他们为您提供了一个容器配置的编程模型,与您编写其他组件的方式一致(而不必知道很多特定的容器扩展点和可能集中配置的API)。
例如,您仍然可以在您的核心模型中创建一个自定义的ITaskFactory
,但是如果需要,可以提供一个基于Func<Owned<ITask>>
的AutofacTaskFactory
实现。
请注意,某些关系基于Autofac中的类型(例如IIndex<X, B>
)。即使您选择使用不同的IoC容器来实现服务的决定,使用这些关系类型也会使您至少具有对Autofac的引用。