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?
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:
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()
{
}
}
Es útil ocultar el miembro no preferido. Por ejemplo, si implementa ambos IComparable<T>
y IComparable
generalmente es mejor ocultar la IComparable
sobrecarga 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.
string
existí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, string
así que lo dejaron como estaba con la protección en su lugar.
Algunas razones adicionales para implementar una interfaz explícitamente:
compatibilidad con versiones anteriores : en caso de que la ICloneable
interfaz 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 Clone
mé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 ICloneable
explícitamente permite Clone()
escribir fuertemente cuando lo llamas directamente como MyObject
miembro de la instancia:
public class MyObject : ICloneable
{
public MyObject Clone()
{
// my cloning logic;
}
object ICloneable.Clone()
{
return this.Clone();
}
}
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 protected
método de clonación y clases selladas que se deriven de ellas y expongan la clonación pública.
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 Clone
método públicamente visible devuelve su propio tipo.
Del mismo modo, un IAutomobileFactory
podría tener un Manufacture
método que devuelve un Automobile
, pero a FordExplorerFactory
, que implementa IAutomobileFactory
, puede hacer que su Manufacture
método devuelva un FordExplorer
(que se deriva de Automobile
). Código que sabe que tiene unFordExplorerFactory
podría usar FordExplorer
propiedades específicas en un objeto devuelto por a FordExplorerFactory
sin tener que escribirlo, mientras que el código que simplemente sabía que tenía algún tipo IAutomobileFactory
simplemente trataría su retorno como un Automobile
.
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
}
public class Animal : Cat, Dog
Puede mantener limpia la interfaz pública para implementar explícitamente una interfaz, es decir, su File
clase podría implementar IDisposable
explí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).
Dispose
son 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
.
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.
#region
es, con una cadena de título apropiada. Y un comentario sobre el método.
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();
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
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();
}
}
}