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

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

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

¿Hay ventajas y desventajas para uno u otro?


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

Creo que esta guía es muy válida en un tiempo pre-IoC , cuando no se pasan las cosas como interfaces.

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

Implícito es cuando defines tu interfaz a través de un miembro en tu clase. Explícito es cuando defines los métodos dentro de tu clase en la interfaz. Sé que suena confuso, pero esto es lo que quiero decir: IList.CopyTo se 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, implícitamente, se puede acceder a través de la clase que creaste cuando se lanza como esa clase, así como cuando se utiliza como interfaz. La implementación explícita solo le permite acceder cuando se usa como interfaz.

 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 la implementación limpia, o cuando necesito dos implementaciones. Pero a pesar de que rara vez lo uso.

Estoy seguro de que hay más razones para usarlo / no usarlo que otros publicarán.

Mira la siguiente publicación en este hilo por un excelente razonamiento detrás de cada uno.

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 estar expuestos solo cuando trabajas directamente con la interfaz, y no a la implementación subyacente. Esto es preferido 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, hacerlo explícitamente los mantendrá separados. Incluso si estuvieran haciendo lo mismo, yo aún delegaría la llamada explícita a la propiedad Nombre. Nunca se sabe, es posible que desee cambiar cómo funciona Name para la clase normal y cómo Name, la propiedad de la interfaz funciona más adelante.
  3. Si implementa una interfaz de forma implícita, su clase ahora expone comportamientos nuevos que solo pueden ser relevantes para un cliente de la interfaz y significa que no está manteniendo sus clases lo suficientemente concisas (mi opinión).

Además de las excelentes respuestas ya proporcionadas, hay algunos casos en los que se REQUIERE una implementación explícita para que el comstackdor pueda descubrir qué se requiere. Eche un vistazo a IEnumerable como un buen ejemplo que probablemente IEnumerable con bastante frecuencia.

