具有循环依赖关系的静态字段的反射GetValue返回null

时间:2016-10-21 17:48:44

标签: c# reflection visual-studio-2015 static c#-6.0

注意:以下代码实际上可以正常工作,但显示了我自己的解决方案中失败的方案。有关详细信息,请参阅本文的底部。

使用这些课程:

public class MainType {
   public static readonly MainType One = new MainType();
   public static readonly MainType Two = SubType.Two;
}

public sealed class SubType : MainType {
   public new static readonly SubType Two = new SubType();
}

获取字段OneTwo

List<FieldInfo> fieldInfos = typeof(MainType)
   .GetFields(BindingFlags.Static | BindingFlags.Public)
   .Where(f => typeof(MainType).IsAssignableFrom(f.FieldType))
   .ToList();

最后,获取他们的价值观:

List<MainType> publicMainTypes = fieldInfos
   .Select(f => (MainType) f.GetValue(null))
   .ToList();

在LinqPad或带有上述代码的简单单元测试类中,一切正常。但是在我的解决方案中,我有一些想要处理这些字段的所有实例的单元测试,GetValue可以正常返回父类型的字段,但是假设父字段具有子类型的实例,他们总是给null! (如果这发生在这里,最终列表将是{ One, null }而不是{ One, Two }。)测试类与两种类型(每个都在他们自己的文件中)处于不同的项目中,但我暂时将一切公开。我已经删除了一个断点,并检查了我可以检查的所有内容,并且在Watch表达式中完成了相当于fieldInfos[1].GetValue(null)的事实,它确实返回null,尽管我的主类中有一行与上面MainType的第二个完全相同。

有什么问题?如何获取子类型字段的所有值?他们甚至可以在没有错误的情况下返回null吗?

理论上可能由于某种原因,由于通过反射访问,子类的类不是静态构造的,我试过

System.Runtime.CompilerServices.RuntimeHelpers
  .RunClassConstructor(typeof(SubType).TypeHandle);

在开始之前的顶部,但它没有帮助(SubType是我项目中的实际子类型。)

我会继续试图在一个简单的案例中重现这一点,但我暂时没有想法。

其他信息

经过一番摆弄后,代码开始工作了。现在它不再起作用了。我正在努力重现触发代码开始工作的内容。

注意:在Visual Studio 2015中使用C#6.0定位.Net 4.6.1。

问题再现可用

您可以通过下载此somewhat minimal working example of the problem at github来播放我的方案的工作(失败)修剪版本。

调试单元测试。发生异常时,请执行步骤,直至到达GlossaryHelper.cs的第20行,并在GetGlossaryMembers选项卡中看到Locals的返回值。您可以看到索引3到12为空。

2 个答案:

答案 0 :(得分:14)

<强>问题

这个问题与Reflection无关,而是两个静态字段初始值设定项之间的循环依赖关系及其执行顺序。

请考虑以下代码段:

var b = MainType.Two;
var a = SubType.Two;
Debug.Assert(a == b); // Success

现在让我们交换前两行:

var a = SubType.Two;
var b = MainType.Two;
Debug.Assert(a == b); // Fail! b == null

那么这里发生了什么?我们来看看:

  1. 代码首次尝试访问SubType.Two静态字段。
  2. 静态初始化程序触发并执行SubType的构造函数。
  3. 由于SubType继承自MainTypeMainType构造函数也会执行并触发MainType静态初始化。
  4. MainType.Two字段静态初始值设定项正在尝试访问SubType.Two。由于静态初始化程序只执行一次,而SubType.Two的执行程序已经执行(好吧,不是真的,它当前正在执行,但被认为是),它只返回当前字段值({ {1}}然后存储在null中,并将通过该字段的进一步访问请求返回。
  5. 简而言之,这种设计的正确工作实际上取决于外部访问字段的顺序,因此它有时可行,有时不起作用并不奇怪。不幸的是,这是你无法控制的。

    如何修复

    如果可能,请避免此类静态字段依赖性。请改用静态只读属性。它们为您提供完全控制,还允许您消除字段重复(目前您有2个不同的字段,其中包含一个相同的值)。

    这是没有这些问题的等效设计(使用C#6.0):

    MainType.Two

    当然,这需要更改您的反射代码以使用public class MainType { public static MainType One { get; } = new MainType(); public static MainType Two => SubType.Two; } public sealed class SubType : MainType { public new static SubType Two { get; } = new SubType(); } 代替GetProperties,但我认为这是值得的。

    更新:解决此问题的另一种方法是将静态字段移动到嵌套的抽象容器类:

    GetFields

    现在两个测试都成功完成:

    public class MainType
    {
        public abstract class Fields
        {
            public static readonly MainType One = new MainType();
            public static readonly MainType Two = SubType.Fields.Two;
        }
    }
    
    public sealed class SubType : MainType
    {
        public new abstract class Fields : MainType.Fields
        {
            public new static readonly SubType Two = new SubType();
        }
    }
    

    var a = SubType.Fields.Two;
    var b = MainType.Fields.Two;
    Debug.Assert(a == b); // Success
    

    这是因为容器类除了包含在内之外与静态字段类型无关,因此它们的静态初始化是独立的。此外,虽然它们使用继承,但它们永远不会被实例化(因为是var b = MainType.Fields.Two; var a = SubType.Fields.Two; Debug.Assert(a == b); // Success ),因此没有由基础构造函数调用引起的副作用。

答案 1 :(得分:0)

我遇到了类似的问题。问题是我实现了一个类的静态字段,并通过反射尝试使用它的值。它在我的调试解决方案中运行良好,但在我的生产环境中无效。 问题是Release配置中的编译器发现从不使用此静态方法并删除无法访问的代码。 要解决此问题,您应该删除Optimize code flag。

相关问题