Por que implementar interface explicitamente?


122

Então, qual é exatamente um bom caso de uso para implementar uma interface explicitamente?

É apenas para que as pessoas que usam a classe não precisem examinar todos esses métodos / propriedades no intellisense?

Respostas:


146

Se você implementar duas interfaces, ambas com o mesmo método e implementações diferentes, precisará implementá-lo explicitamente.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Sim, exatamente este é um caso que o EIMI resolve. E outros pontos são abordados pela resposta "Michael B".
criptografado

10
Excelente exemplo. Ame os nomes de interface / classe! :-)
Brian Rogers

11
Eu não amo isso, dois métodos com a mesma assinatura em uma classe que fazem coisas muito diferentes? Isso é extremamente perigoso e provavelmente causará estragos em qualquer grande desenvolvimento. Se você tiver um código como esse, eu diria que sua análise e design estão no nível máximo.
Mick

4
@ Mike As interfaces podem pertencer a alguma API ou a duas APIs diferentes. Talvez o amor seja um pouco exagerado aqui, mas eu ficaria feliz em saber que a implementação explícita está disponível.
TobiMcNamobi

@BrianRogers E os nomes de métodos também ;-)
Sнаđошƒаӽ

66

É útil ocultar o membro não preferido. Por exemplo, se você implementar os dois IComparable<T>e IComparablegeralmente é melhor esconder a IComparablesobrecarga para não dar às pessoas a impressão de que você pode comparar objetos de tipos diferentes. Da mesma forma, algumas interfaces não são compatíveis com CLS, como IConvertible, portanto, se você não implementar explicitamente a interface, os usuários finais de idiomas que exigem conformidade com CLS não poderão usar seu objeto. (O que seria muito desastroso se os implementadores da BCL não ocultassem os membros IConvertible das primitivas :))

Outra observação interessante é que normalmente o uso dessa construção significa que a estrutura que implementa explicitamente uma interface só pode invocá-la encaixotando no tipo de interface. Você pode contornar isso usando restrições genéricas:

void SomeMethod<T>(T obj) where T:IConvertible

Não encaixotará um int quando você passar um para ele.


1
Você tem um erro de digitação em sua restrição. Para clerificar, o código acima funciona. Ele precisa estar na declaração inicial da assinatura do método na Interface. A postagem original não especificou isso. Além disso, o formato adequado é "void SomeMehtod <T> (T obj) onde T: IConvertible. Observe que há dois pontos extras entre") "e" where "que não deveriam estar lá. Ainda, +1 para uso inteligente de genéricos para evitar boxe caro.
Zack Jannsen

1
Oi Michael B. Então, por que na implementação de string no .NET há implementação pública de IComparable: public int CompareTo (Valor do objeto) {if (value == null) {return 1; } if (! (o valor é String)) {lança nova ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } retornar String.Compare (este valor (String), StringComparison.CurrentCulture); } Obrigado!
Zzfima 30/06

stringexistia antes dos genéricos e essa prática estava em voga. Quando o .net 2 apareceu, eles não queriam quebrar a interface pública do sistema string, deixando-o como estava com a salvaguarda em vigor.
Michael B

37

Alguns motivos adicionais para implementar uma interface explicitamente:

compatibilidade com versões anteriores : caso a ICloneableinterface seja alterada, os membros da classe de método de implementação não precisam alterar suas assinaturas de método.

código mais limpo : haverá um erro do compilador se o Clonemétodo for removido do ICloneable, no entanto, se você implementar o método implicitamente, poderá acabar com métodos públicos 'órfãos' não utilizados

digitação forte : para ilustrar a história de supercat com um exemplo, esse seria o meu código de exemplo preferido, a implementação ICloneableexplicitamente permite Clone()que seja fortemente digitada quando você a chama diretamente como MyObjectmembro da instância:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Para aquele, eu preferiria interface ICloneable<out T> { T Clone(); T self {get;} }. Observe que não há deliberadamente nenhuma ICloneable<T>restrição em T. Embora um objeto geralmente possa ser clonado com segurança apenas se sua base puder ser, pode-se derivar de uma classe base que pode ser clonada com segurança em um objeto de uma classe que não pode. Para permitir isso, recomendo que as classes herdáveis ​​exponham um método de clone público. Em vez disso, tenha classes herdáveis ​​com um protectedmétodo de clonagem e classes seladas que derivam delas e expõem a clonagem pública.
Supercat

Claro que seria melhor, exceto que não há uma versão covariante do ICloneable na BCL, então você teria que criar uma certa?
Wiebe Tijsma

Todos os três exemplos dependem de situações improváveis ​​e recomendam as melhores práticas de interfaces.
MickyD 10/0918

13

Outra técnica útil é fazer com que a implementação pública de um método de uma função retorne um valor mais específico do que o especificado em uma interface.

Por exemplo, um objeto pode ser implementado ICloneable, mas ainda assim seu Clonemétodo publicamente visível retorna seu próprio tipo.

