Compartilhar via


métodos de interface padrão

Nota

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele 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 discrepâ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 .

Resumo

Adicione suporte para métodos de extensão virtual – métodos em interfaces com implementações concretas. Uma classe ou struct que implementa essa interface precisa ter uma única implementação mais específica para o método de interface, seja ela implementada pela classe ou struct, ou herdada de suas classes ou interfaces de base. Os métodos de extensão virtual permitem que um autor de API adicione métodos a uma interface em versões futuras sem quebrar a compatibilidade de origem ou binária com implementações existentes dessa interface.

Eles são semelhantes aos "Métodos Padrão" de Java.

(Com base na técnica de implementação provável) esse recurso requer suporte correspondente na CLI/CLR. Os programas que aproveitam esse recurso não podem ser executados em versões anteriores da plataforma.

Motivação

As principais motivações para esse recurso são

  • Os métodos de interface padrão permitem que um autor de API adicione métodos a uma interface em versões futuras sem quebrar a compatibilidade de origem ou binária com implementações existentes dessa interface.
  • O recurso permite que o C# interopere com APIs direcionadas Android (Java) e iOS (Swift), que dão suporte a recursos semelhantes.
  • Como se pode ver, adicionar implementações de interface padrão traz os elementos do recurso de linguagem de "características" (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Traits provaram ser uma técnica de programação poderosa (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf).

Design detalhado

A sintaxe da interface é estendida para permitir

  • declarações de membros que declaram constantes, operadores, construtores estáticos e tipos aninhados;
  • um corpo para um método ou indexador, propriedade ou acessador de eventos (ou seja, uma implementação "padrão");
  • declarações de membro que declaram campos estáticos, métodos, propriedades, indexadores e eventos;
  • declarações de membros que usam a sintaxe de implementação de interface explícita; e
  • Modificadores de acesso explícitos (o acesso padrão é public).

Membros com corpos permitem que a interface forneça uma implementação "padrão" para o método em classes e structs que não fornecem uma implementação própria.

As interfaces podem não conter o estado da instância. Embora os campos estáticos agora sejam permitidos, os campos de instância não são permitidos em interfaces. Não há suporte para propriedades automáticas de instância em interfaces, pois declarariam implicitamente um campo oculto.

Métodos estáticos e privados permitem refatoração útil e organização de código usada para implementar a API pública da interface.

Uma substituição de método em uma interface deve usar a sintaxe de implementação de interface explícita.

É um erro declarar um tipo de classe, um tipo de struct ou um tipo de enumeração dentro do escopo de um parâmetro de tipo que tenha sido declarado com um variance_annotation. Por exemplo, a declaração de C abaixo é um erro.

interface IOuter<out T>
{
    class C { } // error: class declaration within the scope of variant type parameter 'T'
}

Métodos concretos em interfaces

A forma mais simples desse recurso é a capacidade de declarar um método concreto em uma interface, que é um método com corpo.

interface IA
{
    void M() { WriteLine("IA.M"); }
}

Uma classe que implementa essa interface não precisa implementar seu método concreto.

class C : IA { } // OK

IA i = new C();
i.M(); // prints "IA.M"

A substituição final para IA.M na classe C é o método concreto M declarado em IA. Observe que uma classe não herda membros de suas interfaces; que não é alterado por este recurso:

new C().M(); // error: class 'C' does not contain a member 'M'

Dentro de um membro de instância de uma interface, this tem o tipo da interface delimitadora.

Modificadores em interfaces

A sintaxe de uma interface é flexibilizada para permitir modificadores em seus membros. As seguintes opções são permitidas: private, protected, internal, public, virtual, abstract, sealed, static, externe partial.

Um membro de interface cuja declaração inclui um corpo é um membro virtual, a menos que o modificador sealed ou private seja usado. O modificador virtual pode ser usado em um membro de função que, de outra forma, seria implicitamente virtual. Da mesma forma, embora abstract seja o padrão em membros de interface sem corpos, esse modificador pode ser fornecido explicitamente. Um membro não virtual pode ser declarado usando a palavra-chave sealed.

É erro um membro de função private ou sealed de uma interface não ter corpo. Um membro da função private pode não ter o modificador sealed.

Modificadores de acesso podem ser usados em todos os tipos permitidos de membros de interface. O nível de acesso public é o padrão, mas pode ser dado explicitamente.

Problema Aberto: Precisamos especificar o significado preciso dos modificadores de acesso, como protected e internal, e quais declarações os substituem ou não (em uma interface derivada) ou os implementam (em uma classe que implementa a interface).

As interfaces podem declarar membros static, incluindo tipos aninhados, métodos, indexadores, propriedades, eventos e construtores estáticos. O nível de acesso padrão para todos os membros da interface é public.

As interfaces não podem declarar construtores de instância, destruidores ou campos.

Problema Fechado: As declarações do operador devem ser permitidas em uma interface? Provavelmente não são operadores de conversão, mas e os outros? Decisão: os operadores são permitidos, exceto para operadores de conversão, igualdade e desigualdade.

Problema fechado: Deve-se permitir new em declarações de membros de interface que ocultam membros de interfaces de base? Decisão: Sim.

Problema fechado: No momento, não permitimos partial em uma interface ou em seus membros. Isso exigiria uma proposta separada. Decisão: Sim. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

Implementação explícita em interfaces

Implementações explícitas permitem que o programador forneça uma implementação mais específica de um membro virtual em uma interface em que o compilador ou o runtime não encontrariam um. Uma declaração de implementação tem permissão para implementar explicitamente um método de interface de base específico, qualificando a declaração com o nome da interface (nenhum modificador de acesso é permitido nesse caso). Implementações implícitas não são permitidas.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
    void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}

