2010-03-17 30 views
6

Attualmente ho 2 metodi concreti in 2 classi astratte. Una classe contiene il metodo corrente, mentre l'altra contiene il metodo legacy. Per esempio.C# delegato e classe astratta

// Class #1 
public abstract class ClassCurrent<T> : BaseClass<T> where T : BaseNode, new() 
{ 
    public List<T> GetAllRootNodes(int i) 
    { 
     //some code 
    } 
} 

// Class #2 
public abstract class MyClassLegacy<T> : BaseClass<T> where T : BaseNode, new() 
{ 
    public List<T> GetAllLeafNodes(int j) 
    { 
     //some code 
    } 
} 

Voglio che il metodo corrispondente venga eseguito nei relativi scenari nell'app. Sto pensando di scrivere un delegato per gestire questo. L'idea è che posso semplicemente chiamare il delegato e scrivere la logica al suo interno per gestire quale metodo chiamare a seconda di quale classe/progetto viene chiamato (almeno questo è ciò che penso siano i delegati e come vengono usati).

Tuttavia, ho alcune domande su questo argomento (dopo un po 'googling):

1) E' possibile avere un delegato che conosce le (o più) i metodi 2 che risiedono in classi diverse? 2) È possibile creare un delegato che genera classi astratte (come dal codice precedente)? (La mia ipotesi è un no, poiché i delegati creano un'implementazione concreta delle classi passate) 3) Ho provato a scrivere un delegato per il codice sopra. Ma sto essendo tecnicamente sfidato:

public delegate List<BaseNode> GetAllNodesDelegate(int k); 
    GetAllNodesDelegate del = new GetAllNodesDelegate(ClassCurrent<BaseNode>.GetAllRootNodes); 

ho ottenuto il seguente errore:

An object reference is required for the non-static field, method, property ClassCurrent<BaseNode>.GetAllRootNodes(int) 

potrei aver frainteso qualcosa ... ma se devo dichiarare manualmente un delegato alla classe di chiamata, E per passare manualmente la funzione come sopra, quindi sto iniziando a chiedermi se il delegato è un buon modo per gestire il mio problema.

Grazie.

risposta

7

Il modo in cui si sta tentando di utilizzare delegati (costruirli con new, dichiarando un tipo delegato di nome) suggerisce che si sta utilizzando C# 1. Se si sta usando C# 3, è molto più più facile di così

In primo luogo, il tipo di delegato:

public delegate List<BaseNode> GetAllNodesDelegate(int k); 

esiste già. È solo:

Func<int, List<BaseNode>> 

Quindi non è necessario dichiarare la propria versione di esso.

In secondo luogo, si dovrebbe pensare a un delegato come a un'interfaccia con un solo metodo, e si può "implementarlo" al volo, senza dover scrivere una classe con nome. Basta scrivere un lambda o assegnare direttamente un nome al metodo.

Func<int, List<BaseNode>> getNodesFromInt; 

// just assign a compatible method directly 
getNodesFromInt = DoSomethingWithArgAndReturnList; 

// or bind extra arguments to an incompatible method: 
getNodesFromInt = arg => MakeList(arg, "anotherArgument"); 

// or write the whole thing specially: 
getNodesFromInt = arg => 
    { 
     var result = new List<BaseNode>(); 
     result.Add(new BaseNode()); 
     return result; 
    }; 

Un lambda è del formato (arguments) => { body; }. Gli argomenti sono separati da virgola. Se ce n'è solo uno, puoi omettere le parentesi. Se non accetta parametri, inserisci un paio di parentesi vuote: (). Se il corpo è solo una dichiarazione lunga, puoi omettere le parentesi graffe. Se è solo una singola espressione, puoi omettere le parentesi e la parola chiave return. Nel corpo, è possibile fare riferimento praticamente a qualsiasi variabile e metodo dall'ambito di inclusione (ad eccezione dei parametri ref/out per il metodo di inclusione).

Non è quasi mai necessario utilizzare new per creare un'istanza delegata.E raramente una necessità di dichiarare tipi di delegati personalizzati. Utilizzare Func per i delegati che restituiscono un valore e Action per i delegati che restituiscono void.

