Os ponteiros podem ser usados ​​para modificar o campo somente leitura? Mas por que?

8

Eu venho de C ++ e encontro um comportamento muito diferente entre ponteiros em C ++ e C #.

Surpreendentemente, acho que esse código compila ... E até ... Funciona perfeitamente.

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

Isso me deixa muito confuso, mesmo em C ++, temos um mecanismo para proteger constobjetos ( constem C ++ quase a mesma coisa que readonlyem C #, não constem C #), porque não temos motivos suficientes para modificar arbitrariamente os valores const através de ponteiros.

Explorando mais, acho que não há equivalente aos ponteiros const de baixo nível do C ++ em C #, que serão algo como:

readonly int* p

em c # se tivesse um.

Então p só pode ler o objeto apontado e não pode gravar nele.

E para constobjetos, o C # proibiu as tentativas de recuperar seu endereço.

No C ++, qualquer tentativa de modificar o consterro de compilação ou o comportamento indefinido. Em C #, não sei se existe alguma possibilidade de podermos fazer uso disso.

Então, minha pergunta é:

  1. Esse comportamento é realmente bem definido? Embora eu saiba em C #, não há conceito como UB em C ++
  2. Se sim, como devo usá-lo? Ou nunca usá-lo?

Nota: Na seção de comentários: jogar fora constem C ++ não está relacionado a esta pergunta, porque é válido apenas quando o objeto apontado em si não é const, caso contrário, é UB. Além disso, estou basicamente falando sobre C # e compilar o comportamento do tempo.

Você pode solicitar que eu forneça mais detalhes se não entender completamente a pergunta. Descobri que a maioria das pessoas não consegue entender isso corretamente, talvez seja minha culpa não esclarecer.

John Ding
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Samuel Liew

Respostas:

2

Eu gostaria de poder dizer que você só recebe esse comportamento inesperado por causa da unsafepalavra - chave. Infelizmente, existem pelo menos mais duas maneiras de alterar esse campo somente de leitura, que nem exigem segurança. Você pode encontrar mais informações sobre esses métodos no post de Joe Duffy 'When is a readonly field not readonly' .

Sobrepondo um campo a uma estrutura não somente leitura:

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

E acidentalmente alternando thiscom um parâmetro ref (este é feito de fora):

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

Todos os três métodos são C # perfeitamente bem definidos que você deve evitar como uma praga. Imagine o código de depuração com problemas complexos de aliasing decorrentes de fora da sua classe. Infelizmente, ainda não existe uma solução satisfatória para interromper os problemas de alias no C #.

TamaMcGlinn
fonte
1
Isso respondeu à minha pergunta, mas ainda assim não me deu a razão pela qual "somente leitura de baixo nível" não é fornecida em C #. Aceito, no entanto.
John Ding