猿教程 Logo

Autofac教程:隐性关系类型

Autofac支持自动解析特定类型以支持组件和服务之间的特殊关系。 要充分利用这些关系,只需正常注册您的组件,但更改消费组件中的构造函数参数或Resolve()调用中解析的类型,以使其具有指定的关系类型。

例如,当Autofac正在注入IEnumerable <ITask>类型的构造函数时,它不会寻找提供IEnumerable <ITask>的组件。 相反,容器将找到ITask的所有实现并注入它们。

(不用担心 - 下面的例子显示了各种类型的用法以及它们的含义。)

注意:如果要覆盖此默认行为,仍然可以注册这些类型的显式实现。

[本文档内容基于Nick Blumhardt的博客文章The Relationship Zoo。]


支持的关系类型

下面总结了Autofac中每种支持的关系类型,并显示可用于使用它们的.NET类型。 每个关系类型都有详细的描述和用例。

直接依赖

直接依赖关系是支持最基本的关系 - 组件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 is automatically injected into 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()
  {
      // The component implementing B is created the
      // first time M() is called
      _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 is used for some task
      _b.Value.DoSomething();

      // Here _b is no longer needed, so
      // it is released
      _b.Dispose();
  }
}

在内部,Autofac创建一个微小的生命周期范围,其中B服务被解析,当您调用Dispose()时,将处理生命周期范围。 这意味着处理B也将处理其依赖,除非这些依赖关系是共享的(例如单例)。

这也意味着如果您有InstancePerLifetimeScope()注册,并且将其解析为拥有<B>,则可能无法获得与在同一生命周期范围内的其他位置使用的实例。 这个例子显示了getcha:

var builder = new ContainerBuilder();
builder.RegisterType<A>().InstancePerLifetimeScope();
builder.RegisterType<B>().InstancePerLifetimeScope();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  // Here we resolve a B that is InstancePerLifetimeScope();
  var b1 = scope.Resolve<B>();
  b1.DoSomething();

  // This will be the same as b1 from above.
  var b2 = scope.Resolve<B>();
  b2.DoSomething();

  // The B used in A will NOT be the same as the others.
  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>>();

// 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");

您可以在高级主题部分阅读有关委托工厂和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())
{
  // 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>>();

这可以创建一个“gotcha”,如果您使用此关系注入某些东西,您可能会认为您将获得一个空值。 相反,你会得到一个空的列表。

元数据查询(Meta <B>,Meta <B,X>)

自动填充元数据功能可让您将任意数据与可用于在解析时作出决定的服务相关联。 如果要在消费组件中做出这些决定,请使用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();
    }
  }
}

您还可以使用强类型元数据,方法是在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>>>

解释正确是指:

  • 所有的实现,

  • 工厂,return

  • 寿命控制

  • ITask服务



关系类型和容器独立

基于标准.NET类型的Autofac中的自定义关系类型不会强制您将应用程序更紧密地绑定到Autofac。 它们为您提供容器配置的编程模型,与您编写其他组件的方式一致(而不必知道很多特定的容器扩展点和API,也可能集中在您的配置中)。

例如,您仍然可以在核心模型中创建一个自定义ITaskFactory,但是如果需要,则提供基于Func <Owned <ITask >>的AutofacTaskFactory实现。

请注意,某些关系基于Autofac中的类型(例如,IIndex <X,B>)。 使用这些关系类型,至少要引用Autofac,即使您选择使用不同的IoC容器来实际解析服务。


版权声明:本站所有教程均为本站原创或翻译,转载请注明出处,请尊重他人劳动果实。请记住本站地址:www.yuanjiaocheng.net (猿教程) 作者:卿文刚
本文标题: C#环境
本文地址:http://www.yuanjiaocheng.net/Autofac/resolve-relationships.html