Implementações explícitas em interfaces podem não ser declaradas sealed.

Membros de função virtual públicos em uma interface só podem ser implementados explicitamente em uma interface derivada (ao qualificar o nome na declaração com o tipo de interface que originalmente declarou o método e omitir um modificador de acesso). O membro deverá estar acessível quando implementado.

Reabstração

Um método virtual (concreto) declarado em uma interface pode ser reabstrado em uma interface derivada

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.

O modificador abstract é necessário na declaração de IB.M, para indicar que IA.M está sendo reabstrado.

Isso é útil em interfaces derivadas em que a implementação padrão de um método é inadequada e uma implementação mais apropriada deve ser fornecida implementando classes.

A regra de implementação mais específica

Exigimos que cada interface e classe tenham uma implementação mais específica para cada membro virtual entre as implementações que aparecem no tipo ou em suas interfaces diretas e indiretas. A implementação mais específica é uma implementação única que é mais específica do que todas as outras implementações. Se não houver nenhuma implementação, o próprio membro será considerado a implementação mais específica.

Uma implementação M1 é considerada mais específica do que outra implementação M2 quando M1 é declarado no tipo T1, M2 é declarado no tipo T2 e

  1. T1 contém T2 entre suas interfaces diretas ou indiretas ou
  2. T2 é um tipo de interface, mas T1 não é um tipo de interface.

Por exemplo:

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
    public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'

A regra de implementação mais específica garante que um conflito (ou seja, uma ambiguidade decorrente da herança de diamante) seja resolvido explicitamente pelo programador no ponto em que o conflito surge.

Como oferecemos suporte a reabstrações explícitas em interfaces, poderíamos fazer isso também em classes

abstract class E : IA, IB, IC // ok
{
    abstract void IA.M();
}

problema fechado: devemos dar suporte a implementações abstratas de interface explícitas em classes? Decisão: NÃO

Além disso, será um erro se em uma declaração de classe a implementação mais específica de algum método de interface for uma implementação abstrata que foi declarada em uma interface. Essa é uma regra existente reatada usando a nova terminologia.

