parametro - Interfaces C#. Implementación implícita versus implementación explícita
interface como parametro c# (11)
¿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 y / o contras para uno u otro?
Las pautas oficiales de Microsoft (de la primera edición de las Directrices de diseño de Framework ) establecen que no se recomienda el uso de implementaciones explícitas , ya que da un comportamiento inesperado al código.
Creo que esta guía es muy válida en un momento anterior a la IOC , cuando no se pasan las cosas como interfaces.
¿Alguien podría tocar ese aspecto también?
Razón # 1
Tiendo a usar la implementación de la interfaz explícita cuando quiero desalentar la "programación para una implementación" ( Principios de diseño a partir 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, otra clase (como un presenter ) es menos probable que dependa de la implementación de StandardNavigator y más probable que dependa de la interfaz de INavigator (ya que la implementación tendría que convertirse en una interfaz para hacer uso del método de redirección).
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:
- La interfaz principal de la clase, que es utilizada por los desarrolladores de páginas web; y
- 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 lista a los clientes. En este ejemplo, el desarrollador de la página web no está interesado en llenar la lista; en su lugar, solo quieren poder seleccionar un cliente por GUID u obtener el GUID del cliente seleccionado. Un presentador llenaría el recuadro en la primera carga de la página, y el 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 completa la fuente de datos y el desarrollador de la página web nunca necesita estar consciente de su existencia.
Pero no es una bala de cañón de plata
No recomendaría emplear siempre implementaciones de interfaz explícitas. Esos son solo dos ejemplos en los que pueden ser útiles.
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 averiguar lo que se requiere. Eche un vistazo a IEnumerable<T>
como un buen ejemplo que probablemente aparecerá 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>
implementa IEnumerable
, por lo tanto, también necesitamos. Pero espera, 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 está bien. ¿Cómo resuelve el compilador cuál usar? Le obliga a tener solo, 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: la pequeña parte de la 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 se resuelve la llamada a la función. Un pequeño e ingenioso dato para implementar algunas de las capas de abstracción que algunas de las interfaces principales de .NET parecen haber acumulado.
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 compila y se ejecuta en Aceptar, pero la propiedad Título se comparte.
Claramente, quisiéramos que el valor del Título devuelto dependiera de si tratamos a Class1 como un Libro o una Persona. Esto 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"; }
}
Observe que las definiciones de la interfaz explícita se deducen 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 todavía puede tener una versión "compartida" (como se muestra arriba), pero aunque esto es posible, la existencia de tal propiedad es cuestionable. Quizás podría usarse como una implementación predeterminada de Título, de modo que el código existente no tendría que ser modificado para convertir Class1 a IBook o IPerson.
Si no define el Título "compartido" (implícito), los consumidores de Class1 deben convertir explícitamente las instancias de Class1 a IBook o IPerson primero; de lo contrario, el código no se compilará.
Cada miembro de la clase que implementa una interfaz exporta una declaración 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 requerida. También se puede declarar:
Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo
En cuyo caso, la clase y sus derivados podrán acceder a un miembro de la clase con el nombre IFoo_Foo
, pero el mundo exterior solo podrá acceder a ese miembro en particular mediante la 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<T>.Add
NotSupportedException
de una colección de solo lectura es lanzar la 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 bonito.
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 se trabaja directamente con la interfaz, y no con la implementación subyacente. Esto es preferido en la mayoría de los casos.
- Al trabajar directamente con la interfaz, no está reconociendo y acoplando su código a la implementación subyacente.
- En el caso de que ya tenga, digamos, un nombre de propiedad pública en su código y desee implementar una interfaz que también tenga una propiedad de Nombre, hacerlo de manera explícita mantendrá los dos por separado. Incluso si estuvieran haciendo lo mismo, yo delegaría la llamada explícita a la propiedad Nombre. Nunca se sabe, es posible que desee cambiar cómo funciona el nombre para la clase normal y cómo funciona la propiedad de la interfaz más adelante.
- Si implementa una interfaz implícitamente, su clase ahora expone nuevos comportamientos que solo pueden ser relevantes para un cliente de la interfaz, lo que significa que no está manteniendo sus clases lo suficientemente sucintas (mi opinión).
Para citar a Jeffrey Richter de CLR a través de C #
( EIMI significa E xplicit I nterface M ethod I mplementation)
Es de vital importancia para usted entender algunas ramificaciones que existen al usar los EIMI. Y debido a estas ramificaciones, debe intentar evitar los EIMI tanto como sea posible. Afortunadamente, las interfaces genéricas le ayudan a evitar los EIMI bastante. Pero aún puede haber ocasiones en las que necesite usarlos (como implementar dos métodos de interfaz con el mismo nombre y firma). Aquí están los grandes problemas con los EIMIs:
- 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 envían a 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 convierte en la interfaz, se ignora su cadena virtual y se llama a la implementación explícita. Eso es cualquier cosa menos polimorfismo.
Los EIMI también se pueden usar para ocultar miembros de interfaz no fuertemente tipados de las implementaciones básicas de Framework Interfaces como IEnumerable <T> para que su clase no exponga directamente un método no tipificado, pero es sintáctico correcto.
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 de implementación no expondrá a los 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 en implementar explícitamente interfaces
De lo contrario, la implementación explícita de interfaces garantiza que no se utilicen las referencias a su clase de implementación concreta, lo que le permite cambiar esa implementación en un momento posterior. "Se asegura", supongo, es la "ventaja". Una implementación bien estructurada 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 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 esté expuesto.
Un uso importante de la implementación de la interfaz explícita es cuando se necesita implementar interfaces con visibilidad mixta .
El problema y la solución están bien explicados en el artículo C # Interfaz interna .
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ían causar la fuga.
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 usted declara 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
Uso la implementación de la interfaz explícita la mayor parte del tiempo. Aquí están las razones principales.
La refactorización es más segura
Al cambiar una interfaz, es mejor si el compilador 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 llevar a un comportamiento inesperado y me ha mordido varias 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 autodocumentación que se menciona a continuación).
Eliminar una función de una interfaz . Los métodos implementados implícitamente serán códigos muertos repentinamente, pero los métodos implementados explícitamente serán atrapados por un error de compilación. Incluso si es bueno mantener el código muerto, quiero que me obliguen 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 compilador podría realizar 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 con poca frecuencia (generalmente de las API de proveedores), esto no es un problema. Sin embargo, para mis propias interfaces, no puedo predecir cuándo y cómo cambiarán.
Es autodocumentada.
Si veo ''public bool Execute ()'' en una clase, tomará un trabajo extra para descubrir que es parte de una interfaz. Alguien probablemente tendrá que comentarlo diciendo eso, o ponerlo en un grupo de otras implementaciones de interfaz, todas bajo una región o un comentario de 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.
Considero 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 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.
Cuando miro a través del código de una clase, cuando me encuentro con implementaciones de interfaz explícitas, mi cerebro cambia al modo de "contrato de código". A menudo, estas implementaciones simplemente se remiten a otros métodos, pero a veces realizan comprobaciones adicionales de estado / parámetros, conversión de parámetros entrantes para adaptarse mejor a los requisitos internos, o incluso traducción para fines de control de versiones (es decir, múltiples generaciones de interfaces, todas ellas adaptadas 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 basada en interfaz donde el uso directo de tipos concretos suele ser un signo de código solo interno).
Relacionados: Razón 2 arriba por Jon .
Y así
Más las ventajas ya mencionadas en otras respuestas aquí:
- Cuando sea necesario, según disambiguation o necesidad de una interfaz interna
- Desalienta la "programación para una implementación" ( Razón 1 por Jon )
Problemas
No es todo diversión y felicidad. Hay algunos casos donde me quedo con implicaciones:
- Tipos de valor, porque eso requerirá boxeo y menor perf. Esta no es una regla estricta, y depende de la interfaz y de cómo se utiliza. 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 una molestia hacer el casting cuando de hecho tiene el tipo concreto y desea llamar a un método de interfaz explícito. Me ocupo de esto de una de dos maneras:
- Agregue públicos y solicite los métodos de interfaz para la implementación. Normalmente ocurre con interfaces más simples cuando se trabaja internamente.
- (Mi método preferido) Agregar un
public IMyInterface I { get { return this; } }
public IMyInterface I { get { return this; } }
(que debería estar en línea) y llamar afoo.I.InterfaceMethod()
. Si varias interfaces necesitan esta capacidad, amplíe el nombre más allá de I (en mi experiencia, es raro que tenga esta necesidad).
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.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 está implícitamente accesible a través de la clase que creó cuando se emite como esa clase, así como cuando se emite como interfaz. La implementación explícita permite que solo sea accesible cuando se emite como la propia interfaz.
MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.
Uso explícito 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.
Vea la siguiente publicación en este hilo para un excelente razonamiento detrás de cada uno.