¿Se pueden utilizar punteros para modificar el campo de solo lectura? ¿Pero por qué?

8

Vengo de C ++ y encuentro un comportamiento muy diferente entre los punteros en C ++ y C #.

Sorprendentemente encuentro que este código compila ... E incluso ... Funciona perfectamente.

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

Esto me deja muy perplejo, incluso en C ++ tenemos un mecanismo para proteger constobjetos ( consten C ++ casi lo mismo que readonlyen C #, no consten C #), porque no tenemos suficientes razones para modificar arbitrariamente los valores const a través de punteros.

Al explorar más, encuentro que no hay equivalente a los punteros constantes de bajo nivel de C ++ en C #, que será algo así como:

readonly int* p

en C # si tuviera uno.

Entonces p solo puede leer el objeto señalado y no puede escribirle.

Y para los constobjetos, C # prohibió los intentos de recuperar su dirección.

En C ++, cualquier intento de modificar el const es un error de compilación o un Comportamiento indefinido. En C #, no sé si existe alguna posibilidad de que podamos hacer uso de esto.

Entonces mi pregunta es:

  1. ¿Está este comportamiento realmente bien definido? Aunque sé que en C # no hay un concepto como UB en C ++
  2. Si es así, ¿cómo debo usarlo? ¿O nunca lo usas?

Nota: En la sección de comentarios: el rechazo consten C ++ no está relacionado con esta pregunta, porque es válido solo cuando el objeto señalado en sí no lo está const; de lo contrario, es UB. Además, básicamente estoy hablando de C # y el comportamiento del tiempo de compilación.

Puede pedirme que proporcione más detalles si no comprende completamente la pregunta. Descubrí que la mayoría de la gente no puede entender esto correctamente, tal vez es mi culpa no dejarlo claro.

John Ding
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Samuel Liew

Respuestas:

2

Me gustaría poder decir que solo obtienes este comportamiento inesperado debido a la unsafepalabra clave. Desafortunadamente, hay al menos dos formas más de cambiar ese campo de solo lectura, que ni siquiera requieren inseguridad. Puede encontrar más información sobre estos métodos en la publicación de blog de Joe Duffy 'Cuando es un campo de solo lectura no solo de lectura' .

Al superponer un campo con una estructura no de solo lectura:

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

Y al alias accidentalmente thiscon un parámetro ref (este se hace desde afuera):

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

Los tres métodos están perfectamente definidos en C # que debes evitar como la peste. Solo imagine el código de depuración con problemas complejos de alias derivados de fuera de su clase. Desafortunadamente, todavía no hay una solución satisfactoria para detener los problemas de alias en C #.

TamaMcGlinn
fuente
1
Esto respondió a mi pregunta, sin embargo, aún no me dio la razón por la cual "solo lectura de bajo nivel" no se proporciona en C #. Aceptado, sin embargo.
John Ding