Eventos
17 de mar., 21 - 21 de mar., 10
Junte-se à série de encontros para criar soluções de IA escaláveis com base em casos de uso do mundo real com outros desenvolvedores e especialistas.
Registrar agoraNão há mais suporte para esse navegador.
Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.
Observação
Este artigo é uma especificação de recurso. A especificação age como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .
Problema do especialista: https://github.com/dotnet/csharplang/issues/39
Essa proposta acompanha a especificação do recurso de registros C# 9, conforme acordado pela equipe de design de linguagem C#.
A sintaxe de um registro é a seguinte:
record_declaration
: attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
record_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
record_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
Os tipos de registro são tipos de referência, semelhantes a uma declaração de classe. É um equívoco que um registro forneça um record_base
argument_list
se o record_declaration
não contiver um parameter_list
.
No máximo, uma declaração de tipo parcial de um registro parcial é capaz de fornecer um parameter_list
.
Os parâmetros de registro não podem usar modificadores ref
, out
ou this
(mas são permitidos in
e params
).
Os registros não podem herdar de classes, a menos que a classe seja object
, e as classes não podem herdar de registros. Os registros podem herdar de outros registros.
Além dos membros declarados no corpo do registro, um tipo de registro também tem membros sintetizados adicionais. Os membros são sintetizados, exceto quando um membro com uma assinatura "correspondente" é declarado no corpo do registro ou quando é herdado um membro concreto não virtual acessível com uma assinatura "correspondente". Um membro correspondente impede que o compilador gere esse membro, e não outros membros sintetizados. Dois membros são considerados correspondentes se tiverem a mesma assinatura ou forem considerados "ocultos" em um cenário de herança. É um erro que um membro de um registro seja chamado de "Clone". Caso um campo de instância de um registro tenha um tipo de ponteiro de nível superior, trata-se de um erro. Um tipo de ponteiro aninhado, como uma matriz de ponteiros, é permitido.
Os membros sintetizados são os seguintes:
Se o registro for derivado de object
, o tipo de registro incluirá uma propriedade sintetizada de somente leitura que equivale a uma propriedade declarada da seguinte maneira:
Type EqualityContract { get; }
A propriedade é private
se o tipo de registro for sealed
. Caso contrário, a propriedade é virtual
e protected
.
A propriedade pode ser declarada explicitamente. Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
.
Se o tipo de registro for derivado de um tipo de registro base Base
, o tipo de registro incluirá uma propriedade somente leitura sintetizada equivalente a uma propriedade declarada da seguinte maneira:
protected override Type EqualityContract { get; }
A propriedade pode ser declarada explicitamente. Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
. Trata-se de um erro se a propriedade sintetizada ou declarada explicitamente não substituir uma propriedade com essa assinatura no tipo de registro Base
(por exemplo, se a propriedade não for encontrada no Base
, ou se for selada ou não virtual, etc.).
A propriedade sintetizada retorna typeof(R)
, onde R
é o tipo de registro.
O tipo de registro implementa System.IEquatable<R>
e inclui uma sobrecarga sintetizada com tipagem forte de Equals(R? other)
, onde R
é o tipo de registro.
O método é public
, e o método é virtual
a menos que o tipo de registro seja sealed
.
O método pode ser declarado explicitamente. Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
.
Se Equals(R? other)
for definido pelo usuário (não sintetizado), mas GetHashCode
não for, um aviso será emitido.
public virtual bool Equals(R? other);
O Equals(R?)
sintetizado retorna true
se e somente se cada um dos seguintes forem true
:
other
não é null
, efieldN
no tipo de registro que não é herdado, o valor de System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
onde TN
é o tipo de campo ebase.Equals(other)
(uma chamada não virtual para public virtual bool Equals(Base? other)
); caso contrário, o valor será EqualityContract == other.EqualityContract
.O tipo de registro inclui operadores ==
e !=
sintetizados equivalentes aos operadores declarados da seguinte maneira:
public static bool operator==(R? left, R? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
=> !(left == right);
O método Equals
chamado pelo operador ==
é o método Equals(R? other)
especificado acima. O operador !=
delega ao operador ==
. Será um erro se os operadores forem declarados explicitamente.
Se o tipo de registro for derivado de um tipo de registro base Base
, o tipo de registro incluirá uma propriedade somente de substituição sintetizada equivalente a um método declarado da seguinte maneira:
public sealed override bool Equals(Base? other);
Será um erro se a substituição for declarada explicitamente. Será um erro se o método não substituir um método com a mesma assinatura no tipo de registro Base
(por exemplo, se o método não for encontrado no Base
, ou for selado ou não virtual, etc.).
A substituição sintetizada retorna Equals((object?)other)
.
O tipo de registro inclui uma substituição sintetizada equivalente a um método declarado da seguinte maneira:
public override bool Equals(object? obj);
Será um erro se a substituição for declarada explicitamente. É um erro se o método não substituir object.Equals(object? obj)
(por exemplo, devido a sombreamento em tipos base intermediários etc.).
A substituição sintetizada retorna Equals(other as R)
ONDE R
é o tipo de registro.
O tipo de registro inclui uma substituição sintetizada equivalente a um método declarado da seguinte maneira:
public override int GetHashCode();
O método pode ser declarado explicitamente.
Será um erro se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
. É um erro se o método sintetizado ou explicitamente declarado não substituir object.GetHashCode()
(por exemplo, devido ao sombreamento em tipos de base intermediários, etc.).
Um aviso será emitido se um dos Equals(R?)
e GetHashCode()
for declarado explicitamente, mas o outro método não for declarado explicitamente.
A substituição sintetizada de GetHashCode()
retorna um resultado int
da combinação dos valores a seguir:
fieldN
no tipo de registro que não é herdado, o valor de System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
onde TN
é o tipo de campo ebase.GetHashCode()
; caso contrário, o valor de System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract)
.Por exemplo, considere os seguintes tipos de registro:
record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);
Para esses tipos de registro, os membros de igualdade sintetizados seriam algo como:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual Type EqualityContract => typeof(R1);
public override bool Equals(object? obj) => Equals(obj as R1);
public virtual bool Equals(R1? other)
{
return !(other is null) &&
EqualityContract == other.EqualityContract &&
EqualityComparer<T1>.Default.Equals(P1, other.P1);
}
public static bool operator==(R1? left, R1? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R1? left, R1? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
EqualityComparer<T1>.Default.GetHashCode(P1));
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
protected override Type EqualityContract => typeof(R2);
public override bool Equals(object? obj) => Equals(obj as R2);
public sealed override bool Equals(R1? other) => Equals((object?)other);
public virtual bool Equals(R2? other)
{
return base.Equals((R1?)other) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public static bool operator==(R2? left, R2? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R2? left, R2? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
class R3 : R2, IEquatable<R3>
{
public T3 P3 { get; init; }
protected override Type EqualityContract => typeof(R3);
public override bool Equals(object? obj) => Equals(obj as R3);
public sealed override bool Equals(R2? other) => Equals((object?)other);
public virtual bool Equals(R3? other)
{
return base.Equals((R2?)other) &&
EqualityComparer<T3>.Default.Equals(P3, other.P3);
}
public static bool operator==(R3? left, R3? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R3? left, R3? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T3>.Default.GetHashCode(P3));
}
}
Um tipo de registro contém dois membros de cópia:
A finalidade do construtor de cópia é copiar o estado do parâmetro para a nova instância que está sendo criada. Esse construtor não executa nenhum inicializador de campo/propriedade de instância presente na declaração de registro. Se o construtor não for explicitamente declarado, um construtor será sintetizado pelo compilador. Se o registro for lacrado, o construtor será privado, caso contrário, ele será protegido. Um construtor de cópia declarado explicitamente deve ser público ou protegido, a menos que o registro seja lacrado. A primeira coisa que o construtor deve fazer é chamar um construtor de cópia da base ou um construtor de objeto sem parâmetro se o registro herdar do objeto. Um erro será relatado se um construtor de cópia definido pelo usuário usar um inicializador de construtor implícito ou explícito que não atenda a esse requisito. Depois que um construtor de cópia base é invocado, um construtor de cópia sintetizado copia valores para todos os campos de instância implicitamente ou explicitamente declarados dentro do tipo de registro. A presença de um construtor de cópia por si só, seja explícito ou implícito, não impede a adição automática de um construtor de instância padrão.
Se um método "clone" virtual estiver presente no registro base, o método "clone" sintetizado o substituirá e o tipo de retorno do método será o tipo atual que contém. Um erro será produzido se o método de clonagem do registro base estiver selado. Se um método de "clone" virtual não estiver presente no registro base, o tipo de retorno do método de clone será o tipo que o contém e o método será virtual, a menos que o registro seja selado ou abstrato. Se o registro de contenção for abstrato, o método de clone sintetizado também será abstrato. Se o método de "clone" não for abstrato, ele retornará o resultado de uma chamada para um construtor de cópia.
Se o registro for derivado de object
, o registro incluirá um método sintetizado equivalente a um método declarado da seguinte maneira:
bool PrintMembers(System.Text.StringBuilder builder);
O método é private
se o tipo de registro for sealed
. Caso contrário, o método será virtual
e protected
.
O método :
System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()
se o método estiver presente e o registro tiver membros que possam ser impressos.Para um membro que tem um tipo de valor, converteremos seu valor em uma representação de cadeia de caracteres usando o método mais eficiente disponível para a plataforma de destino. No momento, isso significa chamar ToString
antes de passar para StringBuilder.Append
.
Se o tipo de registro for derivado de um registro base Base
, o registro incluirá uma propriedade somente de substituição sintetizada equivalente a um método declarado da seguinte maneira:
protected override bool PrintMembers(StringBuilder builder);
Se o registro não tiver membros imprimíveis, o método chama o método base PrintMembers
com um argumento (seu parâmetro builder
) e retorna o resultado.
Caso contrário, o método:
PrintMembers
com um argumento (seu parâmetro builder
)PrintMembers
retornar true, acrescente ", " ao builder.this.member
(ou this.member.ToString()
para tipos de valor), separado com ",O método PrintMembers
pode ser declarado explicitamente.
Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
.
O registro inclui um método sintetizado equivalente a um método declarado da seguinte maneira:
public override string ToString();
O método pode ser declarado explicitamente. Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou se a declaração explícita não permitir substituição em um tipo derivado e o tipo de registro não for sealed
. É um erro se o método sintetizado ou explicitamente declarado não substituir object.ToString()
(por exemplo, devido ao sombreamento em tipos de base intermediários, etc.).
O método sintetizado:
StringBuilder
,PrintMembers
do registro, fornecendo-lhe o construtor e seguido por " " se ele retornar true,builder.ToString()
.Por exemplo, considere os seguintes tipos de registro:
record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);
Para esses tipos de registro, os membros de impressão sintetizados seriam algo como:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R1));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
public T3 P3 { get; init; }
protected override bool PrintMembers(StringBuilder builder)
{
if (base.PrintMembers(builder))
builder.Append(", ");
builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
builder.Append(", ");
builder.Append(nameof(P3));
builder.Append(" = ");
builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R2));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
Além dos membros acima, os registros com uma lista de parâmetros ("registros posicionais") sintetizam membros adicionais com as mesmas condições que os membros acima.
Um tipo de registro tem um construtor público cuja assinatura corresponde aos parâmetros de valor da declaração de tipo. Isso é chamado de construtor primário para o tipo e faz com que o construtor de classe padrão declarado implicitamente, se houver, seja suprimido. É um erro ter um construtor primário e um construtor com a mesma assinatura já presentes na classe.
Em runtime, o construtor primário
executa os inicializadores de instância que aparecem no corpo da classe
invoca o construtor de classe base com os argumentos fornecidos na cláusula record_base
, se houver
Se um registro tiver um construtor primário, qualquer construtor definido pelo usuário, exceto "construtor de cópia", deverá ter um inicializador de construtor this
explícito.
Os parâmetros do construtor primário, assim como os membros do registro, estão em escopo dentro do argument_list
da cláusula record_base
e nos inicializadores de campos de instância ou propriedades. Os membros da instância seriam um erro nesses locais (semelhante à forma como os membros da instância estão no escopo em inicializadores de construtores regulares atualmente, mas um erro de uso), mas os parâmetros do construtor primário estariam no escopo e seriam utilizáveis, além de serem membros sombreados. Também seria possível usar estáticos, semelhante à forma como as chamadas base e os inicializadores funcionam hoje em construtores comuns.
Um aviso será gerado se um parâmetro do construtor primário não for lido.
As variáveis de expressão declaradas no argument_list
estão no escopo dentro do argument_list
. Aplicam-se as mesmas regras de sombreamento que em uma lista de argumentos de um inicializador de construtor regular.
Para cada parâmetro individual de um tipo de registro, há um membro de propriedade pública correspondente cujo nome e tipo são derivados da declaração de parâmetro de valor.
Para registro:
get
e automática init
é criada (consulte a init
especificação do acessador separado).
Uma propriedade herdada abstract
com o tipo correspondente é substituída.
É um erro se a propriedade herdada não tiver public
substituíveis get
e acessadores init
.
Se a propriedade herdada estiver oculta, trata-se de um erro.property:
ou field:
para atributos que são aplicados sintaticamente ao parâmetro de registro correspondente.Um registro posicional com pelo menos um parâmetro sintetiza um método de instância de retorno de void público chamado Deconstruct com uma declaração de parâmetro de saída para cada parâmetro da declaração do construtor primário. Cada parâmetro do método Deconstruct
tem o mesmo tipo que o parâmetro correspondente da declaração do construtor primário. O corpo do método atribui o valor da propriedade da instância de mesmo nome a cada parâmetro do método Deconstruct
.
O método pode ser declarado explicitamente. Será um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada ou for estática.
O exemplo a seguir mostra um registro posicional R
com seu método Deconstruct
sintetizado pelo compilador, juntamente com seu uso:
public record R(int P1, string P2 = "xyz")
{
public void Deconstruct(out int P1, out string P2)
{
P1 = this.P1;
P2 = this.P2;
}
}
class Program
{
static void Main()
{
R r = new R(12);
(int p1, string p2) = r;
Console.WriteLine($"p1: {p1}, p2: {p2}");
}
}
Uma expressão with
é uma nova expressão usando a sintaxe a seguir.
with_expression
: switch_expression
| switch_expression 'with' '{' member_initializer_list? '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: identifier '=' expression
;
Uma expressão with
não é permitida como uma instrução.
Uma expressão with
permite "mutação não destrutiva", projetada para produzir uma cópia da expressão receptora com modificações em atribuições no member_initializer_list
.
Uma expressão with
válida tem um receptor com um tipo não nulo. O tipo de receptor deve ser um registro.
No lado direito da expressão with
está um member_initializer_list
com uma sequência de atribuições para o identificador, o qual deve ser um campo de instância acessível ou propriedade do tipo do receptor.
Primeiro, o método de "clone" do receptor (especificado acima) é invocado e seu resultado é convertido no tipo do receptor. Em seguida, cada member_initializer
é processado da mesma maneira que uma atribuição a um campo ou acesso à propriedade do resultado da conversão. As tarefas são processadas em ordem lexical.
Comentários do C# feature specifications
O C# feature specifications é um projeto código aberto. Selecione um link para fornecer comentários:
Eventos
17 de mar., 21 - 21 de mar., 10
Junte-se à série de encontros para criar soluções de IA escaláveis com base em casos de uso do mundo real com outros desenvolvedores e especialistas.
Registrar agora