Ogni volta che la cosa che devi passare è come un oggetto con un metodo (un'interfaccia o una classe), quindi usa un delegato e sarai in grado di evitare un sacco di problemi.

In particolare, evitare di definire interfacce con un metodo. Sarà solo dire che, invece di essere in grado di scrivere un lambda di attuare tale metodo, si dovrà dichiarare una classe denominata separata per ogni diversa applicazione, con il modello:

class Impl : IOneMethod 
{ 
    // a bunch of fields 

    public Impl(a bunch of parameters) 
    { 
     // assign all the parameters to their fields 
    } 

    public void TheOneMethod() 
    { 
     // make use of the fields 
    } 
} 

Un lambda fa in modo efficace tutto ciò che per te, eliminando tali schemi meccanici dal tuo codice. Basta dire:

() => /* same code as in TheOneMethod */ 

Essa ha anche il vantaggio che è possibile aggiornare le variabili nel perimetro che racchiude, in quanto è possibile fare riferimento direttamente a loro (invece di lavorare con i valori copiati nei campi di una classe). A volte questo può essere uno svantaggio, se non vuoi modificare i valori.

+0

Se si desidera che il delegato esegua il mapping su un metodo specifico se digita 'ClassCurrent' (il' GetAllRootNodes') e un altro metodo se è di tipo 'MyClassLegacy' (il' GetAllLeaveNodes'), allora si dovrebbe conoscere il tipo dell'istanza. Considerando che se condivide un'interfaccia comune non è necessario conoscere il tipo solo per implementare l'interfaccia specificata. Il codice per l'impostazione del delegato per ogni tipo dovrebbe contenere un se per controllare il tipo o un metodo che accetta il tipo specifico come argomento per ogni tipo consentito. – Cornelius

+0

@Cornelius - cosa pensi che sia un delegato? Sembra che tu pensi che sia lo stesso di un puntatore a funzione in C. –

+2

i delegati sono più potenti dei puntatori di funzione essendo che sono "metodi di prima classe" e hanno la chiusura, ma questo ti permetterebbe solo di passarli polimorficamente e non lo faranno eliminare la complessità dell'impostazione come nel commento sopra. – Cornelius

1

È possibile avere un delegato inizializzato con riferimenti a metodi diversi a seconda di alcune condizioni.

Per le vostre domande:
1) Non sono sicuro di cosa intendiate con "sa". È possibile passare qualsiasi metodo al delegato, quindi se è possibile scrivere un metodo che "conosce" alcuni metodi diversi da quello che si può fare con un delegato simile.
2) Di nuovo, i delegati possono essere creati da qualsiasi metodo che può essere eseguito. Ad esempio, se si dispone di una variabile locale inizializzata di tipo ClassCurrent<T>, è possibile creare un delegato per qualsiasi metodo di istanza di tipo ClassCurrent<T>.
3) Il delegato può chiamare solo il metodo che può essere effettivamente chiamato. Voglio dire che non è possibile chiamare ClassCurrent.GetAllRootNodes perché GetAllRootNodes non è un metodo statico, quindi è necessario un istanza di ClassCurrent per chiamarlo.

Il delegato può soggiornare in qualsiasi classe che abbia accesso allo ClassCurrent e MyClassLegacy.

Ad esempio è possibile creare smth come:

class SomeActionAccessor<T> 
{ 
    // Declare delegate and fied of delegate type. 
    public delegate T GetAllNodesDelegate(int i); 

    private GetAllNodesDelegate getAllNodesDlg; 

    // Initilaize delegate field somehow, e.g. in constructor. 
    public SomeActionAccessor(GetAllNodesDelegate getAllNodesDlg) 
    { 
     this.getAllNodesDlg = getAllNodesDlg; 
    } 

    // Implement the method that calls the delegate. 
    public T GetAllNodes(int i) 
    { 
     return this.getAllNodesDlg(i); 
    } 
} 

I delegati possono avvolgere sia statica che metodo di istanza. L'unica differenza è che per i delegati di creazione con metodo di istanza è necessaria l'istanza della classe che possiede il metodo.

+0

Una volta creato il delegato, ad es. delegato pubblico void del (int i), il codice rimane in una particolare classe. Stavo pensando che il delegato ha bisogno di stare nella classe che ha i metodi di interesse altrimenti non sarà in grado di trovarli. Inoltre, non capisco perché i delegati possono creare un metodo statico? – BeraCim

+0

Ho aggiornato la risposta. –

0

