C # Interfaces. Implementación implícita versus implementación explícita


632

¿Cuáles son las diferencias en la implementación de interfaces implícita y explícitamente en C #?

¿Cuándo debería usar implícito y cuándo debería usar explícito?

¿Hay ventajas o desventajas para uno u otro?


Las pautas oficiales de Microsoft (de la primera edición de las Pautas de diseño de marcos ) establecen que no se recomienda el uso de implementaciones explícitas , ya que le da al código un comportamiento inesperado.

Creo que esta directriz es muy válida en un tiempo anterior a IoC , cuando no pasas las cosas como interfaces.

¿Alguien podría tocar ese aspecto también?


Lea el artículo completo en las interfaces de C #: planetofcoders.com/c-interfaces
Gaurav Agrawal

Sí, se deben evitar las interfaces explícitas y se necesitaría un enfoque más profesional para implementar ISP (principio de segregación de interfaz). Aquí hay un artículo detallado sobre el mismo codeproject.com/Articles/1000374/…
Shivprasad Koirala

Respuestas:


492

Implícito es cuando define su interfaz a través de un miembro de su clase. Explícito es cuando define métodos dentro de su clase en la interfaz. Sé que suena confuso, pero esto es lo que quiero decir: IList.CopyTose implementaría implícitamente como:

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

y explícitamente como:

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

La diferencia es que la implementación implícita le permite acceder a la interfaz a través de la clase que creó al convertir la interfaz como esa clase y como la interfaz misma. La implementación explícita le permite acceder a la interfaz solo convirtiéndola como la interfaz misma.

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

Utilizo explícitamente principalmente para mantener limpia la implementación, o cuando necesito dos implementaciones. De todos modos, rara vez lo uso.

Estoy seguro de que hay más razones para usar / no usar explícitamente que otros publicarán.

Vea la próxima publicación en este hilo para un excelente razonamiento detrás de cada uno.


8
Sé que esta publicación es antigua, pero la encontré muy útil: una cosa a tener en cuenta si no está clara porque no fue para mí fue que en el ejemplo, la implícita tiene la publicpalabra clave ... de lo contrario, obtendrá un error
jharr100

El CLR de Jeffrey Richter a través de C # 4 ed ch 13 muestra un ejemplo en el que no se necesita conversión: estructura interna SomeValueType: IComparable {private Int32 m_x; public SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType other) {...);} Int32 IComparable.CompareTo (Object other) {return CompareTo ((SomeValueType) other); }} public static void Main () {SomeValueType v = new SomeValueType (0); Objeto o = nuevo Objeto (); Int32 n = v.CompareTo (v); // Sin boxeo n = v.CompareTo (o); // error en tiempo de compilación}
Andy Dent

1
Hoy me encontré con una situación rara que REQUIERE el uso de una interfaz explícita: una clase con un campo generado por un generador de interfaz que crea el campo como privado (Xamarin apunta a iOS, usando el storyboard de iOS). Y una interfaz en la que tenía sentido exponer ese campo (solo lectura pública). Podría haber cambiado el nombre del captador en la interfaz, pero el nombre existente era el nombre más lógico para el objeto. Así que en vez hice una implementación explícita referencia al ámbito privado: UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
ToolmakerSteve

1
Los horrores de la programación. Bueno, bien desacreditado!
Liquid Core

1
@ToolmakerSteve otra situación (bastante más común) que requiere la implementación explícita de al menos un miembro de la interfaz es implementar múltiples interfaces que tienen miembros con la misma firma pero diferentes tipos de retorno. Esto puede suceder debido a la herencia de interfaces, como lo hace con IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator()y ISet<T>.Add(T). Esto se menciona en otra respuesta .
phoog

201

La definición implícita sería simplemente agregar los métodos / propiedades, etc., exigidos por la interfaz directamente a la clase como métodos públicos.

