Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Este artigo é uma especificação de recurso. A especificação serve 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 aprender mais sobre o processo de adoção de especificações especiais de recursos no padrão da linguagem C# no artigo sobre as especificações .
Problema do especialista: https://github.com/dotnet/csharplang/issues/49
Resumo
Suporte tipos de retorno covariantes . Especificamente, permita a substituição de um método para declarar um tipo de retorno mais derivado do que o método que ele substitui e também permita a substituição de uma propriedade somente leitura para declarar um tipo mais derivado. Declarações de substituição que aparecem em tipos mais derivados devem fornecer um tipo de retorno pelo menos tão específico quanto os que aparecem nas substituições em seus tipos base. Os chamadores do método ou propriedade receberiam estaticamente o tipo de retorno mais refinado de uma invocação.
Motivação
É um padrão comum no código que diferentes nomes de métodos precisam ser inventados para contornar a restrição de linguagem de que as substituições devem retornar o mesmo tipo que o método substituído.
Isso seria útil no padrão de fábrica. Por exemplo, na base de código Roslyn, teríamos
class Compilation ...
{
public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
public override CSharpCompilation WithOptions(Options options)...
}
Projeto detalhado
Esta é uma especificação para tipos de retorno covariantes em C#. Nossa intenção é permitir a substituição de um método para retornar um tipo de retorno mais derivado do que o método que ele substitui e, da mesma forma, permitir a substituição de uma propriedade somente leitura para retornar um tipo de retorno mais derivado. Os chamadores do método ou propriedade receberiam estaticamente o tipo de retorno mais refinado de uma invocação, e as substituições que aparecem em tipos mais derivados seriam necessárias para fornecer um tipo de retorno pelo menos tão específico quanto aquele que aparece nas substituições em seus tipos base.
Substituição do método de classe
A restrição existente na substituição de métodos de classe (§15.6.5)
- O método de substituição e o método base substituído têm o mesmo tipo de retorno.
é modificado para
- O método de substituição deve ter um tipo de retorno que seja conversível por uma conversão de identidade ou (se o método tiver um retorno de valor - não um ref return consulte §13.1.0.5 conversão de referência implícita para o tipo de retorno do método base substituído.
E os seguintes requisitos adicionais são acrescentados a essa lista:
- O método de substituição deve ter um tipo de retorno que seja conversível por uma conversão de identidade ou (se o método tiver um retorno de valor - não um ref return, §13.1.0.5) conversão de referência implícita para o tipo de retorno de cada substituição do método base substituído que é declarado em um tipo base (direto ou indireto) do método de substituição.
- O tipo de retorno do método de substituição deve ser pelo menos tão acessível quanto o método de substituição (domínios de acessibilidade - §7.5.3).
Essa restrição permite que um método de substituição em uma classe private tenha um tipo de retorno private. No entanto, é necessário que um método de substituição public em um tipo public tenha um tipo de retorno public.
Substituição de propriedade de classe e indexador
A restrição existente na substituição de propriedades de classe (§15.7.6)
Uma declaração de propriedade de substituição deve especificar exatamente os mesmos modificadores de acessibilidade e o mesmo nome da propriedade herdada, e deve haver uma conversão de identidade
entre o tipo da propriedade de substituição e o tipo da propriedade herdada. Se a propriedade herdada tiver apenas um único acessador (ou seja, se a propriedade herdada for somente leitura ou somente gravação), a propriedade de substituição deverá incluir somente esse acessador. Se a propriedade herdada incluir ambos os acessadores (ou seja, se a propriedade herdada for leitura-gravação), a propriedade de substituição poderá incluir um único acessador ou ambos os acessadores.
é modificado para
Uma declaração de propriedade de substituição deve especificar exatamente os mesmos modificadores de acessibilidade e o mesmo nome da propriedade herdada. Além disso, deve haver uma conversão de identidade ou, (se a propriedade herdada for somente leitura e tiver um retorno de valor - não um ref return§13.1.0.5) uma conversão implícita de referência do tipo da propriedade de substituição para o tipo da propriedade herdada. Se a propriedade herdada tiver apenas um único acessador (ou seja, se a propriedade herdada for somente leitura ou somente gravação), a propriedade de substituição deverá incluir somente esse acessador. Se a propriedade herdada incluir ambos os acessadores (ou seja, se a propriedade herdada for leitura-gravação), a propriedade de substituição poderá incluir um único acessador ou ambos os acessadores. O tipo da propriedade de substituição deve ser pelo menos tão acessível quanto a propriedade de substituição (domínios de acessibilidade – §7.5.3).
O restante do rascunho da especificação abaixo propõe uma extensão adicional para retornos covariantes de métodos de interface a ser considerada posteriormente.
Substituição de método, propriedade e indexador de interface
Ao adicionar aos tipos de membros permitidos em uma interface com o recurso DIM em C# 8.0, também oferecemos suporte adicional para membros override, além de retornos covariantes. Eles seguem as regras dos membros override conforme especificado para as classes, com as seguintes diferenças:
O texto a seguir nas classes é:
O método base substituído por uma declaração de substituição é conhecido como método substituído. Para um método de substituição
Mdeclarado em uma classeC, o método base substituído é determinado examinando cada classe base deC, começando com a classe base direta deCe continuando com cada classe base direta sucessiva, até que em um determinado tipo de classe base seja localizado pelo menos um método acessível que tenha a mesma assinatura queMapós a substituição de argumentos de tipo.
é dada a especificação correspondente para interfaces:
O método base substituído por uma declaração de substituição é conhecido como método substituído. Para um método substituído
Mdeclarado em uma interfaceI, o método base que está sendo substituído é determinado ao examinar cada interface base direta ou indireta deI, coletando o conjunto de interfaces que declaram um método acessível com a mesma assinatura queMapós a substituição dos argumentos de tipo. Se esse conjunto de interfaces tiver um tipo mais derivado, para o qual existe uma identidade ou uma conversão de referência implícita de cada tipo neste conjunto, e esse tipo contiver uma declaração única desse método, então esse é o método base substituído.
De maneira semelhante, permitimos override propriedades e indexadores em interfaces, conforme especificado para classes em §15.7.6 Acessadores virtuais, selados, substituídos e abstratos.
Pesquisa de nome
A pesquisa de nome na presença de declarações de override de classe atualmente altera o resultado da pesquisa ao aplicar, ao membro encontrado, os detalhes da declaração de override mais derivada na hierarquia de classe, começando do tipo do qualificador do identificador (ou this quando não há qualificador). Por exemplo, em §12.6.2.2 Parâmetros correspondentes, temos
Para métodos virtuais e indexadores definidos em classes, a lista de parâmetros é escolhida na primeira declaração ou substituição do membro da função encontrado ao começar com o tipo estático do receptor e pesquisar nas classes base.
adicionamos o seguinte a isso
Para métodos virtuais e indexadores definidos em interfaces, a lista de parâmetros é escolhida na declaração ou substituição do membro da função encontrado no tipo mais derivado entre os tipos que contêm a declaração de substituição do membro da função. É um erro de compilação se não existir um único tipo desse tipo.
Para o tipo de resultado de um acesso de propriedade ou indexador, o texto existente
- Se
Iidentificar uma propriedade de instância, o resultado será um acesso de propriedade com uma expressão de instância associada deEe um tipo associado que é o tipo da propriedade. CasoTseja um tipo de classe, o tipo associado é selecionado a partir da primeira declaração ou sobrescrição da propriedade encontrada ao iniciar comTe ao procurar por suas classes base.
é aprimorado com
Se
Tfor um tipo de interface, o tipo associado será escolhido na declaração ou substituição da propriedade encontrada na interface mais derivada deTou de suas interfaces base diretas ou indiretas. É um erro de compilação se não existir um único tipo desse tipo.
Uma alteração semelhante deve ser feita em §12.8.12.3 Acesso ao indexador
Em §12.8.10 Expressões de invocação aprimoramos o texto existente.
- Caso contrário, o resultado será um valor, associado ao tipo de retorno do método ou delegado. Se a invocação for de um método de instância e o receptor for de um tipo de classe
T, o tipo associado será escolhido na primeira declaração ou substituição do método encontrado ao começar comTe pesquisar por meio das classes base.
por
Se a invocação for de um método de instância e o receptor for de um tipo de interface
T, o tipo associado será escolhido da declaração ou substituição do método encontrado na interface mais derivada entreTe suas interfaces base diretas e indiretas. É um erro de compilação se não existir um único tipo desse tipo.
Implementações de interface implícitas
Esta seção da especificação
Para fins de mapeamento de interface, um membro de classe
Acorresponde a um membro de interfaceBquando:
AeBsão métodos e o nome, tipo e listas de parâmetros formais deAeBsão idênticos.AeBsão propriedades, o nome e o tipo deAeBsão idênticos, eAtem os mesmos acessadores queB(Aé permitido ter acessadores adicionais se não for uma implementação de membro de interface explícita).AeBsão eventos, e o nome e o tipo deAeBsão idênticos.AeBsão indexadores, o tipo e as listas de parâmetros formais deAeBsão idênticos, eAtem os mesmos acessadores queB(Aé permitido ter acessadores adicionais se não for uma implementação de membro de interface explícita).
é modificado da seguinte maneira:
Para fins de mapeamento de interface, um membro de classe
Acorresponde a um membro de interfaceBquando:
AeBsão métodos, e o nome e as listas de parâmetros formais deAeBsão idênticos e o tipo de retorno deAé conversível para o tipo de retorno deBpor meio de uma identidade de conversão de referência implícita para o tipo de retorno deB.AandBsão propriedades, o nome deAeBsão idênticos,Atem os mesmos acessadores queB(Aé permitido ter acessadores adicionais se não for uma implementação de membro de interface explícita) e o tipo deAé conversível para o tipo de retorno deBpor meio de uma conversão de identidade ou, seAé uma propriedade somente leitura, uma conversão de referência implícita.AeBsão eventos, e o nome e o tipo deAeBsão idênticos.AandBsão indexadores, as listas de parâmetros formais deAeBsão idênticos,Atem os mesmos acessadores queB(Aé permitido ter acessadores adicionais se não for uma implementação de membro de interface explícita) e o tipo deAé conversível para o tipo de retorno deBpor meio de uma conversão de identidade ou, seAé um indexador somente leitura, uma conversão de referência implícita.
Esta é tecnicamente uma alteração drástica, pois o programa abaixo imprime "C1.M" hoje, mas imprimiria "C2.M" na revisão proposta.
using System;
interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }
class Program
{
static void Main()
{
I1 i = new C2();
Console.WriteLine(i.M());
}
}
Devido a essa mudança significativa, podemos considerar a possibilidade de não oferecer suporte a tipos de retorno covariantes em implementações implícitas.
Restrições na implementação da interface
Precisaremos de uma regra que determine que uma implementação de interface explícita deve declarar um tipo de retorno não menos derivado que o tipo de retorno declarado em qualquer substituição em suas interfaces base.
Implicações da compatibilidade da API
A ser definido
Problemas Abertos
A especificação não diz como o chamador obtém o tipo de retorno mais refinado. Supostamente, isso seria feito de forma semelhante à maneira como os chamadores obtêm as especificações de parâmetro da substituição mais derivada.
Se tivermos as seguintes interfaces:
interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); }
Note que, em I3, os métodos I1.M() e I2.M() foram "mesclados". Ao implementar I3, é necessário implementá-los juntos.
Geralmente, exigimos uma implementação explícita para fazer referência ao método original. A questão é, em uma classe
class C : I1, I2, I3
{
C IN.M();
}
O que isso significa aqui? O que N deve ser?
Sugiro que permitamos a implementação de qualquer um I1.M ou I2.M (mas não ambos) e trate isso como uma implementação de ambos.
Desvantagens
- [ ] Toda alteração no idioma deve se justificar.
- [ ] Devemos garantir que o desempenho seja razoável, mesmo no caso de hierarquias de herança profundas
- [ ] Devemos garantir que os artefatos da estratégia de tradução não afetem a semântica da linguagem, mesmo ao consumir novo IL de compiladores antigos.
Alternativas
Poderíamos flexibilizar um pouco as regras de linguagem para permitir, na origem,
// Possible alternative. This was not implemented.
abstract class Cloneable
{
public abstract Cloneable Clone();
}
class Digit : Cloneable
{
public override Cloneable Clone()
{
return this.Clone();
}
public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types
{
return this;
}
}
Perguntas não resolvidas
- [ ] Como as APIs que foram compiladas para usar esse recurso funcionarão em versões mais antigas da linguagem?
Reuniões de design
- alguma discussão em https://github.com/dotnet/roslyn/issues/357.
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-01-08.md
- Discussão offline para uma decisão de dar suporte à substituição de métodos de classe somente no C# 9.0.
C# feature specifications