¿Por qué implementar la interfaz explícitamente?


122

Entonces, ¿qué es exactamente un buen caso de uso para implementar una interfaz explícitamente?

¿Es solo para que las personas que usan la clase no tengan que mirar todos esos métodos / propiedades en intellisense?

Respuestas:


146

Si implementa dos interfaces, ambas con el mismo método y diferentes implementaciones, entonces debe implementar explícitamente.

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

    void IDoItSlow.Go()
    {
    }
}

Sí, este es exactamente un caso que EIMI resuelve. Y otros puntos están cubiertos por la respuesta "Michael B".
cifrado el

10
Excelente ejemplo Me encantan los nombres de interfaz / clase! :-)
Brian Rogers

11
No me encanta, ¿dos métodos con la misma firma en una clase que hacen cosas muy diferentes? Esto es extremadamente peligroso y es muy probable que cause estragos en cualquier gran desarrollo. Si tiene un código como este, diría que su análisis y diseño están a la altura del wahzoo.
Mick

44
@ Mike Las interfaces pueden pertenecer a alguna API o a dos API diferentes. Quizás el amor es un poco exagerado aquí, pero al menos me alegraría de que haya una implementación explícita disponible.
TobiMcNamobi

@BrianRogers Y los nombres de los métodos también ;-)
Sнаđошƒаӽ

66

Es útil ocultar el miembro no preferido. Por ejemplo, si implementa ambos IComparable<T>y IComparablegeneralmente es mejor ocultar la IComparablesobrecarga para no dar a la gente la impresión de que puede comparar objetos de diferentes tipos. Del mismo modo, algunas interfaces no son compatibles con CLS IConvertible, por lo que si no implementa explícitamente la interfaz, los usuarios finales de idiomas que requieren el cumplimiento de CLS no pueden usar su objeto. (Lo cual sería muy desastroso si los implementadores de BCL no ocultaran los miembros IConvertible de las primitivas :))

Otra nota interesante es que normalmente el uso de una construcción de este tipo significa que la estructura que implementa explícitamente una interfaz solo puede invocarlos mediante un cuadro al tipo de interfaz. Puede evitar esto utilizando restricciones genéricas ::

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

No boxeará un int cuando le pase uno.


1
Tienes un error tipográfico en tu restricción. Para clerificar, el código anterior funciona. Debe estar en la declaración inicial de la firma del método en la interfaz. La publicación original no especificó esto. Además, el formato apropiado es "void SomeMehtod <T> (T obj) donde T: IConvertible. Tenga en cuenta que hay un punto extra entre") "y" where "que no debería estar allí. Todavía, +1 para el uso inteligente de genéricos para evitar el boxeo caro.
Zack Jannsen

1
Hola Michael B. Entonces, ¿por qué en la implementación de una cadena en .NET hay una implementación pública de IComparable: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (el valor es String)) {lanzar una nueva ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (esto, valor (String), StringComparison.CurrentCulture); } ¡Gracias!
zzfima

stringexistía antes de los genéricos y esta práctica estaba de moda. Cuando apareció .net 2 no querían romper la interfaz pública de la, stringasí que lo dejaron como estaba con la protección en su lugar.
Michael B

37

Algunas razones adicionales para implementar una interfaz explícitamente:

compatibilidad con versiones anteriores : en caso de que la ICloneableinterfaz cambie, los miembros de la clase de método de implementación no tienen que cambiar sus firmas de método.

código más limpio : habrá un error de compilación si el Clonemétodo se elimina de ICloneable, sin embargo, si implementa el método implícitamente, puede terminar con métodos públicos 'huérfanos' no utilizados

escritura fuerte : para ilustrar la historia de supercat con un ejemplo, este sería mi código de muestra preferido, la implementación ICloneableexplícitamente permite Clone()escribir fuertemente cuando lo llamas directamente como MyObjectmiembro de la instancia:

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

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

Para eso, preferiría interface ICloneable<out T> { T Clone(); T self {get;} }. Tenga en cuenta que no existe una ICloneable<T>restricción deliberada sobre T. Si bien un objeto generalmente solo puede clonarse de manera segura si su base puede serlo, es posible que desee derivar de una clase base que podría clonarse de manera segura un objeto de una clase que no puede. Para permitir eso, recomendaría que las clases heredables no expongan un método de clonación pública. En cambio, tenga clases heredables con un protectedmétodo de clonación y clases selladas que se deriven de ellas y expongan la clonación pública.
supercat

Claro que sería mejor, excepto que no hay una versión covariante de ICloneable en el BCL, por lo que tendrías que crear una, ¿verdad?
Wiebe Tijsma

Los 3 ejemplos dependen de situaciones poco probables y rompen las mejores prácticas de interfaces.
MickyD

13

Otra técnica útil es hacer que la implementación pública de un método de una función devuelva un valor más específico que el especificado en una interfaz.

Por ejemplo, un objeto puede implementar ICloneable, pero aún así su Clonemétodo públicamente visible devuelve su propio tipo.

