Interfaces em C #. Implementação implícita versus implementação explícita


632

Quais são as diferenças na implementação de interfaces implícita e explicitamente em C #?

Quando você deve usar implícito e quando você deve usar explícito?

Existem prós e / ou contras em um ou outro?


As diretrizes oficiais da Microsoft (da primeira edição do Framework Design Guidelines ) afirmam que o uso de implementações explícitas não é recomendado , pois fornece um comportamento inesperado ao código.

Eu acho que essa diretriz é muito válida em um período pré-IoC , quando você não passa as coisas como interfaces.

Alguém poderia tocar nesse aspecto também?


Leia o artigo completo em interfaces C #: planetofcoders.com/c-interfaces
Gaurav Agrawal

Sim, as interfaces explícitas devem ser evitados e abordagem mais profissional faria para implementar ISP (princípio da segregação Interface) aqui é um artigo de detalhes sobre o mesmo codeproject.com/Articles/1000374/...
Shivprasad Koirala

Respostas:


492

Implícito é quando você define sua interface através de um membro da sua classe. Explícito é quando você define métodos na sua classe na interface. Eu sei que parece confuso, mas aqui está o que eu quero dizer: IList.CopyToseria implicitamente implementado como:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

e explicitamente como:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

A diferença é que a implementação implícita permite acessar a interface através da classe que você criou, lançando a interface como essa classe e como a própria interface. A implementação explícita permite acessar a interface apenas convertendo-a como a própria interface.

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

Eu uso explícito principalmente para manter a implementação limpa ou quando preciso de duas implementações. Independentemente disso, eu raramente uso.

Estou certo de que existem mais razões para usar / não usar explícito que outras pessoas postarão.

Veja o próximo post neste tópico para um excelente raciocínio por trás de cada um.


8
Eu sei que este post é antigo, mas achei muito útil - uma coisa a observar se não estiver claro porque não era para mim que no exemplo o implícito tem a publicpalavra - chave ... caso contrário, você receberá um erro
jharr100

O CLR de Jeffrey Richter via C # 4 ed ch 13 mostra um exemplo em que a conversão não é necessária: estrutura interna SomeValueType: IComparable {private Int32 m_x; public SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType outro) {...);} Int32 IComparable.CompareTo (Objeto outro) {return CompareTo ((SomeValueType) outro); }} public static void Main () {SomeValueType v = new SomeValueType (0); Objeto o = novo Object (); Int32 n = v.CompareTo (v); // Sem boxe n = v.CompareTo (o); // erro em tempo de compilação}
Andy Dent

1
Hoje, encontrei uma situação rara que EXIGE o uso de uma interface explícita: Uma classe com um campo gerado por um construtor de interface que cria o campo como privado (Xamarin direcionando-se ao iOS, usando o storyboard do iOS). E uma interface em que fazia sentido expor esse campo (público somente leitura). Eu poderia ter alterado o nome do getter na interface, mas o nome existente era o nome mais lógico do objeto. Então, ao invés eu fiz uma implementação explícita referindo-se ao campo privado: UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
Página

1
Os horrores da programação. Bem, bem desmascarado!
Liquid Core

1
@ToolmakerSteve Outra situação (mais comum) que requer implementação explícita de pelo menos um membro da interface é a implementação de várias interfaces que possuem membros com a mesma assinatura, mas diferentes tipos de retorno. Isso pode acontecer por causa da herança de interface, como acontece com IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator()e ISet<T>.Add(T). Isso é mencionado em outra resposta .
phoog

201

A definição implícita seria apenas adicionar os métodos / propriedades, etc. exigidos pela interface diretamente à classe como métodos públicos.