interface IF
{
    void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'

É possível que uma propriedade virtual declarada em uma interface tenha uma implementação mais específica para seu acessador get em uma interface e uma implementação mais específica para seu acessador set em uma interface diferente. Isso é considerado uma violação da regra de implementação mais específica e gera um erro do compilador.

métodos static e private

Como as interfaces agora podem conter código executável, é útil abstrair o código comum em métodos privados e estáticos. Agora, permitimos isso em interfaces.

Problema fechado: Devemos aceitar métodos privados? Devemos dar suporte a métodos estáticos? Decisão: SIM

problema aberto: devemos permitir que os métodos de interface sejam protected ou internal ou outro acesso? Se for o caso, o que são as semânticas? Eles são virtual por padrão? Nesse caso, há uma maneira de torná-los não virtuais?

problema fechado: se dermos suporte a métodos estáticos, devemos dar suporte a operadores (estáticos)? Decisão: SIM

Invocações de interface base

A sintaxe nesta seção não foi implementada. Ela continua sendo uma proposta ativa.

O código em um tipo que deriva de uma interface com um método padrão pode invocar explicitamente a implementação "base" dessa interface.

interface I0
{
   void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
   override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
   override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
   // an explicit override that invoke's a base interface's default method
   void I0.M() { I2.base.M(); }
}

Um método de instância (não estático) tem permissão para invocar a implementação de um método de instância acessível em uma interface base direta de forma não virtual, nomeando-a usando a sintaxe base(Type).M. Isso é útil quando uma substituição que é necessária devido à herança de diamante é resolvida ao delegar para uma implementação de base específica.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    override void IA.M() { WriteLine("IC.M"); }
}

class D : IA, IB, IC
{
    void IA.M() { base(IB).M(); }
}

Quando um membro virtual ou abstract é acessado usando a sintaxe base(Type).M, é necessário que Type contenha uma substituição mais específica e exclusiva para M.

Cláusulas base vinculantes

As interfaces agora contêm tipos. Esses tipos podem ser usados na cláusula base como interfaces base. Ao associar uma cláusula base, talvez seja necessário saber o conjunto de interfaces base para associar esses tipos (por exemplo, pesquisar neles e resolver o acesso protegido). O significado da cláusula base de uma interface é, portanto, definido circularmente. Para interromper o ciclo, adicionamos uma nova regra de idioma correspondente a uma regra semelhante já em vigor para classes.

Ao determinar o significado do interface_base de uma interface, as interfaces base são temporariamente consideradas vazias. Intuitivamente, isso garante que o significado de uma cláusula base não possa depender recursivamente de si mesmo.

costumávamos ter as seguintes regras:

Quando uma classe B deriva de uma classe A, ocorre um erro de tempo de compilação caso A dependa de B. Uma classe depende diretamente de sua classe base direta (se houver), e depende diretamente da classe dentro da qual ela está imediatamente aninhada (se houver). Com base nessa definição, o conjunto completo de classes de que depende uma classe é o fecho reflexivo e transitivo da relação de dependência direta .

É um erro de tempo de compilação que uma interface herde direta ou indiretamente de si mesma. As interfaces base de uma interface são as interfaces base explícitas e suas respectivas interfaces base. Em outras palavras, o conjunto de interfaces de base é o fechamento transitivo completo das interfaces de base explícitas, das interfaces de base explícitas delas e assim por diante.

Estamos ajustando-os da seguinte maneira:

Quando uma classe B deriva de uma classe A, ocorre um erro de tempo de compilação caso A dependa de B. Uma classe depende diretamente de sua classe base direta (se houver), e depende diretamente do tipo dentro do qual ela está imediatamente aninhada (se houver).

Quando a interface IB estende uma interface IA, é um erro de compilação para IA depender de IB. Uma interface depende diretamente de suas interfaces de base diretas (se houver) e depende diretamente do tipo dentro do qual ela está imediatamente aninhada (se houver).

Considerando essas definições, o conjunto completo de tipos dos quais um tipo depende é o fechamento reflexivo e transitivo da relação de dependência direta de.

Efeito nos programas existentes

As regras apresentadas aqui destinam-se a não ter efeito sobre o significado dos programas existentes.

