Prima di tutto, bella domanda. Ammiro la tua attenzione sull'utilità piuttosto che accettare ciecamente le "migliori pratiche". +1 per quello.
Ho letto questa guida prima. Devi ricordare qualcosa a riguardo - è solo una guida, principalmente per i nuovi arrivati di C # che sanno come programmare ma non hanno molta familiarità con il modo in cui C # fa le cose. Non è tanto una pagina di regole quanto una pagina che descrive come le cose sono già fatte normalmente. E poiché sono già fatti in questo modo ovunque, potrebbe essere una buona idea rimanere coerenti.
Arriverò al punto, rispondendo alle tue domande.
Prima di tutto, presumo che tu sappia già cos'è un'interfaccia.
Per quanto riguarda un delegato, è sufficiente dire che si tratta di una struttura contenente un puntatore digitato a un metodo, insieme a un puntatore facoltativo all'oggetto che rappresenta l'argomento this
per quel metodo. In caso di metodi statici, il secondo puntatore è nullo.
Ci sono anche delegati Multicast, che sono simili ai delegati, ma possono avere diverse di queste strutture assegnate a loro (significa che una singola chiamata a Invoke su un delegato multicast richiama tutti i metodi nella sua lista di invocazione assegnata).
Che cosa intendono con un modello di progettazione evento?
Significano usare eventi in C # (che ha parole chiave speciali per implementare in modo sofisticato questo schema estremamente utile). Gli eventi in C # sono alimentati dai delegati multicast.
Quando definisci un evento, come in questo esempio:
class MyClass {
// Note: EventHandler is just a multicast delegate,
// that returns void and accepts (object sender, EventArgs e)!
public event EventHandler MyEvent;
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Il compilatore in realtà trasforma questo nel seguente codice:
class MyClass {
private EventHandler MyEvent = null;
public void add_MyEvent(EventHandler value) {
MyEvent += value;
}
public void remove_MyEvent(EventHandler value) {
MyEvent -= value;
}
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Quindi iscriviti a un evento facendo
MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;
Che compila fino a
MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));
Quindi questo è eventing in C # (o .NET in generale).
In che modo la composizione risulta facile se si utilizza un delegato?
Questo può essere facilmente dimostrato:
Supponiamo di avere una classe che dipende da un insieme di azioni da passarvi. Puoi incapsulare quelle azioni in un'interfaccia:
interface RequiredMethods {
void DoX();
int DoY();
};
E chiunque volesse passare delle azioni alla tua classe avrebbe dovuto prima implementare quell'interfaccia. Oppure potresti semplificarti la vita in base alla seguente classe:
sealed class RequiredMethods {
public Action DoX;
public Func<int> DoY();
}
In questo modo i chiamanti devono solo creare un'istanza di RequiredMethods e associare metodi ai delegati in fase di runtime. Questo è di solito più semplice.
Questo modo di fare le cose è estremamente utile nelle giuste circostanze. Pensaci: perché dipendere da un'interfaccia quando tutto quello che ti interessa è avere una implementazione passata?
Vantaggi dell'uso delle interfacce quando esiste un gruppo di metodi correlati
È vantaggioso utilizzare le interfacce perché le interfacce normalmente richiedono implementazioni esplicite in fase di compilazione. Questo significa che crei una nuova classe.
E se si dispone di un gruppo di metodi correlati in un singolo pacchetto, è utile che il pacchetto sia riusabile da altre parti del codice. Quindi, se possono semplicemente istanziare una classe invece di creare una serie di delegati, è più semplice.
Vantaggi dell'uso di interfacce se una classe richiede solo un'implementazione
Come notato in precedenza, le interfacce vengono implementate in fase di compilazione, il che significa che sono più efficienti rispetto al richiamo di un delegato (che è di per sé un livello di riferimento indiretto).
"Un'implementazione" potrebbe significare un'implementazione che esiste un singolo posto ben definito.
Altrimenti un'implementazione potrebbe provenire da qualsiasi parte del programma che capita semplicemente conforme alla firma del metodo. Ciò consente una maggiore flessibilità, poiché i metodi devono solo conformarsi alla firma prevista, piuttosto che appartenere a una classe che implementa esplicitamente un'interfaccia specifica. Ma questa flessibilità potrebbe comportare un costo e in realtà infrange il Principio di sostituzione di Liskov , perché la maggior parte volte vuoi essere esplicito, perché minimizza la possibilità di incidenti. Proprio come la tipizzazione statica.
Il termine potrebbe anche riferirsi ai delegati multicast qui. I metodi dichiarati dalle interfacce possono essere implementati solo una volta in una classe di implementazione. Ma i delegati possono accumulare più metodi, che saranno chiamati sequenzialmente.
Quindi, tutto sommato, sembra che la guida non sia sufficientemente informativa e funzioni semplicemente come è: una guida, non un manuale. Alcuni consigli potrebbero sembrare un po 'contraddittori. Sta a te decidere quando è giusto applicare cosa. La guida sembra darci solo un percorso generale.
Spero che le tue domande abbiano avuto risposta per la tua soddisfazione. E ancora, complimenti per la domanda.