La definición explícita obliga a los miembros a exponerse solo cuando está trabajando con la interfaz directamente, y no con la implementación subyacente. Esto se prefiere en la mayoría de los casos.

  1. Al trabajar directamente con la interfaz, no está reconociendo y acoplando su código a la implementación subyacente.
  2. En el caso de que ya tenga, por ejemplo, un Nombre de propiedad pública en su código y desee implementar una interfaz que también tenga una propiedad de Nombre, al hacerlo explícitamente los mantendrá separados. Incluso si estuvieran haciendo lo mismo, todavía delegaría la llamada explícita a la propiedad Nombre. Nunca se sabe, es posible que desee cambiar cómo funciona Nombre para la clase normal y cómo Nombre, la propiedad de interfaz funciona más adelante.
  3. Si implementa una interfaz implícitamente, su clase ahora expone nuevos comportamientos que podrían ser relevantes solo para un cliente de la interfaz y eso significa que no mantiene sus clases lo suficientemente sucintas (mi opinión).

2
Haces algunos buenos puntos aquí. especialmente A. Por lo general, paso mis clases como la interfaz de todos modos, pero nunca pensé en eso desde esa perspectiva.
Mattlant

55
No estoy seguro de estar de acuerdo con el punto C. Un objeto Cat podría implementar IEatable pero Eat () es una parte básica de la cosa. Habría casos en los que desearía simplemente llamar a Eat () en un Cat cuando está utilizando el objeto 'en bruto' en lugar de a través de la interfaz IEatable, ¿no?
LegendLength

59
Sé de algunos lugares donde Catestá IEatablesin objeciones.
Humberto

26
Estoy completamente en desacuerdo con todo lo anterior, y diría que usar interfaces explícitas es la receta del desastre y no OOP o OOD por su definición (vea mi respuesta sobre el polimorfismo)
Valentin Kuzub


68

Además de las excelentes respuestas ya proporcionadas, hay algunos casos en los que se REQUIERE una implementación explícita para que el compilador pueda determinar qué se requiere. Eche un vistazo IEnumerable<T>como un excelente ejemplo que probablemente surja con bastante frecuencia.

Aquí hay un ejemplo:

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
}

