Unity Decorator Extension在多个实现中失败

本文关键字:实现 失败 Decorator Extension Unity | 更新日期: 2023-09-27 18:21:09

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

我为UnityContainer创建了一个容器扩展,使我能够轻松地在容器中注册decorator类。这是我目前拥有的实现,与本文中的实现几乎相同:

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);
}

这次测试应该让我的意图变得清晰。我想要singleton类,但对于IAnotherInterfaceIAny<string>,第一次调用Resolve会导致后续每次调用都返回相同的内容。因此,我得到了一个例外:

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

在这条线上:

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

我不知道在这里该做什么。我不得不在我们的项目中暂时禁用单身汉,这样才能按预期工作。我想要的是Base<string>实例是一个sintleton,但当我请求IAny<string>时,它会创建一个带有相同基础的NEW实例。

这仍然使用.Net 4.0,所以我在这里使用Unity 2.1(在这种情况下应该无关紧要)。

Unity Decorator Extension在多个实现中失败

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

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

这篇文章提出了解决这个问题的建议,在我们这边效果很好。

我不确定这是否是你想要的,但我认为这在你的测试中的特定情况下是可行的:

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

你不需要延期。