A definição explícita força os membros a serem expostos apenas quando você está trabalhando com a interface diretamente, e não a implementação subjacente. Isso é preferido na maioria dos casos.

  1. Ao trabalhar diretamente com a interface, você não está reconhecendo e acoplando seu código à implementação subjacente.
  2. No caso de você já ter, digamos, uma propriedade pública Name em seu código e desejar implementar uma interface que também tenha uma propriedade Name, fazê-lo explicitamente manterá os dois separados. Mesmo se eles estivessem fazendo a mesma coisa, eu ainda delegaria a chamada explícita à propriedade Name. Você nunca sabe, pode querer alterar como o Name funciona para a classe normal e como Name, a propriedade da interface funciona posteriormente.
  3. Se você implementar uma interface implicitamente, sua classe agora expõe novos comportamentos que podem ser relevantes apenas para um cliente da interface e isso significa que você não está mantendo suas classes sucintas o suficiente (minha opinião).

2
Você faz alguns bons pontos aqui. especialmente A. Normalmente, passo minhas aulas como a interface de qualquer maneira, mas nunca pensei nisso dessa perspectiva.
mattlant 27/09/08

5
Não sei se concordo com o ponto C. Um objeto Cat pode implementar IEatable, mas Eat () é uma parte básica da coisa. Em alguns casos, você gostaria de chamar Eat () em um gato quando estiver usando o objeto 'bruto' em vez da interface IEatable, não?
LegendLength

59
Conheço alguns lugares onde Caté de fato IEatablesem objeções.
Humberto

26
Eu discordo completamente com todos os itens acima, e diria que o uso de interfaces explícitas é a receita do desastre e não OOP ou OOD por sua definição (ver a minha resposta sobre polimorfismo)
Valentin Kuzub


68

Além das excelentes respostas já fornecidas, há alguns casos em que a implementação explícita é NECESSÁRIA para que o compilador possa descobrir o que é necessário. Dê uma olhada IEnumerable<T>como um excelente exemplo que provavelmente surgirá com bastante frequência.

Aqui está um exemplo:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

