为什么我不能使用反射来改变Type.Delimiter?

5
如果我想要更改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 "."

为什么会这样呢?

1
不确定,似乎无论如何都是一个可怕的想法。 - itsme86
从源代码来看,这两个字段的声明几乎相同但又不完全相同,一个是只读的 =“。”,另一个是只读的 = 另一个只读字面量。 - pm100
一个猜测。由于bool是一个“struct”,而“Type”是一个抽象类,反射可能会对该结构执行一些黑魔法。就像我说的...只是一个猜测。 - lokusking
这个问题应该被称为“为什么我不能使用反射来改变静态只读字段”。这与特定的类无关。 - usr
@usr 但我正在更改 bool.TrueString,所以这并不完全正确。 - Matias Cicero
对于任何其他类,它的行为都是相同的,并且可以澄清这一点。 - usr
2个回答

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),而Type.Delimiter是值类型(char)。我可以想象这会触发不同的优化。
我确实查看了这段代码的反汇编结果:
        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  

您可以清楚地看到,JIT编译器通过将Type.Delimiter字段访问替换为文字值'.',优化了代码。对于bool.TrueString的静态字段访问仍然需要从实际字段加载。

1
这是因为JIT编译此代码会执行静态构造函数,然后将Delimiter烧入x86代码。委托版本不会从此方法引用Delimiter。Delimiter仍然被烧入x86代码,但在稍后的另一个方法中使用。 - usr
@usr 我实际上通过在LINQPad中测试代码来注意到字段的更改,这促使我尝试创建一个委托来强制jit在更改后看到字段值。老实说,这在我这方面有些盲目,但我想说它还是成功了。 - Kyle

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或字符串等引用类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接