Exemplo 1:

interface IA
{
    void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
    public static void M() { } // method unrelated to 'IA.M' because static
}

Exemplo 2:

interface IA
{
    void M();
}
class Base: IA
{
    void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

As mesmas regras fornecem resultados semelhantes à situação análoga envolvendo métodos de interface padrão:

interface IA
{
    void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

problema fechado: confirme se isso é uma consequência pretendida da especificação. Decisão: SIM

Resolução do método runtime

Problema Fechado: A especificação deve descrever o algoritmo de resolução de métodos em tempo de execução frente aos métodos padrão da interface. Precisamos garantir que a semântica seja consistente com a semântica da linguagem, por exemplo, quais métodos declarados fazem e não substituem ou implementam um método internal.

API de suporte do CLR

Para que os compiladores detectem quando estão compilando para um runtime que dá suporte a esse recurso, as bibliotecas para esses runtimes são modificadas para anunciar esse fato por meio da API discutida em https://github.com/dotnet/corefx/issues/17116. Adicionamos

namespace System.Runtime.CompilerServices
{
    public static class RuntimeFeature
    {
        // Presence of the field indicates runtime support
        public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
    }
}

Problema aberto: Este é o melhor nome para o recurso CLR? O recurso CLR faz muito mais do que apenas isso (por exemplo, relaxa as restrições de proteção, dá suporte a substituições em interfaces etc. Talvez devesse ser chamado de algo como "métodos concretos em interfaces" ou "características"?

Outras áreas a serem especificadas

  • [ ] Seria útil catalogar os tipos de efeitos de compatibilidade binária e de origem causados pela adição de métodos de interface padrão e substituições a interfaces existentes.

Inconvenientes

Essa proposta requer uma atualização coordenada para a especificação clr (para dar suporte a métodos concretos em interfaces e resolução de método). Portanto, é bastante "caro" e pode valer a pena fazer em combinação com outros recursos que também antecipamos que exigiriam alterações no CLR.

Alternativas

Nenhum.

Perguntas não resolvidas

  • Perguntas abertas são destacadas ao longo da proposta, acima.
  • Consulte também https://github.com/dotnet/csharplang/issues/406 para obter uma lista de perguntas abertas.
  • A especificação detalhada deve descrever o mecanismo de resolução usado no runtime para selecionar o método preciso a ser invocado.
  • A interação de metadados produzidos por novos compiladores e consumidos por compiladores mais antigos precisa ser trabalhada em detalhes. Por exemplo, precisamos garantir que a representação de metadados que usamos não faça com que a adição de uma implementação padrão em uma interface interrompa uma classe existente que implementa essa interface quando compilada por um compilador mais antigo. Isso pode afetar a representação de metadados que podemos usar.
  • O design deve considerar a interoperação com outros idiomas e compiladores existentes para outros idiomas.

Perguntas resolvidas

Substituição abstrata

A especificação de rascunho anterior continha a capacidade de "reabstratar" um método herdado:

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { }
}
interface IC : IB
{
    override void M(); // make it abstract again
}

Minhas anotações para 2017-03-20 mostraram que decidimos não permitir isso. No entanto, há pelo menos dois casos de uso para ele:

  1. As APIs Java, com as quais alguns usuários desse recurso esperam interoperar, dependem dessa instalação.
  2. A programação com características se aproveita disso. A reabstração é um dos elementos do recurso de linguagem de "características" (https://en.wikipedia.org/wiki/Trait_(computer_programming)). O seguinte é permitido com as classes:
public abstract class Base
{
    public abstract void M();
}
public abstract class A : Base
{
    public override void M() { }
}
public abstract class B : A
{
    public override abstract void M(); // reabstract Base.M
}

Infelizmente, esse código não pode ser refatorado como um conjunto de interfaces (características), a menos que isso seja permitido. De acordo com o princípio Jared de ganância, deve ser permitido.

Problema fechado: Deve-se permitir a reabstração? [SIM] Minhas anotações estavam erradas. As notas do LDM atestam que a reabstração é permitida em uma interface. Não estou em aula.

Modificador Virtual versus Modificador Selado

De Aleksey Tsingauz:

Decidimos permitir modificadores explicitamente declarados em membros da interface, a menos que haja um motivo para não permitir alguns deles. Isso traz uma pergunta interessante em torno do modificador virtual. Deve ser necessário em membros com implementação padrão?

Poderíamos dizer que:

  • se não houver implementação e se nem virtual nem selado for especificado, vamos presumir que o membro seja abstrato.
  • se houver implementação e se nem abstrato nem selado for especificado, vamos presumir que o membro seja virtual.
  • O modificador lacrado é necessário para que um método se torne não virtual nem abstrato.

Como alternativa, poderíamos dizer que o modificador virtual é necessário para um membro virtual. Ou seja, se houver um membro com implementação não explicitamente marcada com modificador virtual, ele não será virtual nem abstrato. Essa abordagem pode fornecer melhor experiência quando um método é movido de uma classe para uma interface:

  • um método abstrato permanece abstrato.
  • um método virtual permanece virtual.
  • um método sem qualquer modificador não permanece nem virtual nem abstrato.
  • o modificador selado não pode ser aplicado a um método que não é uma substituição.

O que você acha?

Problema fechado: Um método concreto (com implementação) deve ser implicitamente virtual? [SIM]

decisões : tomadas no LDM 2017-04-05:

  1. não-virtual deve ser explicitamente expresso por meio de sealed ou private.
  2. sealed é a palavra-chave para tornar não virtuais os membros de instâncias de interface que têm corpos
  3. Queremos permitir todos os modificadores em interfaces
  4. A acessibilidade padrão para membros da interface é pública, incluindo tipos aninhados
  5. membros de funções privadas em interfaces são implicitamente selados, e sealed não é permitido neles.
  6. Classes privadas (em interfaces) são permitidas e podem ser seladas, o que significa selada no sentido de classe selada.
  7. Sem uma boa proposta, a parcial ainda não é permitida em interfaces ou em seus membros.

Compatibilidade binária 1

Quando uma biblioteca fornece uma implementação padrão

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}

Entendemos que a implementação de I1.M no C é I1.M. E se o assembly que contém I2 for alterado da seguinte maneira e recompilado

interface I2 : I1
{
    override void M() { Impl2 }
}

mas C não for recompilado. O que acontece quando o programa é executado? Uma invocação de (C as I1).M()

  1. Executa I1.M
  2. Executa I2.M
  3. Lança algum tipo de erro de runtime

Decisão: Tomada em 2017-04-11: Executa I2.M, que é a substituição inequívoca mais específica em runtime.

Acessadores de eventos (fechado)

Problema fechado: Um evento pode ser substituído "por partes"?

Considere este caso:

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        // error: "remove" accessor missing
    }
}

Essa implementação "parcial" do evento não é permitida porque, como em uma classe, a sintaxe de uma declaração de evento não permite apenas um acessador; ambos (ou nenhum deles) devem ser fornecidos. Você pode fazer a mesma coisa permitindo que o acessador de remoção na sintaxe seja implicitamente abstrato pela ausência de um corpo:

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        remove; // implicitly abstract
    }
}

Observe que esta é uma nova sintaxe (proposta). Na gramática atual, os acessadores de eventos têm um corpo obrigatório.

Problema fechado: Um acessador de eventos pode ser (implicitamente) abstrato pela ausência de um corpo, da mesma forma que métodos em interfaces e acessadores de propriedade são (implicitamente) abstratos pela ausência de um corpo?

Decisão: (2017-04-18) Não. As declarações de eventos exigem métodos de acesso concretos (ou nenhum).

Reabstração em uma classe (fechado)

Problema Fechado: Devemos confirmar se isso é permitido (caso contrário, adicionar uma implementação padrão seria uma alteração significativa):

interface I1
{
    void M() { }
}
abstract class C : I1
{
    public abstract void M(); // implement I1.M with an abstract method in C
}

