注意:以下代码实际上可以正常工作,但显示了我自己的解决方案中失败的方案。有关详细信息,请参阅本文的底部。
使用这些课程:
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 }
。)测试类与两种类型(每个都在他们自己的文件中)处于不同的项目中,但我暂时将一切公开。我已经删除了一个断点,并检查了我可以检查的所有内容,并且在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为空。
答案 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
那么这里发生了什么?我们来看看:
SubType.Two
静态字段。 SubType
的构造函数。 SubType
继承自MainType
,MainType
构造函数也会执行并触发MainType
静态初始化。 MainType.Two
字段静态初始值设定项正在尝试访问SubType.Two
。由于静态初始化程序只执行一次,而SubType.Two
的执行程序已经执行(好吧,不是真的,它当前正在执行,但被认为是),它只返回当前字段值({ {1}}然后存储在null
中,并将通过该字段的进一步访问请求返回。简而言之,这种设计的正确工作实际上取决于外部访问字段的顺序,因此它有时可行,有时不起作用并不奇怪。不幸的是,这是你无法控制的。
如何修复
如果可能,请避免此类静态字段依赖性。请改用静态只读属性。它们为您提供完全控制,还允许您消除字段重复(目前您有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。