Partilhar via


Membros de instância apenas de leitura

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).

Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .

Questão do campeão: <https://github.com/dotnet/csharplang/issues/1710>

Resumo

Forneça uma maneira de especificar que membros de instância individuais em uma struct não modificam o estado, da mesma forma que readonly struct especifica que nenhum membro de instância modifica o estado.

Vale a pena notar que membro de instância somente leitura != membro de instância puro. Um membro de instância pura garante que nenhum estado será modificado. Um membro de instância readonly apenas garante que o estado da instância não será modificado.

Todos os membros de instância em um readonly struct podem ser considerados implicitamente membros de instância somente leitura membros de instância Explicit readonly declarados em structs não somente leitura se comportariam da mesma maneira. Por exemplo, ainda seriam criadas cópias ocultas se fosse chamado um membro de instância (na instância atual ou em um campo da instância) que não fosse 'readonly'.

Motivação

Antes do C# 8.0, os usuários tinham a capacidade de criar tipos de readonly struct que o compilador impõe que todos os campos sejam somente leitura (e, por extensão, que nenhum membro da instância modifique o estado). No entanto, há alguns cenários em que você tem uma API existente que expõe campos acessíveis ou que tem uma mistura de membros mutantes e não mutantes. Nessas circunstâncias, não se pode marcar o tipo como readonly (seria uma alteração disruptiva).

Isso normalmente não tem muito impacto, exceto no caso de in parâmetros. Com parâmetros in para estruturas não apenas para leitura, o compilador fará uma cópia do parâmetro para cada invocação de membro de instância, já que não pode garantir que a invocação não modifique o estado interno. Isso pode levar a uma infinidade de cópias e a um desempenho geral pior do que se você simplesmente tivesse passado a struct diretamente como valor. Para obter um exemplo, consulte este código em sharplab

Alguns outros cenários onde cópias ocultas podem ocorrer incluem campos static readonly e literais. Se no futuro forem suportadas, as constantes blittable acabariam na mesma situação; ou seja, todas elas atualmente precisam de uma cópia completa (na invocação de membro da instância) se a struct não estiver marcada readonly.

Desenho

Permita que um usuário especifique que um membro da instância é, ele próprio, readonly e não modifica o estado da instância (com toda a verificação apropriada feita pelo compilador, é claro). Por exemplo:

public struct Vector2
{
    public float x;
    public float y;

    public readonly float GetLengthReadonly()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public float GetLength()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public readonly float GetLengthIllegal()
    {
        var tmp = MathF.Sqrt(LengthSquared);

        x = tmp;    // Compiler error, cannot write x
        y = tmp;    // Compiler error, cannot write y

        return tmp;
    }

    public readonly float LengthSquared
    {
        get
        {
            return (x * x) +
                   (y * y);
        }
    }
}

public static class MyClass
{
    public static float ExistingBehavior(in Vector2 vector)
    {
        // This code causes a hidden copy, the compiler effectively emits:
        //    var tmpVector = vector;
        //    return tmpVector.GetLength();
        //
        // This is done because the compiler doesn't know that `GetLength()`
        // won't mutate `vector`.

        return vector.GetLength();
    }

    public static float ReadonlyBehavior(in Vector2 vector)
    {
        // This code is emitted exactly as listed. There are no hidden
        // copies as the `readonly` modifier indicates that the method
        // won't mutate `vector`.

        return vector.GetLengthReadonly();
    }
}

Properties de só de leitura podem ser aplicadas a acessores para indicar que this não será alterado no acessor. Os exemplos a seguir têm os setters em modo de leitura apenas, porque os acessores modificam o estado do campo membro, mas não o valor desse campo membro.

public readonly int Prop1
{
    get
    {
        return this._store["Prop1"];
    }
    set
    {
        this._store["Prop1"] = value;
    }
}

Quando readonly é aplicado na sintaxe da propriedade, significa que todos os métodos de acesso são readonly.

public readonly int Prop2
{
    get
    {
        return this._store["Prop2"];
    }
    set
    {
        this._store["Prop2"] = value;
    }
}

Somente leitura só pode ser aplicado a acessores que não alteram o tipo de dados que os contém.

public int Prop3
{
    readonly get
    {
        return this._prop3;
    }
    set
    {
        this._prop3 = value;
    }
}

Readonly pode ser aplicado a algumas propriedades implementadas automaticamente, mas não terá um efeito significativo. O compilador tratará todos os getters implementados automaticamente como somente de leitura, quer a palavra-chave readonly esteja presente ou não.

// Allowed
public readonly int Prop4 { get; }
public int Prop5 { readonly get; set; }

// Not allowed
public int Prop6 { readonly get; }
public readonly int Prop7 { get; set; }
public int Prop8 { get; readonly set; }

Somente leitura pode ser aplicado a eventos implementados manualmente, mas não a eventos do tipo campo. O atributo 'somente leitura' não pode ser aplicado aos acessores de eventos individuais (adicionar/remover).

// Allowed
public readonly event Action<EventArgs> Event1
{
    add { }
    remove { }
}

// Not allowed
public readonly event Action<EventArgs> Event2;
public event Action<EventArgs> Event3
{
    readonly add { }
    readonly remove { }
}
public static readonly event Event4
{
    add { }
    remove { }
}

Alguns outros exemplos de sintaxe:

  • Membros com corpo de expressão: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • Restrições genéricas: public readonly void GenericMethod<T>(T value) where T : struct { }

O compilador emitiria o membro da instância, como de costume, e também emitiria um atributo reconhecido do compilador indicando que o membro da instância não modifica o estado. Isso efetivamente faz com que o parâmetro this oculto se torne in T em vez de ref T.

Isso permitiria que o usuário chamasse com segurança esse método de instância sem que o compilador precisasse fazer uma cópia.

As restrições incluirão:

  • O modificador readonly não pode ser aplicado a métodos estáticos, construtores ou destruidores.
  • O modificador readonly não pode ser aplicado a delegados.
  • O modificador readonly não pode ser aplicado a membros de classe ou interface.

Desvantagens

As mesmas desvantagens que existem hoje com os métodos readonly struct. Certos códigos ainda podem causar cópias ocultas.

Observações

Usar um atributo ou outra palavra-chave também pode ser possível.

Esta proposta está de algum modo relacionada com (mas é mais um subconjunto de) functional purity e/ou constant expressions, ambos com algumas propostas existentes.