Aquí hay un ejemplo:

 public abstract class StringList : IEnumerable { private string[] _list = new string[] {"foo", "bar", "baz"}; // ... #region IEnumerable Members public IEnumerator GetEnumerator() { foreach (string s in _list) { yield return s; } } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion } 

Aquí, IEnumerable implementa IEnumerable , por lo tanto, también necesitamos hacerlo. Pero espere, tanto la versión genérica como la versión normal implementan funciones con la misma firma de método (C # ignora el tipo de retorno para esto). Esto es completamente legal y bueno. ¿Cómo resuelve el comstackdor cuál usar? Te obliga a tener solo, a lo sumo, una definición implícita, luego puede resolver lo que sea necesario.

es decir.

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

PD: la pequeña parte de indirección en la definición explícita de IEnumerable funciona porque dentro de la función el comstackdor sabe que el tipo real de la variable es una StringList, y así es como resuelve la llamada a la función. Un pequeño y hábil hecho para implementar algunas de las capas de abstracción que algunas de las interfaces centrales de .NET parecen haber acumulado.

Para citar a Jeffrey Richter de CLR a través de C #
( EIMI significa Implementación E xplícita de Metodología)

Es summente 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 te ayudan a evitar EIMI bastante. Pero aún puede haber momentos en los que necesite usarlos (como implementar dos métodos de interfaz con el mismo nombre y firma). Estos son los grandes problemas con 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 están encuadradas cuando se lanzan a una interfaz.
  • Un EIMI no puede ser llamado por un tipo derivado.

Si utiliza una referencia de interfaz, CUALQUIER cadena virtual puede ser reemplazada explícitamente por EIMI en cualquier clase derivada y cuando un objeto de ese tipo es enviado a la interfaz, su cadena virtual es ignorada y se llama a la implementación explícita. Eso es todo menos el polymorphism.

Los EIMI también se pueden utilizar para ocultar miembros de la interfaz no fuertemente tipados de las implementaciones básicas de Framework Framework, como IEnumerable , por lo que su clase no expone un método no fuertemente tipado directamente, pero es sintácticamente correcto.

Razón # 1

Tiendo a utilizar la implementación de interfaz explícita cuando quiero desalentar la “progtwigción a 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 probablemente dependa de la interfaz de INavigator (ya que la implementación debería enviarse a una interfaz para utilizar el método Redirect).

Razón # 2

Otra razón por la que podría ir con 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 es utilizada por los desarrolladores de páginas web; y
  2. Una interfaz “oculta” utilizada por el presentador que desarrollé para manejar la lógica del control

Un simple ejemplo sigue. Es un control de cuadro combinado que enumera a los clientes. En este ejemplo, el desarrollador de la página web no está interesado en rellenar la lista; en su lugar, solo desean poder seleccionar un cliente por GUID o para obtener el GUID del cliente seleccionado. Un presentador llenaría la casilla en la primera página cargada, 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 ICustomerComboBox.DataSource { set; } } 

El presentador rellena la fuente de datos, y el desarrollador de la página web nunca necesita estar al tanto de su existencia.

Pero no es una bala de cañón plateada

No recomendaría siempre emplear implementaciones de interfaz explícitas. Esos son solo dos ejemplos donde pueden ser útiles.

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

 ///  /// This is a Book ///  interface IBook { string Title { get; } string ISBN { get; } } ///  /// This is a Person ///  interface IPerson { string Title { get; } string Forename { get; } string Surname { get; } } ///  /// This is some freaky book-person. ///  class Class1 : IBook, IPerson { ///  /// This method is shared by both Book and Person ///  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 comstack y se ejecuta correctamente, pero la propiedad Título se comparte.

Claramente, desearíamos que el valor del Título devuelto dependa de si estamos tratando a Class1 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ícita se infieren como públicas, y por lo tanto no puede declarar que sean públicas (o de otro modo) explícitamente.

Tenga en cuenta también que todavía puede tener una versión “compartida” (como se muestra arriba), pero mientras esto sea posible, la existencia de dicha propiedad es cuestionable. Tal vez se podría utilizar como una implementación predeterminada de Título, de modo que el código existente no tendría que modificarse para convertir Class1 en IBook o IPerson.

Si no define el Título “compartido” (implícito), los consumidores de la Clase 1 deben emitir instancias de Clase1 a IBook o IPerson primero, de lo contrario, el código no se comstackrá.

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

Refactorizar es más seguro

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

Dos casos comunes vienen a la mente:

  • 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 picado muchas veces. Es difícil “ver” cuando se realiza la depuración porque es probable que esa función no se encuentre con los otros métodos de interfaz en el archivo (el problema de la auto documentación que se menciona a continuación).

  • Eliminar una función de una interfaz . Los métodos implementados implícitamente serán repentinamente código muerto, pero los métodos implementados explícitamente quedarán atrapados por el error de comstackción. Incluso si el código muerto es bueno para mantener, me obligan a revisarlo y promocionarlo.

Es desafortunado 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 comstackdor podría hacer las comprobaciones adicionales. Los métodos virtuales no tienen ninguno de los problemas anteriores debido al uso obligatorio de ‘anular’ y ‘nuevo’.

Nota: para interfaces fijas o que cambian raramente (típicamente de las API de proveedores), esto no es un problema. Para mis propias interfaces, sin embargo, no puedo predecir cuándo / cómo van a cambiar.

Es autodocumentado

Si veo ‘public bool Execute ()’ en una clase, va a tomar un trabajo extra para descubrir que es parte de una interfaz. Alguien probablemente tenga que comentarlo, o decirlo en un grupo de otras implementaciones de interfaz, todo bajo un comentario de región o agrupación que diga “implementación de ITask”. Por supuesto, eso solo funciona si el encabezado del grupo no está fuera de la pantalla …

Considerando que: ‘bool ITask.Execute ()’ es claro y no ambiguo.

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

Creo que las interfaces son más “públicas” que los métodos públicos porque están diseñadas para exponer solo un poco del área de la superficie del tipo 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.

Mientras busco 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 comprobaciones de estado / param adicionales, conversión de parámetros entrantes para adecuar mejor los requisitos internos, o incluso traducción para propósitos de creación de versiones (es decir, múltiples generaciones de interfaces que bajan 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 sólidas, 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í:

  • Cuando sea necesario, según la desambiguación o la necesidad de una interfaz interna
  • Desalienta la “progtwigción para una implementación” ( Razón 1 por Jon )

Problemas

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

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

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

  1. Agregue públicos y reenvíe los métodos de interfaz para la implementación. Suele pasar con interfaces más simples cuando se trabaja internamente.
  2. (Mi método preferido) Agregar una public IMyInterface I { get { return this; } } public IMyInterface I { get { return this; } } (que debería estar en línea) y llama a 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).

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 sea del tipo de la clase implementadora no expondrá esos miembros de la interfaz.

Si su clase implementadora 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 las interfaces garantiza que no se utilicen las referencias a su clase concreta de implementación, lo que le permite cambiar esa implementación más adelante. “Se asegura”, 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 se encontrará lanzando tipos a / desde la interfaz en el código de implementación que sí 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 concreta no quede expuesto.

Una implementación de interfaz implícita es donde tienes 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 interfaces implícitas y explícitas

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

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

Aunque el nombre del miembro de la clase con frecuencia 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. Uno también puede declarar:

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

En ese caso, la clase y sus derivados podrían acceder a un miembro de la clase que utiliza el nombre IFoo_Foo , pero el mundo exterior solo podría acceder a ese miembro en particular mediante conversión 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 método IList.Add NotSupportedException de una colección de solo lectura es tirar 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 lindo.

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

El problema y la solución están bien explicados en el artículo C # Internal Interface .

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