Decisão: (2017-04-18) Sim. Adicionar um corpo a uma declaração de membro de interface não deve causar problemas de compatibilidade com C.

Substituição selada (fechado)

A pergunta anterior pressupõe implicitamente que o modificador de sealed pode ser aplicado a um override em uma interface. Isso contradiz a especificação preliminar. Vamos permitir que uma substituição seja selada? Os efeitos de compatibilidade de código-fonte e binário da selagem devem ser considerados.

Problema fechado: Devemos permitir a selagem de uma substituição?

Decisão: (2017-04-18) Não permitiremos sealed em substituições em interfaces. O único uso de sealed em membros de interface é torná-los não virtuais em sua declaração inicial.

Herança e classes de diamante (fechado)

O rascunho da proposta prefere substituições de classe a substituições de interface em cenários de herança de diamante:

Exigimos que cada interface e classe tenham uma substituição mais específica para cada método de interface entre as substituições apresentadas no tipo ou em suas interfaces diretas e indiretas. A substituição mais específica é uma substituição única, que é mais específica do que todas as outras. Se não houver substituição, o método em si será considerado a substituição mais específica.

Uma substituição M1 é considerada mais específica do que outra substituição M2 quando M1 é declarado no tipo T1, M2 é declarado no tipo T2 e

  1. T1 contém T2 entre suas interfaces diretas ou indiretas ou
  2. T2 é um tipo de interface, mas T1 não é um tipo de interface.

O cenário é este

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { WriteLine("IB"); }
}
class Base : IA
{
    void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
    static void Main()
    {
        IA a = new Derived();
        a.M();           // what does it do?
    }
}

Devemos confirmar esse comportamento (ou decidir o contrário)

Problema fechado: Confirme a especificação de rascunho, acima, para substituição mais específica, à medida que se aplica a classes e interfaces mistas (uma classe tem prioridade sobre uma interface). Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes.

Métodos de interface versus structs (encerrado)

Há algumas interações infelizes entre métodos de interface padrão e structs.

interface IA
{
    public void M() { }
}
struct S : IA
{
}

Observe que os membros da interface não são herdados:

var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'

Consequentemente, o cliente deve compactar o struct para invocar métodos de interface.

IA s = default(S); // an S, boxed
s.M(); // ok

Compactar dessa forma mina os principais benefícios de um tipo struct. Além disso, todos os métodos de mutação não terão efeito aparente, pois estão operando em uma cópia compactada do struct:

interface IB
{
    public void Increment() { P += 1; }
    public int P { get; set; }
}
struct T : IB
{
    public int P { get; set; } // auto-property
}

T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0

Problema Fechado: O que podemos fazer sobre isso:

  1. Proibir um struct de herdar uma implementação padrão. Todos os métodos de interface seriam tratados como abstratos em um struct. Então podemos dedicar um tempo depois para decidir como fazê-lo funcionar melhor.
  2. Venha com algum tipo de estratégia de geração de código que evite o boxe. Dentro de um método como IB.Increment, o tipo de this talvez seja semelhante a um parâmetro de tipo restrito a IB. Em conjunto com isso, para evitar compactar o chamador, métodos não abstratos seriam herdados das interfaces. Isso pode aumentar substancialmente o trabalho de implementação do compilador e do CLR.
  3. Não se preocupe com isso e deixe como está, considerando-o uma verruga.
  4. Outras ideias?

Decisão: Não se preocupe com isso, apenas mantenha como falha. Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations.

Invocações de interface base (fechadas)

Essa decisão não foi implementada no C# 8. A sintaxe base(Interface).M() não é implementada.

A especificação de rascunho sugere uma sintaxe para invocações de interface base inspiradas em Java: Interface.base.M(). Precisamos selecionar uma sintaxe, pelo menos para o protótipo inicial. Meu favorito é base<Interface>.M().

Problema Fechado: Qual é a sintaxe de uma invocação de membro base?

