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


8

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

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

readonly int* p

C#の場合。

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

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

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

だから私の質問は:

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

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

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


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
サミュエルLiew

回答:


2

unsafeキーワードのせいで、この予期せぬ振る舞いしか得られないと言えるのではないでしょうか。残念ながら、その読み取り専用フィールドを変更するには、少なくとも2つの方法があります。これらの方法は、安全でなくてもかまいません。これらのメソッドの詳細については、Joe Duffyのブログ記事「読み取り専用フィールドは読み取り専用ではない場合」を参照してください

読み取り専用でない構造体でフィールドをオーバーラップすることにより:

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#でエイリアシングの問題を回避するための満足のいく解決策はまだありません。


1
これは私の質問に答えましたが、それでもC#で「低レベルの読み取り専用」が提供されない理由はわかりませんでした。受け入れられます。
John Ding
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.