Allora, qual è esattamente un buon caso d'uso per implementare esplicitamente un'interfaccia?
È solo così che le persone che usano la classe non devono guardare tutti quei metodi / proprietà in intellisense?
Allora, qual è esattamente un buon caso d'uso per implementare esplicitamente un'interfaccia?
È solo così che le persone che usano la classe non devono guardare tutti quei metodi / proprietà in intellisense?
Risposte:
Se si implementano due interfacce, entrambe con lo stesso metodo e diverse implementazioni, è necessario implementarle esplicitamente.
public interface IDoItFast
{
void Go();
}
public interface IDoItSlow
{
void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
void IDoItFast.Go()
{
}
void IDoItSlow.Go()
{
}
}
È utile nascondere il membro non preferito. Ad esempio, se si implementano entrambi IComparable<T>
e di IComparable
solito è meglio nascondere il IComparable
sovraccarico per non dare alle persone l'impressione di poter confrontare oggetti di diverso tipo. Allo stesso modo, alcune interfacce non sono conformi a CLS IConvertible
, quindi se non si implementa esplicitamente l'interfaccia, gli utenti finali dei linguaggi che richiedono la conformità a CLS non possono utilizzare l'oggetto. (Il che sarebbe molto disastroso se gli implementatori di BCL non nascondessero i membri IConvertible delle primitive :))
Un'altra nota interessante è che normalmente l'uso di un tale costrutto significa che la struttura che implementa esplicitamente un'interfaccia può solo invocarla inserendola nel tipo di interfaccia. Puoi aggirare questo problema utilizzando vincoli generici:
void SomeMethod<T>(T obj) where T:IConvertible
Non inscatolerà un int quando ne passi uno.
string
era in giro prima dei generici e questa pratica era in voga. Quando .net 2 è uscito, non volevano interrompere l'interfaccia pubblica di, string
quindi l'hanno lasciato così com'era con la protezione in atto.
Alcuni motivi aggiuntivi per implementare un'interfaccia in modo esplicito:
compatibilità con le versioni precedenti : nel caso in cui l' ICloneable
interfaccia cambi, l'implementazione dei membri della classe del metodo non deve modificare le firme del metodo.
codice più pulito : ci sarà un errore del compilatore se il Clone
metodo viene rimosso da ICloneable, tuttavia se implementi il metodo implicitamente puoi ritrovarti con metodi pubblici 'orfani' non utilizzati
digitazione forte : per illustrare la storia di supercat con un esempio, questo sarebbe il mio codice di esempio preferito, l'implementazione ICloneable
esplicita consente Clone()
di essere fortemente tipizzato quando lo chiami direttamente come MyObject
membro dell'istanza:
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;} }
. Nota che non c'è deliberatamente alcun ICloneable<T>
vincolo su T. Mentre un oggetto può generalmente essere clonato in modo sicuro solo se la sua base può esserlo, si potrebbe desiderare di derivare da una classe base che potrebbe essere clonata in modo sicuro un oggetto di una classe che non può. Per consentire ciò, consiglierei di non avere classi ereditabili che espongono un metodo clone pubblico. Hanno invece classi ereditabili con un protected
metodo di clonazione e classi sealed che derivano da esse ed espongono la clonazione pubblica.
Un'altra tecnica utile è fare in modo che l'implementazione pubblica di una funzione di un metodo restituisca un valore più specifico di quello specificato in un'interfaccia.
Ad esempio, un oggetto può essere implementato ICloneable
, ma il suo Clone
metodo visibile pubblicamente restituisce il proprio tipo.
Allo stesso modo, an IAutomobileFactory
potrebbe avere un Manufacture
metodo che restituisce an Automobile
, ma a FordExplorerFactory
, che implementa IAutomobileFactory
, potrebbe avere il suo Manufacture
metodo restituire a FordExplorer
(che deriva da Automobile
). Codice che sa di avere una proprietà -specific che FordExplorerFactory
potrebbe usare FordExplorer
su un oggetto restituito da a FordExplorerFactory
senza dover digitare il typecast, mentre il codice che semplicemente sapeva di avere un tipo di proprietà IAutomobileFactory
si occuperebbe semplicemente del suo ritorno come Automobile
.
È utile anche quando si hanno due interfacce con lo stesso nome e firma del membro, ma si desidera modificarne il comportamento a seconda di come vengono utilizzate. (Non consiglio di scrivere codice come questo):
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
Può mantenere l'interfaccia pubblica più pulita per implementare esplicitamente un'interfaccia, cioè la tua File
classe potrebbe implementarla IDisposable
esplicitamente e fornire un metodo pubblico Close()
che potrebbe avere più senso per un consumatore di Dispose(
).
F # offre solo l' implementazione esplicita dell'interfaccia, quindi devi sempre eseguire il cast alla particolare interfaccia per accedere alla sua funzionalità, il che rende molto esplicito (senza giochi di parole) l'uso dell'interfaccia.
Dispose
sono quelle che non richiederanno mai la pulizia); un esempio migliore sarebbe qualcosa come l'implementazione di una raccolta immutabile di IList<T>.Add
.
Se si dispone di un'interfaccia interna e non si desidera implementare pubblicamente i membri della classe, è necessario implementarli in modo esplicito. Le implementazioni implicite devono essere pubbliche.
Un altro motivo per l'implementazione esplicita è per la manutenibilità .
Quando una classe diventa "impegnata" - sì, succede, non tutti abbiamo il lusso di refactoring del codice degli altri membri del team - quindi avere un'implementazione esplicita rende chiaro che c'è un metodo per soddisfare un contratto di interfaccia.
Quindi migliora la "leggibilità" del codice.
#region
è ciò che serve, con una stringa del titolo appropriata. E un commento sul metodo.
Un esempio diverso è fornito da System.Collections.Immutable
, in cui gli autori hanno scelto di utilizzare la tecnica per preservare un'API familiare per i tipi di raccolta mentre raschiavano via le parti dell'interfaccia che non hanno alcun significato per i loro nuovi tipi.
Concretamente, ImmutableList<T>
attrezzi IList<T>
e quindi ICollection<T>
( al fine di consentire ImmutableList<T>
un utilizzo più facilmente con il codice legacy), ma void ICollection<T>.Add(T item)
non ha senso per ImmutableList<T>
: poiché l'aggiunta di un elemento di una lista immutabile non deve modificare l'elenco esistente, ImmutableList<T>
anche deriva da IImmutableList<T>
cui IImmutableList<T> Add(T item)
può essere utilizzato per elenchi immutabili.
Quindi, nel caso di Add
, le implementazioni ImmutableList<T>
finiscono per apparire come segue:
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();
In caso di interfacce definite in modo esplicito, tutti i metodi sono automaticamente privati, non è possibile fornire loro un modificatore di accesso pubblico. Supponiamo che:
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
Questo è il modo in cui possiamo creare un'interfaccia esplicita: se abbiamo 2 interfacce ed entrambe le interfacce hanno lo stesso metodo e una singola classe eredita queste 2 interfacce quindi quando chiamiamo un metodo di interfaccia il compilatore si è confuso quale metodo deve essere chiamato, quindi possiamo gestire questo problema utilizzando Explicit Interface. Ecco un esempio che ho fornito di seguito.
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();
}
}
}