Decisão: A sintaxe é base(Interface).M(). Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. A interface nomeada deve ser uma interface base, mas não precisa ser uma interface base direta.

Questão Aberta: As invocações de interface base devem ser permitidas nos membros da classe?

Decisão: Sim. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation

Substituir membros da interface não pública (fechado)

Em uma interface, membros não públicos de interfaces de base são substituídos usando o modificador override. Se for uma substituição "explícita" que nomeia a interface que contém o membro, o modificador de acesso será omitido.

Problema fechado: Se for uma substituição "implícita" que não nomeia a interface, o modificador de acesso precisará corresponder?

Decisão: Somente os membros públicos podem ser substituídos implicitamente, e o acesso deve corresponder. Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Problema aberto: O modificador de acesso é necessário, opcional ou omitido em uma substituição explícita, como override void IB.M() {}?

Problema aberto: override é obrigatório, opcional ou omitido em uma substituição explícita, como void IB.M() {}?

Como se implementa um membro de interface não pública em uma classe? Talvez seja necessário fazer isso explicitamente?

interface IA
{
    internal void MI();
    protected void MP();
}
class C : IA
{
    // are these implementations?  Decision: NO
    internal void MI() {}
    protected void MP() {}
}

Problema Fechado: Como se implementa um membro de interface não pública em uma classe?

Decisão: Você só pode implementar membros de interface não públicos explicitamente. Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Decisão: Nenhuma palavra-chave override permitida nos membros da interface. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

Compatibilidade binária 2 (fechado)

Considere o código a seguir no qual cada tipo está em um assembly separado

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
    override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}

Entendemos que a implementação de I1.M no C é I2.M. E se o assembly que contém I3 for alterado da seguinte maneira e recompilado

interface I3 : I1
{
    override void M() { Impl3 }
}

mas C não for recompilado. O que acontece quando o programa é executado? Uma invocação de (C as I1).M()

  1. Executa I1.M
  2. Executa I2.M
  3. Executa I3.M
  4. 2 ou 3, deterministicamente
  5. Gera algum tipo de exceção de runtime

Decisão: Lançar uma exceção (5). Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods.

Permitir partial na interface? (fechado)

Considerando que as interfaces podem ser usadas de maneiras análogas à maneira como as classes abstratas são usadas, pode ser útil declará-las partial. Isso seria particularmente útil diante dos geradores.

Proposta: Remover a restrição de idioma que impede que as interfaces e os membros das interfaces sejam declarados partial.

Decisão: Sim. Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface.

Main em uma interface? (fechado)

Problema Aberto: Um método static Main em uma interface é um candidato para ser o ponto de entrada do programa?

Decisão: Sim. Consulte https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface.

Confirmar a intenção de dar suporte a métodos públicos não virtuais (fechados)

Podemos confirmar (ou reverter) nossa decisão de permitir métodos públicos não virtuais em uma interface?

interface IA
{
    public sealed void M() { }
}

Semi-Closed Problema: (2017-04-18) Achamos que será útil, mas vamos voltar a isso. Este é um bloco com base em modelo mental.

Decisão: Sim. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods.

Um override em uma interface apresenta um novo membro? (fechado)

Há algumas maneiras de observar se uma declaração de substituição introduz um novo membro ou não.

interface IA
{
    void M(int x) { }
}
interface IB : IA
{
    override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
    static void M2()
    {
        M(y: 3); // permitted? Decision: No.
    }
    override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}

Problema aberto: Uma declaração de substituição em uma interface apresenta um novo membro? (fechado)

Em uma classe, um método de substituição é "visível" em alguns sentidos. Por exemplo, os nomes de seus parâmetros têm precedência sobre os nomes dos parâmetros no método substituído. Pode ser possível duplicar esse comportamento em interfaces, pois há sempre uma substituição mais específica. Mas queremos duplicar esse comportamento?

Além disso, é possível "substituir" um método de substituição? [Discutível]

Decisão: Nenhuma palavra-chave override permitida nos membros da interface. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.

Propriedades com um acessador privado (fechado)

