2013-04-02 13 views
7

Ho molti metodi che richiedono un po 'di registrazione con lo stesso modello. Alcuni metodi devono restituire un valore, altri no. Ho creato un metodo con parametro Action per evitare copypasting di tutta la logica. Ecco come si presenta:Combinazione di azione e Func in un parametro

private void Execute(Action action) 
{ 
    Logger.Start(); 
    try 
    { 
     action(); 
    } 
    catch(Exception exception) 
    { 
     Logger.WriteException(); 
     throw; 
    } 
    finally 
    { 
     Logger.Finish(); 
    } 
} 

ora ho alcune chiamate che in quel modo

public void DoSomething(string parameter) 
{ 
    Execute(() => GetProvider(parameter).DoSomething()); 
} 

ma ho bisogno di qualche funzione che restituiscono valori. Quali sono i modi migliori per farlo? Ho trovato due ora:

1) Creare una copia del metodo Execute con Funz

private T Execute<T>(Func<T> action) 
{ 
    Logger.Start(); 
    try 
    { 
     return action(); 
    } 
    catch(Exception exception) 
    { 
     Logger.WriteException(); 
     throw; 
    } 
    finally 
    { 
     Logger.Finish(); 
    } 
} 

questo metodo funziona, ma ha incollare qualche copia pure.

2) ingannare i parametri in essere un'azione:

public Result DoSomething(string parameter) 
{ 
    Result result = null; 
    Execute(() => result = GetProvider(parameter).DoSomething()); 
    return result; 
} 

Questo non richiede copia incolla, ma non sembra così bello.

C'è un modo per unire Action e Func in qualche modo per evitare uno di questi metodi o potrebbe esserci un altro modo per ottenere lo stesso risultato?

+0

Io uso il secondo dei tuoi approcci. Non sono riuscito a trovare un buon modo per farlo in un altro modo, quindi sarei interessato a vedere qualsiasi risposta alla tua domanda! –

+0

Sembra essere correlato: http://stackoverflow.com/q/4279210/55209 –

+0

considera l'utilizzo degli aspetti http://stackoverflow.com/questions/1416880/aspect-oriented-programming-in-c-sharp – Lanorkin

risposta

6

Una terza opzione è quella di ancora sovraccaricare Execute, ma fare il lavoro Action versione in termini di versione Func:

private void Execute(Action action) 
{ 
    // We just ignore the return value here 
    Execute(() => { 
     action(); 
     return 0; 
    }); 
} 

Naturalmente tutto questo sarebbe più semplice se void erano più come un tipo "reale" (come Unit in F # e altri), a quel punto potremmo avere Task<T> invece di Task e Task<T> così ...

+0

Ho pensato anche a questo metodo, ma non lo includo nemmeno perché mi sembra abbastanza sporco perché stai fingendo di essere un Func usando int per un tipo generico e restituendo il valore predefinito che non è mai usato. –

+1

@IlyaChernomordik: Penso che sia più pulito dell'assegnazione a una variabile locale all'interno di un 'Action' per farlo agire come un' Func ', personalmente. Ma il punto è che è all'interno di "Esegui", non in "DoSomething" - hai solo bisogno di questa "sporcizia" in un singolo luogo, non in più chiamanti. –

+0

@ JonSkeet Forse hai ragione ed è meno sporco. O potrebbe essere lo stesso sporco ma in un unico posto :) –

3

creare una copia di Execute che converte il Func int o un Action. Basta scrivere che il codice brutto una volta, e non si finisce con una seconda copia completa del metodo Execute:

private T Execute<T>(Func<T> func) 
{ 
    T result = default(T); 
    this.Execute(() => { result = func(); }); 
    return result; 
} 

... 

public Result DoSomething(string parameter) 
{ 
    return Execute(() => GetProvider(parameter).DoSomething()); 
} 
+0

@ p.s.w.g Il problema con questo approccio è che ho sia tipi di valore che classi come ritorno. E il tuo codice non verrà compilato perché la variabile locale non è inizializzata prima del ritorno. –

+0

@IlyaChernomordik Basta inizializzare 'result' a' default (T) '. Vedi la mia risposta aggiornata. –

+0

@ p.s.w.g Giusto, non ho pensato a quello di default. In realtà questo aggiornamento è piuttosto logico per il mio secondo metodo, non so perché non ne ho ancora parlato. –

2

Ecco un'altra opzione. Invece di fare in modo che il framework di logging chiami il tuo codice reale, chiedi al tuo attuale codice di chiamare il framework di registrazione. Qualcosa del genere farebbe il trucco (molto semplificato).

public class LoggerScope : IDisposable { 

    private bool disposed; 

    public LoggerScope() { 
     Logger.Start(); 
    } 

    public void Dispose() { 
     if(!disposed) { 
      Logger.Finish(); 
      disposed = true; 
     } 
    } 
} 

utilizzati come segue:

 using(var scope = new LoggerScope()) { 
      // actual code goes here 
     } 

eccezioni Gestire separatamente con la cattura e la registrazione solo una volta al livello superiore del vostro codice.

Vantaggi:

  • evita la necessità di lambda in tutto il posto, in modo che la traccia dello stack eccezione è un po 'più pulito.
  • È possibile aggiungere dati contestuali arbitrari alla classe LoggerScope, ad es. GUID, timestamp, testo di descrizione dell'attività logica.
+0

Lo svantaggio è che devi creare una classe speciale per ogni caso speciale. Anche se ho usato questo approccio per fare il lock reader reader. –