如何避免Castle Windsor的CollectionResolver的循环行为?

时间:2010-09-17 10:06:00

标签: c# dependency-injection inversion-of-control castle-windsor

我在申请中使用Castle Windsor 2.5。我有一个服务,它是一个复合体,充当实现相同接口的对象的分发器。

public interface IService { void DoStuff(string someArg); }

public class ConcreteService1 : IService {
    public void DoStuff(string someArg) { }
}

public class ConcreteService2 : IService {
    public void DoStuff(string someArg) { }
}

public class CompositeService : List<IService>, IService
{
    private readonly IService[] decoratedServices;

    CompositeService(params IService[] decoratedServices) {
        this.decoratedServices = decoratedServices;
    }

    public void DoStuff(string someArg) {
        foreach (var service in decoratedServices) {
            service.DoStuff(someArg);
        }
    }
}

我遇到的问题是使用如下所示的配置会导致Windsor报告“尝试解析依赖项时检测到一个循环。”

windsor.Register(
    Component
        .For<IService>()
        .ImplementedBy<CompositeService>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService1>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService2>()
);

这种循环行为似乎与可用于实现装饰器模式的标准(非基于集合)行为不一致,其中被解析的组件被忽略,并且使用实现相同接口的下一个注册组件代替。

所以我想知道的是,当Windsor解析CompositeService属性的IService服务时,是否有办法让Windsor排除decoratedServices组件。 decoratedServices属性应包含两个项:ConcreteService1的实例和ConcreteService2的实例。我想在没有明确指定依赖项的情况下这样做。

2 个答案:

答案 0 :(得分:0)

我知道Unity,但我相信你的问题是一般的,并且与所有DI系统有关。 大多数DI工具都可以明确命名您的依赖项。如果要实现相同的接口,这将特别有用。

所以我相信你的问题不是那么多循环(如果有这样一个词)的依赖,而是它的多重性。因此,我会明确地将依赖项命名如下:

windsor.Register( 
Component 
    .For<IService>() 
    .ImplementedBy<CompositeService>("CompositeService"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService1>("ConcreteService1"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService2>("ConcreteService2") 

);

但是我看不出你的DI框架如何处理你的CompositeService的构造函数。 params是一个艰难的问题。

以下是我要做的事情:

1)我删除params 2)我将构造函数更改为IEnumerable<IService> decoratedServices 3)我创建另一个类并实现IEnumerable,这只是一个工厂将返回给我定义的类型列表,我使用它们的名称初始化。

答案 1 :(得分:0)

这是我目前的解决方法。我对Windsor的内部结构并不太熟悉,因此这个解决方案可能存在明显的错误,所以使用它需要您自担风险!

public class NonCyclicCollectionResolver : ISubDependencyResolver
{
    private readonly IKernel kernel;
    private readonly bool    allowEmptyCollections;

    public NonCyclicCollectionResolver(
        IKernel kernel,
        bool    allowEmptyCollections = false
    ) {
        this.kernel                = kernel;
        this.allowEmptyCollections = allowEmptyCollections;
    }

    public bool CanResolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        if (dependency.TargetType == null) return false;

        var itemType = dependency.TargetType.GetCompatibileArrayItemType();
        if (itemType == null) return false;

        if (!allowEmptyCollections)
        {
            return GetOtherHandlers(itemType, model.Name).Any();
        }

        return true;
    }

    public object Resolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        var itemType = dependency.TargetType.GetCompatibileArrayItemType();

        var handlers = GetOtherHandlers(itemType, model.Name);

        var resolved = handlers
            .Select(h => kernel.Resolve(h.ComponentModel.Name, itemType))
            .ToArray();

        var components = Array.CreateInstance(itemType, resolved.Length);
        resolved.CopyTo(components, 0);
        return components;
    }

    private IEnumerable<IHandler> GetOtherHandlers(
        Type   serviceType,
        string thisComponentName
    ) {
        return kernel
            .GetHandlers(serviceType)
            .Where(h => h.ComponentModel.Name != thisComponentName);
    }
}