Dizemos que os membros privados não são virtuais e a combinação de virtual e privada não é permitida. Mas e uma propriedade com um acessador privado?

interface IA
{
    public virtual int P
    {
        get => 3;
        private set { }
    }
}

Isso é permitido? O acessador set está aqui virtual ou não? Ele pode ser substituído quando acessível? O seguinte implementa implicitamente apenas o acessador get?

class C : IA
{
    public int P
    {
        get => 4;
        set { }
    }
}

O seguinte é presumivelmente um erro, porque IA.P.set não é virtual e também não é acessível?

class C : IA
{
    int IA.P
    {
        get => 4;
        set { } // Decision: Not valid
    }
}

Decisão: O primeiro exemplo parece válido, enquanto o último, não. Isso é resolvido de forma análoga a como ele já funciona no C#. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor

Invocações da interface de base, 2ª rodada (fechado)

Isso não foi implementado no C# 8.

Nossa "resolução" anterior de como lidar com invocações de base não fornece expressividade suficiente. Acontece que no C# e no CLR, ao contrário do Java, você precisa especificar tanto a interface que contém a declaração do método quanto o local da implementação que você deseja invocar.

Proponho a sintaxe a seguir para chamadas base em interfaces. Não estou apaixonada por ela, mas ilustra o que qualquer sintaxe deve ser capaz de expressar:

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>(I1).M(); // calls I3's implementation of I1.M
        base<I4>(I1).M(); // calls I4's implementation of I1.M
    }
    void I2.M()
    {
        base<I3>(I2).M(); // calls I3's implementation of I2.M
        base<I4>(I2).M(); // calls I4's implementation of I2.M
    }
}

Se não houver ambiguidade, você pode escrevê-lo de forma mais simples.

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>.M(); // calls I3's implementation of I1.M
        base<I4>.M(); // calls I4's implementation of I1.M
    }
}

Ou

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base(I1).M(); // calls I3's implementation of I1.M
    }
    void I2.M()
    {
        base(I2).M(); // calls I3's implementation of I2.M
    }
}

Ou

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base.M(); // calls I3's implementation of I1.M
    }
}

Decisão: Decidido sobre base(N.I1<T>).M(s), admitindo que, se tivermos uma associação de invocação, poderá haver um problema aqui mais tarde. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations

Aviso de que a struct não implementa o método padrão? (fechado)

@vancem afirma que devemos realmente considerar a criação de um aviso se uma declaração de tipo de valor não substituir algum método de interface, mesmo que herdasse uma implementação desse método de uma interface. Porque provoca compactação e mina chamadas restritas.

Decisão: Parece algo mais adequado para um analisador. Também parece que esse aviso pode ser polêmico, pois seria acionado mesmo se o método de interface padrão nunca fosse chamado e nenhuma compactação ocorresse. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method

Construtores estáticos de interface (fechados)

Quando os construtores estáticos da interface são executados? O rascunho da CLI atual propõe que ele ocorra quando o primeiro método ou campo estático é acessado. Se não houver nenhum desses, então ele nunca será executado?

[2018-10-09 A equipe de CLR propõe "Espelhar o que fazemos para valuetypes (verificar cctor no acesso a cada método de instância)"]

Decisão: Construtores estáticos também são executados na entrada para métodos de instância. Se o construtor estático não foi beforefieldinit, nesse caso, construtores estáticos são executados antes do acesso ao primeiro campo estático. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run

Reuniões de design

Notas da reunião LDM 2017-03-08Notas da reunião LDM 2017-03-21Reunião "Comportamento CLR para métodos de interface padrão" em 2017-03-23Notas da reunião LDM 2017-04-05Notas da reunião LDM 2017-04-11Notas da reunião LDM 2017-04-18Notas da reunião LDM 2017-04-19Notas da reunião LDM 2017-05-17Notas da reunião LDM 2017-05-31Notas da reunião LDM 2017-06-14Notas da reunião LDM 2018-10-17Notas da reunião LDM 2018-11-14