Aqui, IEnumerable<string>implementa IEnumerable, portanto precisamos também. Mas espere, a versão genérica e a normal implementam funções com a mesma assinatura de método (o C # ignora o tipo de retorno para isso). Isso é completamente legal e legal. Como o compilador resolve qual usar? Obriga você a ter apenas, no máximo, uma definição implícita, e pode resolver o que for necessário.

ie

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

PS: O pequeno pedaço de indireção na definição explícita para IEnumerable funciona porque dentro da função o compilador sabe que o tipo real da variável é um StringList, e é assim que resolve a chamada da função. Um pequeno fato bacana para implementar algumas das camadas de abstração que algumas das interfaces principais do .NET parecem ter acumulado.


5
@Tassadaque: Após 2 anos, tudo o que posso dizer é "Boa pergunta". Não faço ideia, exceto, talvez, por ter copiado esse código de algo em que estava trabalhando onde estava abstract.
Matthew Scharley

4
@Tassadaque, você está correto, mas acho que o ponto do post de Matthew Scharley acima não está perdido.
Funkymushroom 07/07/16

35

Para citar Jeffrey Richter de CLR via C #
( EIMI significa E xplicit eu nterface M é todo eu MPLEMENTAÇÃO)

É extremamente importante que você entenda algumas ramificações que existem ao usar EIMIs. E por causa dessas ramificações, você deve evitar os EIMIs o máximo possível. Felizmente, interfaces genéricas ajudam a evitar EIMIs um pouco. Mas ainda pode haver momentos em que você precisará usá-los (como implementar dois métodos de interface com o mesmo nome e assinatura). Aqui estão os grandes problemas com os EIMIs:

  • Não há documentação explicando como um tipo implementa especificamente um método EIMI e não há suporte ao Microsoft Visual Studio IntelliSense .
  • As instâncias de tipo de valor são colocadas em caixa quando convertidas para uma interface.
  • Um EIMI não pode ser chamado por um tipo derivado.

Se você usar uma referência de interface, QUALQUER cadeia virtual pode ser explicitamente substituída pelo EIMI em qualquer classe derivada e quando um objeto desse tipo é convertido para a interface, sua cadeia virtual é ignorada e a implementação explícita é chamada. Isso é tudo menos polimorfismo.

Os EIMIs também podem ser usados ​​para ocultar membros de interface não fortemente tipados das implementações básicas do Framework Interfaces, como IEnumerable <T>, para que sua classe não exponha diretamente um método não fortemente tipado, mas é sintática correta.


2
A reimplementação de interfaces, embora legal, geralmente é duvidosa. Implementações explícitas geralmente devem ser encadeadas para um método virtual diretamente, ou através da lógica de agrupamento, que deve ser vinculativa para as classes derivadas. Embora seja certamente possível usar interfaces de maneiras hostis às convenções apropriadas de OOP, isso não significa que elas não possam ser usadas melhor.
Supercat

4
@Valentin O que EIMI e IEMI representam?
Dzienny

Implementação explícita do método de interface
scobi

5
-1 para "Geralmente vejo interfaces como recurso OOP Semi (na melhor das hipóteses), fornece herança, mas não fornece polimorfismo real". Eu discordo fortemente. Muito pelo contrário, as interfaces são sobre polimorfismo e não são principalmente sobre herança. Eles atribuem várias classificações a um tipo. Evite o IEMI, quando puder, e delegue, como @supercat sugeriu, quando não puder. Não evite interfaces.
Aluan Haddad 04/02

1
"Um EIMI não pode ser chamado por um tipo derivado." << O QUE? Isso não é verdade. Se eu implementar explicitamente uma interface em um tipo, derivei desse tipo, ainda posso convertê-lo na interface para chamar o método, exatamente como eu precisaria para o tipo em que ele foi implementado. Portanto, não tenho certeza do que você está falando. Mesmo dentro do tipo derivado, posso simplesmente converter "this" na interface em questão para alcançar o método explicitamente implementado.
Triynko

34

Razão # 1

Costumo usar a implementação explícita da interface quando quero desencorajar a "programação para uma implementação" ( Princípios de Design dos Padrões de Design ).

Por exemplo, em um aplicativo Web baseado em MVP :

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

Agora, outra classe (como um apresentador ) tem menos probabilidade de depender da implementação do StandardNavigator e mais provável da interface do INavigator (já que a implementação precisaria ser convertida em uma interface para fazer uso do método Redirecionar).

Razão # 2

Outro motivo para eu seguir uma implementação explícita da interface seria manter a interface "padrão" de uma classe mais limpa. Por exemplo, se eu estivesse desenvolvendo um controle de servidor ASP.NET , poderia querer duas interfaces:

  1. A interface principal da classe, usada pelos desenvolvedores de páginas da web; e
  2. Uma interface "oculta" usada pelo apresentador que desenvolvo para lidar com a lógica do controle

Um exemplo simples segue. É um controle de caixa de combinação que lista clientes. Neste exemplo, o desenvolvedor da página da web não está interessado em preencher a lista; em vez disso, eles apenas desejam selecionar um cliente por GUID ou obter o GUID do cliente selecionado. Um apresentador preencheria a caixa no carregamento da primeira página e esse apresentador é encapsulado pelo controle.

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

O apresentador preenche a fonte de dados e o desenvolvedor da página da web nunca precisa estar ciente de sua existência.

Mas não é uma bala de canhão de prata

Eu não recomendaria sempre o emprego de implementações explícitas de interface. Esses são apenas dois exemplos em que podem ser úteis.


19

Além dos outros motivos já mencionados, esta é a situação em que uma classe está implementando duas interfaces diferentes que possuem uma propriedade / método com o mesmo nome e assinatura.

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

Esse código compila e executa OK, mas a propriedade Title é compartilhada.

Claramente, gostaríamos que o valor de Title retornasse dependesse de estarmos tratando a Class1 como um livro ou uma pessoa. É quando podemos usar a interface explícita.

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

Observe que as definições explícitas da interface são inferidas como Públicas - e, portanto, você não pode declara-las públicas (ou não) explicitamente.

Observe também que você ainda pode ter uma versão "compartilhada" (como mostrado acima), mas, embora isso seja possível, a existência dessa propriedade é questionável. Talvez possa ser usado como uma implementação padrão do Title - para que o código existente não precise ser modificado para converter Class1 no IBook ou IPerson.

Se você não definir o Título "compartilhado" (implícito), os consumidores da Classe1 deverão converter explicitamente as instâncias da Classe1 para o IBook ou IPerson primeiro - caso contrário, o código não será compilado.


16

Eu uso a implementação explícita da interface na maioria das vezes. Aqui estão os principais motivos.

A refatoração é mais segura

Ao alterar uma interface, é melhor que o compilador possa verificá-la. Isso é mais difícil com implementações implícitas.

Dois casos comuns vêm à mente:

  • Adicionando uma função a uma interface, onde uma classe existente que implementa essa interface já possui um método com a mesma assinatura que a nova . Isso pode levar a um comportamento inesperado e me incomodou várias vezes. É difícil "ver" durante a depuração, porque essa função provavelmente não está localizada com os outros métodos de interface do arquivo (o problema de auto-documentação mencionado abaixo).

  • Removendo uma função de uma interface . Os métodos implementados implicitamente serão código morto subitamente, mas os métodos implementados explicitamente serão capturados por erro de compilação. Mesmo que seja bom manter o código morto, quero ser forçado a revisá-lo e promovê-lo.

É lamentável que o C # não tenha uma palavra-chave que nos obriga a marcar um método como uma implementação implícita, para que o compilador possa fazer verificações extras. Os métodos virtuais não apresentam nenhum dos problemas acima, devido ao uso obrigatório de 'substituir' e 'novo'.

Nota: para interfaces fixas ou que mudam raramente (normalmente das APIs do fornecedor), isso não é um problema. Para minhas próprias interfaces, porém, não posso prever quando / como elas mudarão.

É auto-documentado

Se eu vir 'public bool Execute ()' em uma classe, será necessário um trabalho extra para descobrir que faz parte de uma interface. Alguém provavelmente terá que comentar isso ou colocá-lo em um grupo de outras implementações de interface, tudo em uma região ou comentário de agrupamento dizendo "implementação do ITask". Obviamente, isso só funciona se o cabeçalho do grupo não estiver fora da tela.

Visto que: 'bool ITask.Execute ()' é claro e inequívoco.

Separação clara da implementação da interface

Penso nas interfaces como sendo mais "públicas" que os métodos públicos, porque são criadas para expor apenas um pouco da área superficial do tipo concreto. Eles reduzem o tipo a uma capacidade, um comportamento, um conjunto de características, etc. E na implementação, acho útil manter essa separação.

Enquanto olho o código de uma classe, quando encontro implementações explícitas de interface, meu cérebro muda para o modo "contrato de código". Freqüentemente, essas implementações simplesmente encaminham para outros métodos, mas às vezes realizam verificação extra de estado / parâmetro, conversão de parâmetros recebidos para melhor atender aos requisitos internos ou mesmo tradução para fins de versão (ou seja, várias gerações de interfaces, todas implementadas em implementações comuns).

(Eu percebo que os públicos também são contratos de código, mas as interfaces são muito mais fortes, especialmente em uma base de código orientada por interface em que o uso direto de tipos concretos geralmente é um sinal de código somente interno.)

Relacionado: Razão 2 acima por Jon .

E assim por diante

Além das vantagens já mencionadas em outras respostas aqui:

Problemas

Nem tudo é diversão e felicidade. Existem alguns casos em que fico com implícitos:

  • Tipos de valor, porque isso exigirá boxe e menor desempenho. Esta não é uma regra estrita e depende da interface e de como ela deve ser usada. IComparável? Implícito. IFormattable? Provavelmente explícito.
  • Interfaces de sistema triviais que possuem métodos frequentemente chamados diretamente (como IDisposable.Dispose).

Além disso, pode ser difícil fazer a transmissão quando, na verdade, você tem o tipo concreto e deseja chamar um método de interface explícito. Eu lido com isso de duas maneiras:

  1. Adicione públicos e envie os métodos de interface para eles para a implementação. Normalmente acontece com interfaces mais simples ao trabalhar internamente.
  2. (Meu método preferido) Adicione um public IMyInterface I { get { return this; } }(que deve ser incorporado) e ligue foo.I.InterfaceMethod(). Se várias interfaces que precisam dessa capacidade, expanda o nome além de I (na minha experiência, é raro que eu tenha essa necessidade).

8

Se você implementar explicitamente, você poderá referenciar apenas os membros da interface através de uma referência que seja do tipo da interface. Uma referência que é o tipo da classe de implementação não expõe esses membros da interface.

Se sua classe de implementação não for pública, exceto pelo método usado para criar a classe (que pode ser um contêiner de fábrica ou IoC ) e exceto pelos métodos de interface (é claro), não vejo vantagem em implementar explicitamente interfaces.

Caso contrário, a implementação explícita de interfaces garante que as referências à sua classe de implementação concreta não sejam usadas, permitindo que você altere essa implementação posteriormente. "Garante que", suponho, é a "vantagem". Uma implementação bem fatorada pode fazer isso sem implementação explícita.

A desvantagem, na minha opinião, é que você encontrará tipos de conversão de / para a interface no código de implementação que tem acesso a membros não públicos.

Como muitas coisas, a vantagem é a desvantagem (e vice-versa). A implementação explícita de interfaces garantirá que seu código de implementação de classe concreto não seja exposto.


1
Boa resposta, Bill. As outras respostas foram ótimas, mas você forneceu alguns pontos de vista objetivos adicionais (além de suas opiniões) que facilitaram a compreensão. Como a maioria das coisas, existem prós e contras com implementações implícitas ou explícitas; portanto, você só precisa usar a melhor para o seu cenário ou caso de uso específico. Eu diria que aqueles que tentam descobrir melhor se beneficiarão da leitura de sua resposta.
Daniel Águia

6

Uma implementação implícita da interface é onde você tem um método com a mesma assinatura da interface.

Uma implementação explícita da interface é onde você declara explicitamente a qual interface o método pertence.

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN: implementações implícitas e explícitas da interface


6

Todo membro da classe que implementa uma interface exporta uma declaração que é semanticamente semelhante à maneira como as declarações da interface do VB.NET são gravadas, por exemplo,

Public Overridable Function Foo() As Integer Implements IFoo.Foo

Embora o nome do membro da classe geralmente corresponda ao nome do membro da interface, e o membro da classe geralmente seja público, nada disso é necessário. Pode-se também declarar:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

Nesse caso, a classe e seus derivados teriam permissão para acessar um membro da classe usando o nome IFoo_Foo, mas o mundo externo só seria capaz de acessar esse membro em particular, transmitindo para IFoo. Essa abordagem geralmente é boa nos casos em que um método de interface terá um comportamento especificado em todas as implementações, mas um comportamento útil em apenas algumas [por exemplo, o comportamento especificado para o IList<T>.Addmétodo de uma coleção somente leitura é o lançamento NotSupportedException]. Infelizmente, a única maneira correta de implementar a interface em C # é:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

Não é tão legal.


2

Um uso importante da implementação explícita da interface é quando é necessário implementar interfaces com visibilidade mista .

O problema e a solução estão bem explicados no artigo Interface interna do C # .

Por exemplo, se você deseja proteger o vazamento de objetos entre as camadas do aplicativo, essa técnica permite especificar uma visibilidade diferente dos membros que podem causar o vazamento.


2

As respostas anteriores explicam por que a implementação explícita de uma interface em C # pode ser preferível (por razões principalmente formais). No entanto, há uma situação em que a implementação explícita é obrigatória : Para evitar vazamento do encapsulamento quando a interface não é public, mas a classe de implementação é public.

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

O vazamento acima é inevitável porque, de acordo com a especificação do C # , "Todos os membros da interface têm acesso público implicitamente". Como conseqüência, implementações implícitas também devem fornecer publicacesso, mesmo que a própria interface seja por exemplo internal.

A implementação implícita da interface em C # é uma grande conveniência. Na prática, muitos programadores o usam o tempo todo / em qualquer lugar sem consideração adicional. Isso leva a superfícies de tipo confuso, na melhor das hipóteses, e a encapsulamento vazado, na pior das hipóteses. Outros idiomas, como o F #, nem mesmo permitem isso .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.