【问题标题】:Unity BuildUp fails for singletonUnity BuildUp 单例失败
【发布时间】:2011-05-13 10:52:35
【问题描述】:

我在使用 Unity 框架时遇到以下问题。

我们的项目中有一个单例类。它们有一些属性应该由 Unity 容器注入。代码如下:

private static SomeClass m_Instance;

private SomeClass()
{ }

public static SomeClass Instance
{
    get
    {
        if (m_Instance == null)
        {
            lock (typeof(SomeClass))
            {
                if (m_Instance == null)
                {
                    IUnityContainer container = ContainerAccessor.GetContainer();
                    m_Instance = container.BuildUp<SomeClass>(new SomeClass());
                }
            }
        }

        return m_Instance;
    }
}

此代码失败并出现以下异常:无法构造类型 SomeClass。您必须配置容器以提供此值。

我深入研究了 Unity 代码,发现问题是由方法 PreBuildUp 引起的,该方法调用 GuardTypeIsNonPrimitive,两者都在 Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy 类中定义。这是它的一段代码:

public override void PreBuildUp(IBuilderContext context)
{
    ...
    SelectedConstructor selectedConstructor = context.Policies.Get<IConstructorSelectorPolicy>(context.BuildKey, out list).SelectConstructor(context, list);
    GuardTypeIsNonPrimitive(context, selectedConstructor);
    ...
}

private static void GuardTypeIsNonPrimitive(IBuilderContext context, SelectedConstructor selectedConstructor)
{
    Type type = context.BuildKey.Type;
    if (!type.IsInterface && ((type == typeof(string)) || (selectedConstructor == null)))
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.TypeIsNotConstructable, new object[] { type.Name }));
    }
}

正如我们所见,Unity 试图为应该构建的类找到构造函数。由于为 SomeClass 定义的唯一构造函数是私有的,因此 Unity 什么也找不到,并将 null 传递给 GuardTypeIsNonPrimitive。而且这个方法会抛出异常。目前我已经为 SomeClass 定义了公共构造函数(只是为了证明这个概念),一切都很好。

问题:

  1. 更新:为什么 BuildUp 方法需要定义构造函数?

  2. 有什么办法可以解决这个问题吗? (删除单例不是一个选项)

【问题讨论】:

    标签: singleton unity-container unity2.0


    【解决方案1】:

    如何为您的SomeClass 使用ContainerControlledLifetimeManager(每个容器单例)。它不会是单例,但容器将始终返回相同的实例。

    您将调用container.Resolve&lt;SomeClass&gt;()而不是SomeClass.Instance

    【讨论】:

    • +1 是的,使用容器来容纳你的单身人士。这就是您拥有容器的原因之一。
    • 如果我错了,请纠正我,但您的建议似乎假设 SomeClass 已在容器中注册。但它不是,也不会是,这实际上是我们在这里尝试使用 BuildUp 方法的原因。
    • 那么你必须为容器提供一个构造函数。
    • @PVitt:这就是问题所在!对我来说,BuildUp 方法需要定义一个构造函数似乎是不正确的,因为它需要一个现有的对象作为参数。还是我错过了什么?
    • 啊,好的。但是您的问题是“为什么”Unity 要求对象具有构造函数还是“如何”解决这个问题?
    【解决方案2】:

    学习新东西永远不会太晚,两年多后我已经准备好回答我自己的问题了。

    长话短说:这是一个known bug,它已在 2.1.505.2 中修复。详情如下。

    该错误是在 2010 年 9 月针对 Unity 2.0 报告的,并且在 2012 年 8 月发布 2.1.505.2 之前一直存在于框架中。这解释了我们遇到它的原因,但不能解释为什么我们无法通过谷歌搜索错误报告...

    这是重现问题所需的代码。

    单例类的定义(注意这个类根本不包含任何依赖):

    public class SingletonClass
    {
        private static SingletonClass m_Instance;
    
        private SingletonClass()
        {
        }
    
        public static SingletonClass Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    m_Instance = new SingletonClass();
                }
    
                return m_Instance;
            }
        }
    }
    

    实际BuildUp调用:

    UnityContainer container = new UnityContainer();
    SingletonClass singleton = container.BuildUp(SingletonClass.Instance);
    

    直到版本 2.1.505.0,此代码抛出 InvalidOperationExceptionResolutionFailedException。从版本 2.1.505.2 开始,此代码可以正常工作(从我的角度来看,它的设计应该如此)。

    很高兴知道实际修复是通过重写我在问题中概述的代码来完成的。以下是Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy 对应部分现在的样子:

    public override void PreBuildUp(IBuilderContext context)
    {
        ...
        SelectedConstructor selectedCtor = selector.SelectConstructor(context, resolverPolicyDestination);
    
        GuardTypeIsNonPrimitive(context);
        ...
    }
    
    private static void GuardTypeIsNonPrimitive(IBuilderContext context)
    {
        var typeToBuild = context.BuildKey.Type;
        if (!typeToBuild.GetTypeInfo().IsInterface)
        {
            if (typeToBuild == typeof(string))
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.TypeIsNotConstructable,
                        typeToBuild.GetTypeInfo().Name));
            }
        }
    }
    

    这里最重要的部分是现在保护方法GuardTypeIsNonPrimitive 不考虑构造函数——只考虑类型本身。我认为这是问题的根源。

    事实上这是一个错误回答了帖子中的第一个问题。第二个呢?如何解决这个问题?如果您使用的是 Unity 2.1.505.2 及更高版本,则没有该问题,因此最受欢迎的选项是更新您的 Unity 版本。但是,如果您必须处理 2.1.505.0 及以下版本 - 有几种方法:

    1. 重构代码以摆脱单例,并在具有适当生命周期的容器中注册类型,正如Ladislav Mrnka 在此问题的另一个答案中所建议的那样。在我们的情况下这是不可能的,但有时它仍然可能是要走的路。

    2. 下载源代码并重新编译它们,将GuardTypeIsNonPrimitive 的实现更改为上面发布的版本。

    3. 实现您自己的注入,例如作为UnityContainer 类的扩展方法。一个例子可以在here 找到(链接本身已经失效,所以链接 Web Archive 版本)。

    一如既往,正确的选择取决于具体情况。

    【讨论】:

      猜你喜欢
      • 2011-01-04
      • 2017-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-12
      • 2021-07-16
      • 2018-10-03
      • 1970-01-01
      相关资源
      最近更新 更多