web-dev-qa-db-ja.com

ポインターを使用して読み取り専用フィールドを変更できますか?しかし、なぜ?

私は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オブジェクトを保護するメカニズムがあります(const C++のreadonlyとほぼ同じですが、const C#では)、ポインタを介してconst値を任意に変更する十分な理由がないためです。

さらに調べてみると、C++のC++の低レベルのconstポインターに相当するものはなく、次のようになります。

readonly int* p

c#の場合は1つあります。

その場合、pはポイントされたオブジェクトのみを読み取ることができ、それに書き込むことはできません。

constオブジェクトの場合、C#アドレスを取得する試みを禁止しました。

C++では、constを変更しようとすると、コンパイルエラーまたは未定義の動作になります。 C#では、これを利用できる可能性があるかどうかわかりません。

だから私の質問は:

  1. この動作は本当に明確に定義されていますか? C#では知っていますが、C++にはUBのような概念はありません
  2. その場合、どのように使用すればよいですか?それとも使用しないのですか?

注:コメントセクション:C++でのconstのキャストキャストはこの質問には関係ありません。これは、オブジェクト自体がconstでない場合にのみ有効で、それ以外の場合はUBだからです。さらに、私は基本的にC#とコンパイル時の動作について話しています。

質問を完全に理解していない場合は、詳細を提供するように依頼できます。私はほとんどの人がこれを正しく理解できないことを発見しました、多分それはそれを明確にしていない私のせいです。

8
John Ding

unsafeキーワードが原因で、この予期しない動作が発生するだけだと言えるでしょう。残念ながら、その読み取り専用フィールドを変更するには、少なくとも2つの方法があります。これらの方法は、安全でなくてもかまいません。これらのメソッドの詳細については、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);
}

3つのメソッドはすべて、完全に明確に定義されたC#であり、ペストのように回避する必要があります。クラスの外部から発生する複雑なエイリアシングの問題があるコードのデバッグを想像してみてください。残念ながら、C#でエイリアシングの問題を解決するための満足のいく解決策はまだありません。

2
TamaMcGlinn