Aquí, IEnumerable<string>implementos IEnumerable, por lo tanto, también tenemos que hacerlo. Pero espere, tanto la versión genérica como la normal implementan funciones con la misma firma de método (C # ignora el tipo de retorno para esto). Esto es completamente legal y está bien. ¿Cómo resuelve el compilador cuál usar? Te obliga a tener, como máximo, una definición implícita, luego puede resolver lo que necesite.

es decir.

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();

PD: El pequeño fragmento de indirección en la definición explícita de IEnumerable funciona porque dentro de la función el compilador sabe que el tipo real de la variable es una StringList, y así es como resuelve la llamada a la función. Pequeño hecho ingenioso para implementar algunas de las capas de abstracción que algunas de las interfaces principales de .NET parecen haberse acumulado.


55
@Tassadaque: Después de 2 años, todo lo que puedo decir es "Buena pregunta". No tengo idea, excepto tal vez que copié ese código de algo en lo que estaba trabajando donde estaba abstract.
Matthew Scharley

44
@Tassadaque, tienes razón, pero creo que el punto de la publicación anterior de Matthew Scharley no se pierde.
funkymushroom

35

Para citar a Jeffrey Richter de CLR a través de C #
( EIMI significa E xplicit I nterface M ethod I mplementation)

Es muy importante que comprenda algunas ramificaciones que existen al usar EIMI. Y debido a estas ramificaciones, debe intentar evitar los EIMI tanto como sea posible. Afortunadamente, las interfaces genéricas lo ayudan a evitar los EIMI bastante. Pero aún puede haber ocasiones en las que necesite usarlas (como implementar dos métodos de interfaz con el mismo nombre y firma). Estos son los grandes problemas con los EIMI:

  • No hay documentación que explique cómo un tipo implementa específicamente un método EIMI, y no hay soporte para Microsoft Visual Studio IntelliSense .
  • Las instancias de tipo de valor se encuadran cuando se convierten en una interfaz.
  • Un EIMI no puede ser llamado por un tipo derivado.

Si usa una referencia de interfaz, CUALQUIER cadena virtual se puede reemplazar explícitamente con EIMI en cualquier clase derivada y cuando un objeto de ese tipo se lanza a la interfaz, su cadena virtual se ignora y se llama a la implementación explícita. Eso es todo menos polimorfismo.

Los EIMI también se pueden usar para ocultar miembros de interfaz no fuertemente tipados de implementaciones básicas de Framework Framework como IEnumerable <T> para que su clase no exponga un método no fuertemente tipado directamente, pero es sintácticamente correcto.


2
La reimplementación de interfaces, aunque es legal, generalmente es dudosa en el mejor de los casos. Las implementaciones explícitas generalmente deben encadenarse a un método virtual directamente o mediante una lógica de ajuste que debe ser vinculante para las clases derivadas. Si bien es posible usar interfaces de formas hostiles a las convenciones de OOP adecuadas, eso no significa que no se puedan usar mejor.
supercat

44
@Valentin ¿Qué significan EIMI e IEMI?
Dzienny

Implementación explícita del método de interfaz
scobi

55
-1 para "En general, veo Interfaces como característica OOP semi (en el mejor de los casos), proporciona herencia, pero no proporciona polimorfismo real". Estoy totalmente en desacuerdo. Por el contrario, las interfaces tienen que ver con el polimorfismo y no principalmente con la herencia. Atribuyen múltiples clasificaciones a un tipo. Evite IEMI, cuando pueda, y delegue, como sugirió @supercat, cuando no pueda. No evite las interfaces.
Aluan Haddad

1
"Un tipo derivado no puede llamar a un EIMI". << ¿QUÉ? Eso no es cierto. Si implemento explícitamente una interfaz en un tipo, entonces me derivo de ese tipo, todavía puedo enviarlo a la interfaz para llamar al método, exactamente como tendría que hacerlo para el tipo en el que está implementado. Así que no estoy seguro de lo que estás hablando. Incluso dentro del tipo derivado, simplemente puedo transmitir "esto" a la interfaz en cuestión para llegar al método implementado explícitamente.
Triynko

34

Razón # 1

Tiendo a usar una implementación de interfaz explícita cuando quiero desalentar la "programación para una implementación" ( Principios de diseño de patrones de diseño ).

Por ejemplo, en una aplicación web basada en MVP :

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

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

Ahora es menos probable que otra clase (como un presentador ) dependa de la implementación de StandardNavigator y más probable que dependa de la interfaz INavigator (ya que la implementación necesitaría ser convertida en una interfaz para hacer uso del método Redirect).

Razón # 2

Otra razón por la que podría optar por una implementación de interfaz explícita sería mantener limpia la interfaz "predeterminada" de una clase. Por ejemplo, si estuviera desarrollando un control de servidor ASP.NET , podría querer dos interfaces:

  1. La interfaz principal de la clase, que utilizan los desarrolladores de páginas web; y
  2. Una interfaz "oculta" utilizada por el presentador que desarrollo para manejar la lógica del control

Un ejemplo simple sigue. Es un control de cuadro combinado que enumera los clientes. En este ejemplo, el desarrollador de la página web no está interesado en completar la lista; en cambio, solo quieren poder seleccionar un cliente por GUID u obtener el GUID del cliente seleccionado. Un presentador llenaría el cuadro en la carga de la primera página, y este presentador está encapsulado por el control.

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; }
}

El presentador llena la fuente de datos, y el desarrollador de la página web nunca necesita ser consciente de su existencia.

Pero no es una bala de cañón de plata

No recomendaría siempre emplear implementaciones explícitas de interfaz. Esos son solo dos ejemplos en los que podrían ser útiles.


19

Además de las otras razones ya mencionadas, esta es la situación en la que una clase está implementando dos interfaces diferentes que tienen una propiedad / método con el mismo nombre y firma.

/// <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
}

Este código se compila y se ejecuta correctamente, pero la propiedad Título se comparte.

Claramente, nos gustaría que el valor del Título se devuelva dependiendo de si tratamos la Clase 1 como un Libro o una Persona. Aquí es cuando podemos usar la interfaz 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"; }
}

Tenga en cuenta que las definiciones de interfaz explícitas se infieren como públicas y, por lo tanto, no puede declararlas como públicas (o de otro modo) explícitamente.

Tenga en cuenta también que aún puede tener una versión "compartida" (como se muestra arriba), pero aunque esto es posible, la existencia de dicha propiedad es cuestionable. Tal vez podría usarse como una implementación predeterminada de Título, de modo que el código existente no tenga que modificarse para emitir Class1 a IBook o IPerson.

