Autofac 委托工厂
默认情况下,Autofac 支持 Func<T>
和 Func<X,Y,T>
的 隐式关系。如果你需要在运行时自动创建一个用于解决某些东西的工厂,这些是最佳选择。
自动生成工厂的一个缺点是它们不支持 多个相同类型的参数 。此外,有时你可能希望提供特定的委托类型作为工厂,而不是使用 Func<X,Y,T>
并希望人们总是正确地传递参数。
在这种情况下,可能需要考虑使用委托工厂。
生命周期范围也会被尊重,无论是在使用 Func<T>
或参数化的 Func<X,Y,T>
关系时,还是使用委托工厂。如果你将一个对象注册为 InstancePerDependency()
,并且多次调用委托工厂,每次都会得到一个新的实例。然而,如果你将一个对象注册为 SingleInstance()
,并且多次通过委托工厂来解决该对象,无论你传递不同的参数,你都将始终获得同一个对象实例。只是传递不同的参数不会破坏对生命周期范围的尊重。
通过本页,我们将使用股票投资组合的例子,其中持有不同的股票,并可能需要从远程服务获取当前报价。
创建委托
设置委托工厂的第一步是创建一个将用于从生命周期范围动态解析值的委托。这将代替 Func<T>
或 Func<X,Y,T>
的隐式关系。
这里有一个单个持股示例 - 持有一股股票和数量。消费者不再直接实例化这个类,而是使用委托工厂。
public class Shareholding
{
// 注意,委托中的类型和名称都与构造函数中的参数相匹配。这一点很重要!
public delegate Shareholding Factory(string symbol, uint holding);
public Shareholding(string symbol, uint holding)
{
Symbol = symbol;
Holding = holding;
}
public string Symbol { get; private set; }
public uint Holding { get; set; }
}
Shareholding
类声明了一个构造函数,但还提供了可以间接通过从生命周期范围中解析它们来创建 Shareholding
实例的委托类型。
Autofac 可以利用这一点自动生成一个工厂:
var builder = new ContainerBuilder();
builder.RegisterType<Shareholding>();
using var container = builder.Build();
using var scope = container.BeginLifetimeScope();
var createHolding = scope.Resolve<Shareholding.Factory>();
var holding = createHolding("ABC", 1234);
工厂是一个标准的委托,可以通过 Invoke()
或如上所示的函数语法进行调用。
默认情况下,Autofac 会根据名称匹配委托的参数到构造函数的参数。如果你使用泛型 Func
关系,Autofac 将切换到按类型匹配参数。名称匹配很重要 - 它允许你在需要时提供多个相同类型的参数,这是 Func
隐式关系无法支持的。然而,这也意味着如果你更改构造函数参数的名称,你也必须在委托中更改这些名称。
使用委托
一旦你注册了工厂,其他组件就可以消费它。
public class Portfolio
{
private readonly Shareholding.Factory _shareHoldingFactory;
private readonly List<Shareholding> _holdings = new List<Shareholding>();
public Portfolio(Shareholding.Factory shareholdingFactory)
{
_shareHoldingFactory = shareholdingFactory;
}
public void Add(string symbol, uint holding)
{
_holdings.Add(_shareHoldingFactory(symbol, holding));
}
}
要设置此关系,Portfolio
类会在构建之前用以下方式注册到容器中:
builder.RegisterType<Portfolio>();
然后你可以从生命周期范围请求 Portfolio
实例:
var portfolio = scope.Resolve<Portfolio>();
portfolio.Add("DEF", 4324);
添加已解析的构造函数参数
这是使用委托工厂的主要好处:你可以添加更多从生命周期范围中解析的构造参数,而不会影响委托!想象一个远程股票报价服务:
public interface IQuoteService
{
decimal GetQuote(string symbol);
}
在 Shareholding
类中,我们想要通过获取报价来计算股票的当前价值。我们可以在 Shareholding
类中添加对 IQuoteService
的依赖,这将由 Autofac 自动填充 - 并且你不必将其添加到委托工厂!然后我们可以添加一个 CurrentValue()
方法来使用新服务。
public class Shareholding
{
// 我们不必将报价服务添加到工厂委托中。
public delegate Shareholding Factory(string symbol, uint holding);
private readonly IQuoteService _quoteService;
// 构造函数中未显示在委托中的参数将来自适当的生命周期范围。
public Shareholding(string symbol, uint holding, IQuoteService quoteService)
{
Symbol = symbol;
Holding = holding;
_quoteService = quoteService;
}
public string Symbol { get; private set; }
public uint Holding { get; set; }
public decimal CurrentValue()
{
// 我们可以使用新服务获取当前值。
return _quoteService.GetQuote(Symbol) * Holding;
}
}
IQuoteService
的实现者也应注册到容器中:
builder.RegisterType<WebQuoteService>().As<IQuoteService>();
现在 Portfolio
类可以利用新的 CurrentValue()
方法,而不了解任何关于报价服务的事情。
public class Portfolio
{
private readonly Shareholding.Factory _shareHoldingFactory;
private readonly List<Shareholding> _holdings = new List<Shareholding>();
public Portfolio(Shareholding.Factory shareholdingFactory)
{
_shareHoldingFactory = shareholdingFactory;
}
public void Add(string symbol, uint holding)
{
// 注意,我们不必传递报价服务 - Autofac将自动从生命周期范围中填充它。
_holdings.Add(_shareHoldingFactory(symbol, holding));
}
public decimal CurrentValue()
{
// 我们可以使用新方法获取整个投资组合的当前值。
return _holdings.Aggregate(0m, (agg, holding) => agg + holding.CurrentValue());
}
}
生命周期范围和释放
就像使用Func<T>
关系或直接调用Resolve<T>()
一样,使用委托工厂是从生命周期范围中解决某个东西。如果你解决的是可释放的(disposable),那么生命周期范围将跟踪它,并在范围释放时释放它<../lifetime/disposal>
。如果直接从容器或在使用可释放组件的长时间存在的生命周期范围中解决,可能会导致内存泄漏,因为范围持有所有已解决的可释放组件的引用。
注册 GeneratedFactory(已废弃)
重要 自 Autofac 7.0 开始,
RegisterGeneratedFactory
现在标记为废弃。委托工厂和 函数关系 已经取代了这个功能。
处理松散耦合场景的旧方法是通过使用 RegisterGeneratedFactory()
。这与委托工厂的工作方式非常相似,但需要显式的注册操作。
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");