【问题标题】:Why can't I change Type.Delimiter using Reflection?为什么我不能使用反射更改 Type.Delimiter?
【发布时间】:2016-08-09 20:11:45
【问题描述】:

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

这是为什么?

【问题讨论】:

  • 不确定,反正这似乎是个可怕的主意。
  • 查看源代码,这两个字段的声明几乎相同但不完全相同,一个是只读的 = ".",另一个是只读的 = 另一个只读字面量。
  • 一个疯狂的猜测。由于 bool 是 struct 而 Type 是抽象类,反射可能会对该结构执行一些黑魔法。正如我所说..只是一个猜测
  • 这个问题应该叫做“为什么我不能使用反射更改静态只读字段”。这与具体的类无关。
  • @usr 但是我正在更改bool.TrueString,所以这并不完全正确

标签: c# .net reflection


【解决方案1】:

我认为您正在成为 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  

您可以非常清楚地看到,JITter 优化了 Type.Delimiter 字段访问,将其替换为文字值 '.'。 bool.TrueString 的静态字段访问似乎仍从实际字段加载。

【讨论】:

  • 这是因为jitting这段代码会执行静态ctor,然后将Delimiter刻录到x86代码中。委托版本不引用此方法中的分隔符。定界符仍被刻录到 x86 代码中,但稍后使用不同的方法。
  • @usr 我实际上通过测试 LINQPad 中的代码注意到该字段发生了变化,这促使我尝试创建一个委托来尝试强制 jit 在更改后查看字段值。老实说,这对我来说有点摸不着头脑,但我猜它成功了。
【解决方案2】:

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

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

发生这种情况的原因是,第一次调用您的方法时,JIT 编译器会判断是否可以将 static readonly 值作为常量直接烘焙到汇编代码中。如果可以,它可以。

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

您会发现,如果您使用可以硬编码到面向 CPU 的指令中的值类型,就会应用这种 JIT 优化。这包括 int、long 和 char 之类的内容,但不包括 DateTimes 之类的内容或字符串之类的引用类型

【讨论】:

    猜你喜欢
    • 2013-01-29
    • 1970-01-01
    • 2011-07-01
    • 1970-01-01
    • 2017-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多