Si no define el título "compartido" (implícito), los consumidores de Class1 deben emitir explícitamente instancias de Class1 a IBook o IPerson primero; de lo contrario, el código no se compilará.


16

Uso la implementación explícita de la interfaz la mayor parte del tiempo. Aquí están las razones principales.

Refactorizar es más seguro

Al cambiar una interfaz, es mejor si el compilador puede verificarlo. Esto es más difícil con implementaciones implícitas.

Me vienen a la mente dos casos comunes:

  • Agregar una función a una interfaz, donde una clase existente que implementa esta interfaz ya tiene un método con la misma firma que la nueva . Esto puede conducir a un comportamiento inesperado y me ha mordido varias veces. Es difícil "ver" al depurar porque es probable que esa función no se encuentre con los otros métodos de interfaz en el archivo (el problema de autodocumentación mencionado a continuación).

  • Eliminar una función de una interfaz . Los métodos implementados implícitamente serán de repente código muerto, pero los métodos implementados explícitamente quedarán atrapados por un error de compilación. Incluso si es bueno mantener el código muerto, quiero verme obligado a revisarlo y promocionarlo.

Es lamentable que C # no tenga una palabra clave que nos obligue a marcar un método como una implementación implícita, por lo que el compilador podría hacer las comprobaciones adicionales. Los métodos virtuales no tienen ninguno de los problemas anteriores debido al uso requerido de 'anular' y 'nuevo'.

Nota: para interfaces fijas o que cambian raramente (generalmente de API de proveedores), esto no es un problema. Sin embargo, para mis propias interfaces, no puedo predecir cuándo / cómo cambiarán.

Es autodocumentado

Si veo 'public bool Execute ()' en una clase, me tomará un trabajo extra descubrir que es parte de una interfaz. Alguien probablemente tendrá que comentarlo diciendo eso, o ponerlo en un grupo de otras implementaciones de interfaz, todo bajo una región o un comentario grupal que diga "implementación de ITask". Por supuesto, eso solo funciona si el encabezado del grupo no está fuera de la pantalla.

Mientras que: 'bool ITask.Execute ()' es claro e inequívoco.

Separación clara de la implementación de la interfaz

Pienso que las interfaces son más 'públicas' que métodos públicos porque están diseñadas para exponer solo un poco de la superficie del tipo de concreto. Reducen el tipo a una capacidad, un comportamiento, un conjunto de rasgos, etc. Y en la implementación, creo que es útil mantener esta separación.

Cuando miro el código de una clase, cuando encuentro implementaciones de interfaz explícitas, mi cerebro cambia al modo de "contrato de código". A menudo, estas implementaciones simplemente reenvían a otros métodos, pero a veces harán una comprobación adicional de estado / parámetro, conversión de parámetros entrantes para que coincidan mejor con los requisitos internos, o incluso traducción para propósitos de versionado (es decir, múltiples generaciones de interfaces, todo apuntando a implementaciones comunes).

(Me doy cuenta de que los públicos también son contratos de código, pero las interfaces son mucho más fuertes, especialmente en una base de código impulsada por interfaz donde el uso directo de tipos concretos suele ser un signo de código solo interno).

Relacionado: Razón 2 anterior por Jon .

Y así

Además de las ventajas ya mencionadas en otras respuestas aquí:

Problemas

No todo es diversión y felicidad. Hay algunos casos en los que me quedo con implicidades:

  • Tipos de valor, porque eso requerirá boxeo y menor rendimiento. Esta no es una regla estricta, y depende de la interfaz y de cómo debe usarse. IComparable? Implícito. IFormattable? Probablemente explícito.
  • Interfaces triviales del sistema que tienen métodos que con frecuencia se llaman directamente (como IDisposable.Dispose).

Además, puede ser difícil hacer el casting cuando, de hecho, tiene el tipo concreto y desea llamar a un método de interfaz explícito. Trato esto de una de dos maneras:

  1. Agregue públicos y envíe los métodos de interfaz para la implementación. Normalmente ocurre con interfaces más simples cuando se trabaja internamente.
  2. (Mi método preferido) Agregue un public IMyInterface I { get { return this; } }(que debería estar en línea) y llame foo.I.InterfaceMethod(). Si hay varias interfaces que necesitan esta capacidad, expanda el nombre más allá de I (en mi experiencia, es raro que tenga esta necesidad).

