【问题标题】:Unity Decorator Extension fails with multiple implementationsUnity 装饰器扩展因多个实现而失败
【发布时间】:2013-07-02 22:55:22
【问题描述】:

我已经为这个问题苦苦挣扎了几天,但我仍然不知道如何解决它。

我为 Unity Container 创建了一个容器扩展,使我能够轻松地在容器中注册装饰器类。这是我目前的实现,和this文章中的几乎一模一样:

public class DecoratorExtension : UnityContainerExtension
{
    private int m_order;
    private Dictionary<Type, IList<DecoratorRegistration>> m_typeStacks;

    protected override void Initialize()
    {
        m_typeStacks = new Dictionary<Type, IList<DecoratorRegistration>>();
        Context.Registering += AddRegistration;
        Context.Strategies.Add(new DecoratorBuildStrategy(m_typeStacks), UnityBuildStage.PreCreation);
    }

    private void AddRegistration(object _sender, RegisterEventArgs _e)
    {
        if (_e.TypeFrom == null || !_e.TypeFrom.IsInterface)
            return;

        GetStack(_e.TypeFrom)
            .Add(new DecoratorRegistration {Order = m_order++, Type = _e.TypeTo});
    }

    private IList<DecoratorRegistration> GetStack(Type _type)
    {
        if (!m_typeStacks.ContainsKey(_type))
            m_typeStacks.Add(_type, new List<DecoratorRegistration>());

        return m_typeStacks[_type];
    }
}

这样做是为每种类型使用一个列表,存储同一目标类型的所有类型注册,以便在调用 Resolve 时使用此构建策略重新组装它:

internal class DecoratorBuildStrategy : BuilderStrategy
{
    private readonly Dictionary<Type, IList<DecoratorRegistration>> m_typeStacks;

    internal DecoratorBuildStrategy(Dictionary<Type, IList<DecoratorRegistration>> _typeStacks)
    {
        m_typeStacks = _typeStacks;
    }

    public override void PreBuildUp(IBuilderContext _context)
    {
        var key = _context.OriginalBuildKey;
        if (_context.GetOverriddenResolver(key.Type) != null)
            return;

        // Only interfaces can use decorators.
        if (!key.Type.IsInterface)
            return;
        
        // Gets the list of types required to build the 'decorated' instance.
        // The list is reversed so that the least dependent types are built first.
        var decoratorTypes = GetDecoratorTypes(key.Type).Reverse().ToList();
        if (!decoratorTypes.Any())
            return;

        object value = null;
        foreach (var type in decoratorTypes)
        {
            Type typeToBuild = type;
            if (typeToBuild.IsGenericTypeDefinition)
            {
                Type[] genericArgumentTypes = key.Type.GetGenericArguments();
                typeToBuild = typeToBuild.MakeGenericType(genericArgumentTypes);
            }

            value = _context.NewBuildUp(new NamedTypeBuildKey(typeToBuild, key.Name));

            // An Override is created so that in the next BuildUp the already 
            // built object gets used instead of doing the BuildUp again and 
            // entering an infinite loop
            _context.AddResolverOverrides(new DependencyOverride(key.Type, value));
        }

        _context.Existing = value;
        _context.BuildComplete = true;
    }

    private IEnumerable<Type> GetDecoratorTypes(Type _type)
    {
        var typeList = m_typeStacks.GetValueOrDefault(_type) ?? new List<DecoratorRegistration>(0);
        if (!_type.IsGenericType)
            return typeList.Select(_reg => _reg.Type);

        // If the type is a generic type, we need to get all open generic registrations
        // alongside the closed ones
        var openGenericList = m_typeStacks
                .GetValueOrDefault(_type.GetGenericTypeDefinition()) ?? 
                new List<DecoratorRegistration>(0);

        // The final result is an ordered concatenation of the closed and open registrations 
        // that should be used for the type
        return typeList
            .Concat(openGenericList)
            .OrderBy(_registration => _registration.Order)
            .Select(_reg => _reg.Type);
    }
}

这是使用 DecoratorRegistration 模型的地方。它只是一对表示注册顺序的 type/int。我创建它是为了能够正确混合打开和关闭的通用注册:

internal struct DecoratorRegistration
{
    public int Order { get; set; }
    public Type Type { get; set; }
}

这在很大程度上创造了奇迹。当我有一个实现两个接口的类时,问题就开始了,一个是装饰的,一个没有装饰。

这是我正在尝试制作的当前测试用例:

private interface IAny<T> {}
private interface IAnotherInterface {}
private class Base<T> : IAnotherInterface, IAny<T> {}   
private class Decorator1<T> : IAny<T>
{
    internal readonly IAny<T> Decorated;

    public Decorator1(IAny<T> _decorated)
    {
        Decorated = _decorated;
    }
}

