为什么我不能使用Reflection更改Type.Delimiter?

时间:2016-08-09 20:11:46

标签: c# .net reflection

如果我要更改bool.TrueString的值,我会使用反射:

typeof(bool).GetField("TrueString", BindingFlags.Public | BindingFlags.Static).SetValue(null, "Yes");
Console.WriteLine(bool.TrueString); // Outputs "Yes"

但是,我无法更改Type.Delimiter

的值
typeof(Type).GetField("Delimiter", BindingFlags.Public | BindingFlags.Static).SetValue(null, '-');
Console.WriteLine(Type.Delimiter); // Outputs "."

为什么会这样?

2 个答案:

答案 0 :(得分:4)

我认为你正在成为JIT正在进行的优化的牺牲品。您实际上可以更改该字段的值,但由于某种原因,该更改的结果将不会立即可见。我设法通过做一些愚蠢的事情来解决这个问题:

typeof(Type).GetField("Delimiter", BindingFlags.Public | BindingFlags.Static).SetValue(null, '-');
Func<char> getDelimiter = () => Type.Delimiter;
Console.WriteLine( getDelimiter() );

此代码可靠地显示了该字段的更新值。我不能说我非常惊讶;该字段被声明为只读,因此JITter可以在访问该字段时使用该假设。你正在做一些顽皮和邪恶的事情,应该没有任何期望能够以理智的方式工作。

现在,至于为什么在修改bool.TrueString字段时没有出现这种情况,我最好的猜测是由于bool.TrueString是引用类型(string)而{ {1}}是值类型(Type.Delimiter)。我可以想象这触发了不同的优化。

我确实看过这段代码的反汇编:

char

您可以清楚地看到JITter通过将其替换为文字值 Console.WriteLine( bool.TrueString ); 006F2E53 8B 0D B8 10 40 03 mov ecx,dword ptr ds:[34010B8h] 006F2E59 E8 52 A6 77 54 call 54E6D4B0 Console.WriteLine(Type.Delimiter); 006F2E5E B9 2E 00 00 00 mov ecx,2Eh 006F2E63 E8 B0 FA E0 54 call 55502918 来优化Type.Delimiter字段访问权限。 '.'的静态字段访问仍然似乎从实际字段加载。

答案 1 :(得分:1)

区别在于bool.TrueString是引用类型,Type.Delimiter是值类型。对这两个属性尝试相同的代码,您将看到相同的行为:

public class A
{
    public static readonly string S = "S";
    public static readonly char C = 'C';
}

发生这种情况的原因是,第一次调用方法时,JIT编译器会计算static readonly值是否可以作为常量直接添加到汇编代码中。如果可以,它确实如此。

如果您将访问该字段的代码提取到另一个方法,那么它的输出将取决于它的第一次运行是之前的还是之后的该字段的值是改变。由于您的代码都采用相同的方法,因此在更改字段值之前,它显然是JIT。

您会发现,如果您使用可以硬编码到面向CPU的指令中的值类型,则将应用此JIT优化。这包括intlongchar等内容,但不包括DateTime s或类似字符串的引用类型