Perché vuoi un delegato per questo? Sembra troppo complesso. Vorrei solo creare un metodo in una nuova classe che potresti istanziare quando hai bisogno di chiamarti metodo. A questa classe potrebbero essere fornite alcune informazioni di contesto per aiutarla a decidere. Quindi implementerei la logica nel nuovo metodo che deciderebbe se chiamare il metodo corrente o il metodo legacy.

Qualcosa di simile a questo:

public class CurrentOrLegacySelector<T> 
{ 

    public CurrentOrLegacySelector(some type that describe context) 
    { 
    // .. do something with the context. 
    // The context could be a boolean or something more fancy. 
    } 

    public List<T> GetNodes(int argument) 
    { 
    // Return the result of either current or 
    // legacy method based on context information 
    } 
} 

Questo darebbe un wrapper pulita per i metodi che è facile da leggere e capire.

+0

Penso che la soluzione con i delegati sia più flessibile: occorreranno meno azioni per supportare il terzo metodo. D'altra parte il terzo metodo potrebbe non apparire in futuro :) –

+0

Un delegato non è più complicato, è molto più semplice. –

1

Let sia ClassCurrent e MyClassLegacy implementare un'interfaccia INodeFetcher:

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k); 
} 

Per ClassCurrent chiamata al metodo GetAllRootNodes dalla implementazione dell'interfaccia e per MyLegacyClass il metodo GetAllLeaveNodes.

+3

Se si dispone di un metodo nell'interfaccia, utilizzare invece un delegato. Ti risparmierai una * montagna * di codice. Un delegato è come un'interfaccia a un solo metodo, oltre a un sacco di supporto linguistico che significa che non è necessario scrivere classi con nome separato ogni volta che si desidera implementarlo. Il tipo che stai cercando qui è 'Func >'. –

+0

@Daniel ma non sarà possibile utilizzare il delegato in modo polimorfico senza dichiararlo su una classe base comune o un'interfaccia e sarà comunque necessario impostare il delegato che richiederà anche una quantità di codice comparabile. – Cornelius

+0

Vedi la mia risposta. L'impostazione di un delegato richiede molto meno codice rispetto alla dichiarazione e all'implementazione di un'interfaccia. Anche i delegati sono effettivamente polimorfici: il chiamante ottiene solo un valore dal quale è possibile effettuare una chiamata di metodo tramite, non sono vincolati a un'implementazione specifica. È esattamente come un'interfaccia a un solo metodo, ma richiede molto meno codice cerimoniale per usarlo. –

0

Come variante del tema suggerito da Rune Grimstad, penso che potresti utilizzare lo schema di strategia (ad esempio
Introduction to the GOF Strategy Pattern in C#
).

Questo sarebbe particolarmente interessante nel caso in cui non è possibile modificare il LegacyClass (e quindi forse non può facilmente utilizzare l'"approccio di interfaccia" suggerito da Cornelius) e se si utilizza l'integrazione di dipendenza (DI; Dependency injection). DI (forse) ti permetterà di iniettare la corretta implementazione (strategia concreta) nel posto giusto.

Strategia:

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k); 
} 

strategie concrete:

public class CurrentSelector<T> : INodeFetcher<T> 
{ 
    public List<T> GetNodes(int argument) 
    { 
    // Return the result "current" method 
    } 
} 

public class LegacySelector<T> : INodeFetcher<T> 
{ 
    public List<T> GetNodes(int argument) 
    { 
    // Return the result "legacy" method 
    } 
} 

-> Iniettare/un'istanza della strategia concreta corretta.

saluti

+0

Ma l'interfaccia ha un solo metodo. Questa è semplicemente una ricetta per un sacco di codifiche manuali inutili che vengono eliminate usando un delegato. –

+0

Dove mi manca qualcosa? Tra l'altro avrete notato che non sono fluente nella programmazione dei delegati; se potessi, per favore, sii gentile :) – scherand

+0

hai visto la mia risposta? Non sono sicuro di cos'altro aggiungere ad esso ... se sei interessato solo a come appaiono oggi i tuoi requisiti, allora non hai davvero bisogno di organizzare il tuo codice in un modo particolare. Quasi tutte le funzionalità linguistiche di alto livello riguardano la semplificazione dell'evoluzione futura del tuo programma. Quindi oggi potresti avere solo due implementazioni, ma per quanto riguarda il futuro? –

Problemi correlati