注意:以下代码实际上可以正常工作,但显示了我自己的解决方案中失败的方案。有关更多信息,请参阅本文的底部。
有了这些课程:
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();
}
获取字段 One
和 Two
:
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 }
。)测试类与两种类型(每种类型都在自己的文件中)处于不同的项目中,但我暂时制作了 一切 上市。我已经删除了一个断点并检查了我可以检查的所有内容,并完成了相同的操作 fieldInfos[1].GetValue(null)
在一个Watch表达式中,它确实返回null,尽管我的主类中有一行与第二行完全相同 MainType
以上。
哪里不对?如何获取子类型字段的所有值?他们甚至可以在没有错误的情况下返回null?
根据理论,由于某些原因,由于通过反射访问,子类型的类不是静态构造的,我试过
System.Runtime.CompilerServices.RuntimeHelpers
.RunClassConstructor(typeof(SubType).TypeHandle);
在开始之前的顶部,但它没有帮助(在哪里 SubType
是我项目中的实际子类型)。
我会继续试图在一个简单的案例中重现这一点,但我暂时没有想法。
附加信息
在一堆摆弄之后,代码开始工作了。现在它不再起作用了。我正在努力复制触发代码开始工作的内容。
注意:在Visual Studio 2015中使用C#6.0定位.Net 4.6.1。
问题再现可用
您可以通过下载此操作来播放我的方案的工作(失败)修剪版本 github问题的一些最小的工作示例。
调试单元测试。当异常发生时,直到你到达GlossaryHelper.cs的第20行,并且可以看到返回值 GetGlossaryMembers
在里面 Locals
标签。您可以看到索引3到12为空。
问题
该问题与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
那么这里发生了什么?让我们来看看:
- 代码尝试访问
SubType.Two
静态场是第一次。
- 静态初始化程序触发并执行构造函数
SubType
。
- 以来
SubType
继承自 MainType
, MainType
构造函数也执行和触发 MainType
静态初始化。
- 该
MainType.Two
字段静态初始化程序正在尝试访问 SubType.Two
。由于静态初始化程序只执行一次,而一次执行 SubType.Two
已经执行了(好吧,不是真的,它当前正在执行,但被认为是),它只返回当前字段值(null
那一刻)然后存储在 MainType.Two
并将通过该字段的进一步访问请求返回。
简而言之,这种设计的正确工作实际上取决于外部访问字段的顺序,因此它有时可行,有时不起作用也就不足为奇了。不幸的是,这是你无法控制的。
怎么修
如果可能,请避免此类静态字段依赖性。使用静态只读 性能 代替。它们为您提供完全控制,还允许您消除字段重复(目前您有2个不同的字段,其中包含一个相同的值)。
这是没有这些问题的等效设计(使用C#6.0):
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
这是因为容器类除了包含在内之外与静态字段类型无关,因此它们的静态初始化是独立的。此外,虽然他们使用继承,但他们永远不会被实例化(因为存在 abstract
),因此没有基础构造函数调用引起的副作用。
我有类似的问题。问题是我实现了类的静态字段,并通过反射尝试使用它的值。它在我的调试解决方案中运行良好,但在我的生产环境中无效。
问题是Release配置中的编译器发现从不使用此静态方法并删除无法访问的代码。
要解决此问题,您应该删除Optimize code flag。