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

Joh*_*ing 8 c# c++ pointers readonly-attribute language-lawyer

我来自 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}");
    }
}
Run Code Online (Sandbox Code Playgroud)

这让我很困惑,即使在 C++ 中我们也有保护const对象的机制(const在 C++ 中与readonly在 C# 中几乎相同,而不是const在 C# 中),因为我们没有足够的理由通过指针随意修改 const 值。

进一步探索,我发现在 C# 中没有等效于 C++ 的低级 const 指针,它类似于:

readonly int* p
Run Code Online (Sandbox Code Playgroud)

在 C# 中,如果有的话。

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

对于const对象,C#禁止尝试检索其地址。

在 C++ 中,任何修改 的尝试const要么是编译错误,要么是未定义的行为。在 C# 中,我不知道我们是否有可能利用它。

所以我的问题是:

  1. 这种行为真的很好定义吗?虽然我知道在 C# 中没有像 C++ 中的 UB 这样的概念
  2. 如果是这样,我应该如何使用它?还是从不使用?

注意:在评论部分:const在 C++ 中抛弃与这个问题无关,因为只有当指向的对象本身不是时才有效const,否则它是 UB。另外,我基本上是在谈论 C# 和编译时行为。

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

Tam*_*inn 2

I wish I could say that you only get this unexpected behaviour because of the unsafe keyword. Unfortunately, there are at least two more ways to change that readonly field, that don't even require unsafe. You can find more about these methods in Joe Duffy's blogpost 'When is a readonly field not readonly'.

By overlapping a field with a non-readonly struct:

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}");
    }
}
Run Code Online (Sandbox Code Playgroud)

And by accidentally aliasing this with a ref parameter (this one is done from outside):

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);
}
Run Code Online (Sandbox Code Playgroud)

All three methods are perfectly well-defined C# that you should avoid like the plague. Just imagine debugging code with complex aliasing problems stemming from outside your class. Unfortunately, there still isn't any satisfactory solution to stop aliasing problems in C#.