Können Zeiger verwendet werden, um das schreibgeschützte Feld zu ändern? Aber warum?

8

Ich komme aus C ++ und finde ein ganz anderes Verhalten zwischen Zeigern in C ++ und C #.

Ich finde überraschenderweise, dass dieser Code kompiliert wird ... und sogar ... funktioniert perfekt.

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

Das macht mich sehr verwirrt, selbst in C ++ haben wir einen Mechanismus zum Schutz von constObjekten ( constin C ++ fast das gleiche wie readonlyin C #, nicht constin C #), weil wir nicht genug Grund haben, const-Werte willkürlich durch Zeiger zu ändern.

Wenn ich weiter untersuche, finde ich, dass es in C # kein Äquivalent zu C ++ 's konstanten Zeigern auf niedriger Ebene gibt , was ungefähr so ​​aussehen wird:

readonly int* p

in C #, wenn es eine hatte.

Dann kann p nur das Objekt lesen, auf das gezeigt wird, und kann nicht darauf schreiben.

Und für constObjekte verbot C # die Versuche, ihre Adresse abzurufen.

In C ++ ist jeder Versuch, das zu ändern, constentweder ein Kompilierungsfehler oder ein undefiniertes Verhalten. In C # weiß ich nicht, ob es eine Möglichkeit gibt, dass wir davon Gebrauch machen können.

Meine Frage lautet also:

  1. Ist dieses Verhalten wirklich gut definiert? Obwohl ich weiß, dass es in C # kein Konzept wie UB in C ++ gibt
  2. Wenn ja, wie soll ich es verwenden? Oder nie benutzen?

Hinweis: Im Kommentarbereich: Das Wegwerfen constin C ++ hat nichts mit dieser Frage zu tun, da es nur gültig ist, wenn das Objekt, auf das verwiesen wird, nicht vorhanden ist const, andernfalls ist es UB. Außerdem spreche ich im Grunde genommen über C # und das Verhalten beim Kompilieren.

Sie können mich bitten, weitere Details anzugeben, wenn Sie die Frage nicht vollständig verstehen. Ich fand, dass die meisten Leute dies nicht richtig verstehen können, vielleicht ist es meine Schuld, es nicht klar zu machen.

John Ding
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

Antworten:

2

Ich wünschte, ich könnte sagen, dass Sie dieses unerwartete Verhalten nur aufgrund des unsafeSchlüsselworts erhalten. Leider gibt es mindestens zwei weitere Möglichkeiten, um dieses schreibgeschützte Feld zu ändern, die nicht einmal unsicher sind. Weitere Informationen zu diesen Methoden finden Sie in Joe Duffys Blogpost "Wann ist ein schreibgeschütztes Feld nicht schreibgeschützt?" .

Durch Überlappen eines Feldes mit einer nicht schreibgeschützten Struktur:

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}");
    }
}

Und durch versehentliches Aliasing thismit einem ref-Parameter (dieser erfolgt von außen):

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);
}

Alle drei Methoden sind perfekt definierte C #, die Sie wie die Pest vermeiden sollten. Stellen Sie sich vor, Sie debuggen Code mit komplexen Aliasing-Problemen, die von außerhalb Ihrer Klasse stammen. Leider gibt es immer noch keine zufriedenstellende Lösung, um Aliasing-Probleme in C # zu stoppen.

TamaMcGlinn
quelle
1
Dies beantwortete meine Frage, gab mir jedoch immer noch nicht den Grund, warum "Low-Level-Readonly" in C # nicht angegeben ist. Akzeptiert jedoch.
John Ding