2015-09-05 59 views
2

autofac documentation状态工作:Autofac枚举不与类型多个注册或模块

当Autofac被注入IEnumerable<ITask>类型的构造符参数也不会寻找供给IEnumerable<ITask>的成分。相反,容器会查找ITask的所有实现并注入所有实现。

但实际上,它会将每个注册类型添加为已注册的次数。所以,如果你注册类两倍如下:

builder.RegisterType<A>(); 
builder.RegisterType<A>(); 

然后你得到两个项目枚举!在单个模块中,这不是问题,因为您显然只需注册一次您的类型。但是如果你有一个由多个模块注册的共享模块(典型的钻石模块依赖关系图),那么你可以在枚举中获得尽可能多的项目,因为共享模块已被其他人注册...

它是一个错误吗? 有没有办法强制枚举为每个实现提供单个项目,正如文档中所述,不再有?

回答

2

您必须看到Autofac作为一种Dictionary<IComponentRegistration>。 A 注册可以绑定到类型或委托或其他具有配置行为的其他位置。

通过注册登记两次类型你将有两个不同的IComponentRegistration,你的情况是相似。如果你看看以下的差异,你可以看到你将有两个不同的注册,它们使用相同的Type但具有不同的配置。

builder.RegisterType<A>().WithConstrusctorArgument("param1", "x"); 
builder.RegisterType<A>().WithConstrusctorArgument("param1", "y"); 

在这种情况下解决IEnumerable<A>会给你的A两个不同的实例,它有一定道理。

有没有什么办法来强制枚举每个实现

,我不会建议这样做,提供一个单一的项目。重构你的应用程序可能会更好,不会允许这种情况,如果你需要的话,你可以删除很多伟大的东西在Autofac。 如果您有一个Type可能注册在多个模块中,则在主模块中注册Type模块可能有意义。


顺便说一句,如果你真的想这样做,你将不得不找到一种方法来区分你的容器的所有注册。然后,您可以通过实施您自己的IRegistrationSource来覆盖IEnumerable<>的默认实现,下面的示例将为您提供每种类型的一次注册。

class CustomEnumerableRegistrationSource : IRegistrationSource 
{ 
    public Boolean IsAdapterForIndividualComponents 
    { 
     get 
     { 
      return false; 
     } 
    } 

    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
    { 
     IServiceWithType typedService = service as IServiceWithType; 

     if (typedService == null) 
     { 
      return Enumerable.Empty<IComponentRegistration>(); 
     } 
     if (!(typedService.ServiceType.IsGenericType && typedService.ServiceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))) 
     { 
      return Enumerable.Empty<IComponentRegistration>(); 
     } 

     Type elementType = typedService.ServiceType.GetGenericArguments()[0]; 

     Service elementService = typedService.ChangeType(elementType); 

     Type collectionType = typeof(List<>).MakeGenericType(elementType); 

     IComponentRegistration registration = RegistrationBuilder.ForDelegate(collectionType, (c, p) => 
     { 
      IEnumerable<IComponentRegistration> registrations = c.ComponentRegistry.RegistrationsFor(elementService); 

      IEnumerable<Object> elements = registrations.Select(cr => c.ResolveComponent(cr, p)); 

      // get distinct elements by type 
      Array array = elements.GroupBy(o => o.GetType()).Select(o => o.First()).ToArray(); 

      Array array2 = Array.CreateInstance(elementType, array.Length); 
      array.CopyTo(array2, 0); 

      Object collection = Activator.CreateInstance(collectionType, new Object[] { array2 }); 

      return collection; 
     }).As(service) 
      .CreateRegistration(); 

     return new IComponentRegistration[] { registration }; 
    } 
} 

而且你可以使用它像这样:

ContainerBuilder builder = new ContainerBuilder(); 

builder.RegisterType<A1>().As<IA>(); 
builder.RegisterType<A1>().As<IA>(); 
builder.RegisterType<A2>().As<IA>(); 

builder.RegisterSource(new CustomEnumerableRegistrationSource()); 

IContainer container = builder.Build(); 

IEnumerable<IA> services = container.Resolve<IEnumerable<IA>>(); 
Console.WriteLine(services.Count()); 

这个注册是原始CollectionRegistrationSource的简化版本。请查看CollectionRegistrationSource.cs的源代码以获取更完整的注册版本。

+1

谢谢!我了解现场背后的困境。我不知道类型的注册不是幂等的。不知何故,你是对的,如果你想在类型和实例之间有一个唯一的注册行为,这是有道理的。因此,即使多个模块依赖于它,模块也只能被注册一次。 *所以拇指规则:从不在另一个模块中注册模块!保持一个平坦的模块模型。只在外部例程中注册它们,创建容器* – jeromerg