指针可以用来修改只读字段吗?但为什么?

Can pointers be used to modify readonly field? But why?

我来自 C++,发现 C++ 和 C# 中指针的行为非常不同。

我惊讶地发现这段代码可以编译...甚至...完美运行。

class C
{
    private readonly int x = 0;
    unsafe public void Edit()
    {
        fixed (int* p = &x)
        {
           *p = x + 1;
        }
        Console.WriteLine($"Value: {x}");
    }
}

这让我很疑惑,即使在C++中我们也有保护const对象的机制(C++中的const和C#中的readonly几乎是一样的,不是const in C#), 因为我们没有足够的理由通过指针任意修改const值。

进一步探索,我发现在 C# 中没有与 C++ 的 低级常量指针 等价的东西,类似于:

readonly int* p

在 C# 中,如果有的话。

那么p只能读指向的对象,不能写。

对于 const 个对象,C# 禁止尝试检索它们的地址。

在 C++ 中,任何修改 const 的尝试都是编译错误或未定义行为。在C#中,不知道有没有可能利用这个。

所以我的问题是:

  1. 这种行为真的很明确吗?虽然我知道在 C# 中没有像 C++ 中的 UB 这样的概念
  2. 如果可以,我该如何使用?或者从不使用它?

注:评论区:cast away const in C++ 与本题无关,因为只有指向的对象本身不是const才有效,否则就是UB。另外,我基本上是在谈论 C# 和编译时行为。

如果您不完全理解问题,可以要求我提供更多详细信息。我发现大多数人都不能正确理解这一点,也许是我没说清楚。

我希望我能说你只是因为 unsafe 关键字才得到这种意外行为。不幸的是,至少还有两种方法可以更改只读字段,甚至不需要不安全。您可以在 Joe Duffy 的博文 'When is a readonly field not readonly'.

中找到有关这些方法的更多信息

通过将字段与非只读结构重叠:

class C
{
    private readonly int x = 0;

    class other_C
    {
        public int x;
    }

    [StructLayout(LayoutKind.Explicit)]
    class Overlap_them
    {
        [FieldOffset(0)] public C actual;
        [FieldOffset(0)] public other_C sneaky;
    }

    public void Edit()
    {
        Overlap_them overlapper = new Overlap_them();
        overlapper.actual = this;
        overlapper.sneaky.x = 1;
        Console.WriteLine($"Value: {x}");
    }
}

并且不小心将 this 与 ref 参数别名化(这是从外部完成的):

class C
{
    private readonly int x = 0;

    public C(int X)
    {
        x = X;
    }

    public void Edit(ref C foo)
    {
        foo = new C(1);
        Console.WriteLine($"Value: {x}");
    }
}

private static void Main()
{
    var c = new C(0);
    c.Edit(ref c);
}

这三种方法都是完全定义良好的 C#,您应该像避免瘟疫一样避免使用它们。试想一下,调试代码时会出现来自 class 之外的复杂别名问题。不幸的是,仍然没有任何令人满意的解决方案来解决 C# 中的别名问题。