[TestMethod]
public void DecoratorExtensionDoesNotInterfereWithNormalRegistrations()
{
    // Arrange
    var container = new UnityContainer()
        .AddNewExtension<DecoratorExtension>()
        .RegisterType<Base<string>>(new ContainerControlledLifetimeManager())
        .RegisterType<IAny<string>, Decorator1<string>>()
        .RegisterType<IAny<string>, Base<string>>()
        .RegisterType<IAnotherInterface, Base<string>>();

    // Act
    var decorated = container.Resolve<IAny<string>>();
    var normal = container.Resolve<IAnotherInterface>();
    var anotherDecorated = container.Resolve<IAny<string>>();
    var anotherNormal = container.Resolve<IAnotherInterface>();

    // Assert
    Assert.IsInstanceOfType(normal, typeof (IAnotherInterface));
    Assert.IsInstanceOfType(decorated, typeof (Decorator1<string>));
    Assert.AreSame(normal, anotherNormal);
    Assert.AreSame(decorated, anotherDecorated);
}

这个测试应该清楚我的意图。我想要单例类,但是对于 IAnotherInterfaceIAny&lt;string&gt; 的第一次调用 Resolve 会导致每个后续调用都返回相同的内容。因此,我得到了一个例外:

System.InvalidCastException: Unable to cast object of type 'Decorator1`1[System.String]' to type 'IAnotherInterface'.

在这一行:

    var normal = container.Resolve<IAnotherInterface>();

我不确定在这里做什么。我不得不在我们的项目中暂时禁用单例,以便它可以按预期工作。我想要的是 Base&lt;string&gt; 实例是一个 sintleton,但是当我请求 IAny&lt;string&gt; 时,它会创建一个新的实例,并装饰了相同的基础。

这仍然使用 .Net 4.0,所以我在这里坚持使用 Unity 2.1(不过在这种情况下应该没关系)。

【问题讨论】:

  • 您是否考虑过切换到不同的框架?
  • @Steven 嘿,Steven,我真的期待你的评论,你并没有让 xD 失望。是的,我知道有些容器开箱即用,例如 Windsor 或您的 Simple Injector,但我们这里有一个非常大的项目,有些东西在某些地方与容器相关联(注入不是本机像网络表单和其他东西一样受支持),所以如果完全不可能完成这项工作,我只会走这条路。例如,我可以将实现拆分为多个类,但这是最后的解决方案,因为它会显着影响代码库。
  • @Steven 另一个原因是我们还有一个定制设计的企业库应用程序块。它实际上是在我们开始使用 IoC 之前创建的,并且非常容易将它与 unity 集成(已经实现了一个扩展,可以在容器中自动加载应用程序块)。我必须实现适配器逻辑才能将其与其他容器集成。其实做到这一点并不难,只是另一个使事情复杂化的方面。
  • 很高兴我达到了您的期望 :-) 我不认为 Windsor 对装饰器的支持比 Unity 更好。 Autofac 有更好的支持,但是在应用泛型装饰器时,Simple Injector 是这里的王者。它原生地理解泛型类型约束,并轻松地允许您根据静态类型信息有条件地应用装饰器。

标签: c# .net-4.0 dependency-injection unity-container decorator


【解决方案1】:

我已经有一段时间没有解决这个问题了,所以我认为最好在这里复制我从 EntLib 团队的 Randy Levy 那里得到的答案。

它基本上归结为我用来注册装饰器实例的构建密钥。在我的代码中,实例实际上是用基类类型注册的,而我需要用实际的装饰器类型来注册它。

This post 提供了针对该问题的建议解决方法,这对我们来说非常有效。

【讨论】:

    【解决方案2】:

    我不确定这是否是您要查找的内容,但我认为这在您的测试中的特定情况下可以解决问题:

    container.RegisterType<IAny<string>, Base<string>>(
        new ContainerControlledLifetimeManager(), "Inner");
    
    container.RegisterType<IAny<string>, Decorator1<string>>(
        new InjectionConstructor(
            new ResolvedParameter(typeof(IAny<string>), "Inner")));
    
    container.Register<IAnotherInterface>(new InjectionFactory(
        c => c.Resolve<IAny<string>>("Inner")));
    

    你不需要那个扩展。

    【讨论】:

    • 是的,这是在 Unity 上开箱即用的装饰器的好方法,但显然这很麻烦。我目前在一个班级中有 5 个链式装饰器。注册的代码将是荒谬的不可读且很难维护。还有另一个问题是必须知道每个构造函数,这又存在维护问题。我认为在我的情况下,我会认为这是这个问题的绝对最后解决方案:(。考虑到我在问题中提出的问题,这是一个完全有效的答案。
    猜你喜欢
    • 1970-01-01
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多