8

Si implementa explícitamente, solo podrá hacer referencia a los miembros de la interfaz a través de una referencia que sea del tipo de la interfaz. Una referencia que es el tipo de la clase implementadora no expondrá esos miembros de la interfaz.

Si su clase de implementación no es pública, excepto por el método utilizado para crear la clase (que podría ser un contenedor de fábrica o IoC ), y excepto por los métodos de interfaz (por supuesto), entonces no veo ninguna ventaja para implementar explícitamente interfaces

De lo contrario, la implementación explícita de interfaces asegura que no se utilicen referencias a su clase de implementación concreta, lo que le permite cambiar esa implementación más adelante. "Asegurarse", supongo, es la "ventaja". Una implementación bien factorizada puede lograr esto sin una implementación explícita.

La desventaja, en mi opinión, es que te encontrarás enviando tipos a / desde la interfaz en el código de implementación que tiene acceso a miembros no públicos.

Como muchas cosas, la ventaja es la desventaja (y viceversa). La implementación explícita de interfaces asegurará que su código de implementación de clase concreto no esté expuesto.


1
Buena respuesta, Bill. Las otras respuestas fueron geniales, pero usted proporcionó algunos puntos de vista objetivos adicionales (además de sus opiniones) que me facilitaron la comprensión. Como la mayoría de las cosas, hay pros y contras con implementaciones implícitas o explícitas, por lo que solo tiene que usar la mejor para su escenario particular o caso de uso. Diría que aquellos que intentan entenderlo mejor se beneficiarán al leer su respuesta.
Daniel Eagle

6

Una implementación de interfaz implícita es donde tiene un método con la misma firma de la interfaz.

Una implementación de interfaz explícita es donde declaras explícitamente a qué interfaz pertenece el método.

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: implementaciones de interfaz implícitas y explícitas


6

Cada miembro de la clase que implementa una interfaz exporta una declaración que es semánticamente similar a la forma en que se escriben las declaraciones de la interfaz VB.NET, por ejemplo

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

Aunque el nombre del miembro de la clase a menudo coincidirá con el del miembro de la interfaz, y el miembro de la clase a menudo será público, ninguna de esas cosas es necesaria. También se puede declarar:

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

En cuyo caso, se le permitiría a la clase y sus derivados acceder a un miembro de la clase utilizando el nombre IFoo_Foo, pero el mundo exterior solo podría acceder a ese miembro en particular mediante el envío a IFoo. Este enfoque suele ser bueno en los casos en que un método de interfaz tendrá un comportamiento específico en todas las implementaciones, pero un comportamiento útil solo en algunos [por ejemplo, el comportamiento especificado para el IList<T>.Addmétodo de una colección de solo lectura es lanzar NotSupportedException]. Desafortunadamente, la única forma correcta de implementar la interfaz en C # es:

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

No tan bueno


2

Un uso importante de la implementación de interfaz explícita es cuando se necesita implementar interfaces con visibilidad mixta .

El problema y la solución se explican bien en el artículo C # Interfaz interna .

Por ejemplo, si desea proteger la fuga de objetos entre las capas de la aplicación, esta técnica le permite especificar una visibilidad diferente de los miembros que podrían causar la fuga.


2

Las respuestas anteriores explican por qué implementar una interfaz explícitamente en C # puede ser preferible (por razones principalmente formales). Sin embargo, hay una situación en la que la implementación explícita es obligatoria : para evitar fugas de la encapsulación cuando la interfaz no es public, pero la clase de implementación sí lo es 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() { } }

La fuga anterior es inevitable porque, de acuerdo con la especificación C # , "Todos los miembros de la interfaz tienen acceso público implícitamente". Como consecuencia, las implementaciones implícitas también deben dar publicacceso, incluso si la interfaz en sí es, por ejemplo internal.

La implementación de interfaz implícita en C # es una gran conveniencia. En la práctica, muchos programadores lo usan todo el tiempo / en todas partes sin mayor consideración. Esto conduce a superficies de tipo desordenado en el mejor de los casos y filtración encapsulada en el peor Otros idiomas, como F #, ni siquiera lo permiten .

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.