Da mesma forma, um IAutomobileFactorypode ter um Manufacturemétodo que retorna um Automobile, mas a FordExplorerFactory, que implementa IAutomobileFactory, pode ter seu Manufacturemétodo retornando a FordExplorer(que deriva de Automobile). O código que sabe que ele FordExplorerFactorypode usar FordExplorerpropriedades específicas em um objeto retornado por a FordExplorerFactorysem ter que digitar, enquanto o código que apenas sabia que ele tinha algum tipo de IAutomobileFactoryação simplesmente lidaria com seu retorno como um Automobile.


2
+1 ... isso seria meu uso preferencial de implementações de interfaces explícitas, embora um exemplo de código pequena seria provavelmente um pouco mais claro do que essa história :)
Wiebe Tijsma

7

Também é útil quando você tem duas interfaces com o mesmo nome e assinatura de membro, mas deseja alterar o comportamento dele, dependendo de como é usado. (Eu não recomendo escrever código como este):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
estranha exemplo relacionado OO que eu já vi:public class Animal : Cat, Dog
mbx

38
@mbx: Se o Animal também implementasse o Parrot, seria um animal que se transformava em Polly.
RenniePet

3
Lembro-me de um personagem de desenho animado que era um gato em uma extremidade e um cão na outra extremidade ;-)
George Birbilis

2
houve uma série de TV nos anos 80 .. 'Manimal' .. onde um homem poderia se transformar em um .. Oh, esqueça
bkwdesign

Os Morkies parecem gatos, e também são gatos ninjas.
21417 Samis

6

Ele pode manter a interface pública mais limpa para implementar explicitamente uma interface, ou seja, sua Fileclasse pode implementar IDisposableexplicitamente e fornecer um método público Close()que faça mais sentido para o consumidor do que Dispose().

O F # oferece apenas implementação explícita da interface, portanto você sempre deve transmitir para a interface específica para acessar sua funcionalidade, o que contribui para um uso muito explícito (sem trocadilhos) da interface.


Eu acho que a maioria das versões do VB também suportava apenas definições explícitas de interface.
Gabe

1
@ Gabe - é mais sutil que o do VB - a nomeação e acessibilidade dos membros que implementam uma interface são separadas da indicação de que fazem parte da implementação. Assim, no VB, e observando a resposta de @ Iain (resposta atual atual), você pode implementar o IDoItFast e o IDoItSlow com os membros públicos "GoFast" e "GoSlow", respectivamente.
Damien_The_Unbeliever

2
Eu não gosto do seu exemplo em particular (IMHO, as únicas coisas que devem esconder Disposesão aquelas que nunca exigirão limpeza); um exemplo melhor seria algo como a implementação de uma coleção imutável de IList<T>.Add.
Supercat 23/10

5

Se você possui uma interface interna e não deseja implementar publicamente os membros de sua classe, você os implementaria explicitamente. Implementações implícitas devem ser públicas.


Ok, isso explica por que o projeto não seria compilado com uma implementação implícita.
pauloya

4

Outro motivo para implementação explícita é a manutenção .

Quando uma classe fica "ocupada" - sim, acontece, nem todos temos o luxo de refatorar o código de outros membros da equipe - então ter uma implementação explícita deixa claro que existe um método para satisfazer um contrato de interface.

Por isso, melhora a "legibilidade" do código.


IMHO é mais importante decidir se a classe deve ou não estar expondo o método a seus clientes. Isso determina se é explícito ou implícito. Documentar que vários métodos pertencem um ao outro, neste caso porque eles satisfazem um contrato - é o que #regioné necessário, com uma sequência de títulos apropriada. E um comentário sobre o método.
Página

1

Um exemplo diferente é dado por System.Collections.Immutable, no qual os autores optaram por usar a técnica para preservar uma API familiar para os tipos de coleção, raspando as partes da interface que não têm significado para seus novos tipos.

Concretamente, ImmutableList<T>implementos IList<T>e, portanto, ICollection<T>( a fim de permitir ImmutableList<T>a ser usada mais facilmente com código legado), mas void ICollection<T>.Add(T item)não faz sentido para um ImmutableList<T>: desde a adição de um elemento de uma lista imutável não deve alterar a lista existente, ImmutableList<T>decorre também de IImmutableList<T>quem IImmutableList<T> Add(T item)pode ser usado para listas imutáveis.

Assim, no caso de Add, as implementações ImmutableList<T>acabam tendo a seguinte aparência:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

No caso de interfaces explicitamente definidas, todos os métodos são automaticamente privados, você não pode dar acesso ao modificador de acesso público. Suponha:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"todos os métodos são automaticamente privados" - isso não é tecnicamente correto, porque eles eram de fato privados, então eles não seriam chamados, lançando ou não.
samis

0

É assim que podemos criar uma interface explícita: se tivermos 2 interfaces e ambas tiverem o mesmo método e uma única classe herdar essas 2 interfaces, portanto, quando chamamos um método de interface, o compilador ficou confuso com o método a ser chamado, para que possamos gerencie esse problema usando a interface explícita. Aqui está um exemplo que eu dei abaixo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
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.