Del mismo modo, un IAutomobileFactorypodría tener un Manufacturemétodo que devuelve un Automobile, pero a FordExplorerFactory, que implementa IAutomobileFactory, puede hacer que su Manufacturemétodo devuelva un FordExplorer(que se deriva de Automobile). Código que sabe que tiene unFordExplorerFactory podría usar FordExplorerpropiedades específicas en un objeto devuelto por a FordExplorerFactorysin tener que escribirlo, mientras que el código que simplemente sabía que tenía algún tipo IAutomobileFactorysimplemente trataría su retorno como un Automobile.


2
+1 ... este sería mi uso preferido de implementaciones explícitas de interfaz, aunque una pequeña muestra de código probablemente sería un poco más clara que esta historia :)
Wiebe Tijsma

7

También es útil cuando tiene dos interfaces con el mismo nombre de miembro y firma, pero desea cambiar su comportamiento dependiendo de cómo se use. (No recomiendo escribir 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
}

6060
El ejemplo más extraño de OO que he visto:public class Animal : Cat, Dog
mbx

38
@mbx: Si Animal también implementara Parrot, sería un animal que se transforma en Polly.
RenniePet

3
Recuerdo un personaje de dibujos animados que era un gato en un extremo y un perro en el otro extremo ;-)
George Birbilis

2
hubo una serie de televisión en los años 80 ... "Manimal" ... donde un hombre podía transformarse en ... oh no importa
bkwdesign

Los Morkies se parecen a los gatos, y también son como los gatos ninja.
samis

6

Puede mantener limpia la interfaz pública para implementar explícitamente una interfaz, es decir, su Fileclase podría implementar IDisposableexplícitamente y proporcionar un método público Close()que podría tener más sentido para un consumidor que Dispose().

F # solo ofrece una implementación explícita de la interfaz, por lo que siempre debe enviar a la interfaz particular para acceder a su funcionalidad, lo que hace que el uso de la interfaz sea muy explícito (sin juego de palabras).


Creo que la mayoría de las versiones de VB también solo admiten definiciones de interfaz explícitas.
Gabe

1
@Gabe: es más sutil que eso para VB: los nombres y la accesibilidad de los miembros que implementan una interfaz están separados de lo que indica que son parte de la implementación. Entonces, en VB, y mirando la respuesta de @ Iain (respuesta principal actual), podría implementar IDoItFast e IDoItSlow con los miembros públicos "GoFast" y "GoSlow", respectivamente.
Damien_The_Unbeliever

2
No me gusta su ejemplo particular (en mi humilde opinión, las únicas cosas que deberían esconderse Disposeson aquellas que nunca requerirán limpieza); un mejor ejemplo sería algo así como la implementación de una colección inmutable IList<T>.Add.
supercat

5

Si tiene una interfaz interna y no desea implementar públicamente a los miembros de su clase, los implementará explícitamente. Las implementaciones implícitas deben ser públicas.


Ok, eso explica por qué el proyecto no se compilaría con una implementación implícita.
pauloya

4

Otra razón para la implementación explícita es la mantenibilidad .

Cuando una clase se "ocupa" - sí, sucede, no todos tenemos el lujo de refactorizar el código de otros miembros del equipo - luego tener una implementación explícita deja en claro que existe un método para satisfacer un contrato de interfaz.

Por lo tanto, mejora la "legibilidad" del código.


En mi humilde opinión, es más importante decidir si la clase debe o no exponer el método a sus clientes. Eso impulsa a ser explícito o implícito. Para documentar que varios métodos van de la mano, en este caso porque satisfacen un contrato, para eso #regiones, con una cadena de título apropiada. Y un comentario sobre el método.
ToolmakerSteve

1

Un ejemplo diferente es dado por System.Collections.Immutable , en el que los autores optaron por utilizar la técnica para preservar una API familiar para los tipos de colección, mientras que eliminan las partes de la interfaz que no tienen ningún significado para sus nuevos tipos.

Concretamente, ImmutableList<T>los implementos IList<T>y por lo tanto ICollection<T>( con el fin de permitir ImmutableList<T>a utilizar más fácilmente con código anterior), sin embargo, void ICollection<T>.Add(T item)no tiene sentido para un ImmutableList<T>: desde la adición de un elemento a una lista inmutable no debe cambiar la lista existente, ImmutableList<T>también se deriva de IImmutableList<T>que IImmutableList<T> Add(T item)se puede utilizar para listas inmutables

Por lo tanto, en el caso de Add, las implementaciones ImmutableList<T>terminan teniendo el siguiente aspecto:

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

En el caso de interfaces explícitamente definidas, todos los métodos son privados de forma automática, no puede darles acceso al modificador de acceso. Suponer:

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 los métodos son automáticamente privados": esto no es técnicamente correcto, b / c si de hecho fueran privados, entonces no serían invocables en absoluto, emitiendo o no.
samis

0

Así es como podemos crear una interfaz explícita: si tenemos 2 interfaces y ambas tienen el mismo método y una sola clase hereda estas 2 interfaces, entonces, cuando llamamos a un método de interfaz, el compilador se confundió a qué método llamar, para que podamos maneje este problema usando la interfaz explícita. Aquí hay un ejemplo que he dado a continuación.

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();    
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.