Come già notato, hai quasi implementato una Monade qui.
Il tuo codice è un po 'poco elegante in quanto i lambdas hanno effetti collaterali. Le Monadi risolvono questo più elegantemente.
Quindi, perché non trasformare il codice in una Monade corretta?
Bonus: è possibile utilizzare la sintassi LINQ!
I presenti:
LINQ to Risultati
Esempio:
var result =
from a in SomeThingHappensHere()
let someData = a.Data
from b in SomeOtherThingHappensHere(someData)
from c in AndYetAnotherThing()
from d in AndOneMoreThing(someData)
select d;
HandleTheFinalResultHere(result.Value);
Con LINQ to risultati, questa prima esegue SomeThingHappensHere
. Se ciò riesce, ottiene il valore della proprietà Data
del risultato ed esegue SomeOtherThingHappensHere
.Se ciò riesce, esegue AndYetAnotherThing
e così via.
Come si può vedere, è possibile eseguire facilmente operazioni a catena e fare riferimento ai risultati delle operazioni precedenti. Ogni operazione verrà eseguita una dopo l'altra e l'esecuzione si interromperà quando viene rilevato un errore.
Il from x in
bit ogni riga è un po 'rumoroso, ma IMO nulla di simile complessità sarà più leggibile di così!
Come si fa a fare questo lavoro?
Monadi in C# sono costituiti da tre parti:
un tipo Qualcosa-of-T,
Select
/SelectMany
metodi di estensione per esso, e
un metodo per convertire un T in un Something-of-T.
Tutto quello che dovete fare è creare qualcosa che assomiglia a una Monade, si sente come un Monade e gli odori come una monade, e tutto funzionerà automagicamente.
I tipi e metodi per LINQ a Risultati sono i seguenti.
Risultato <T> Tipo:
Una classe semplice che rappresenta un risultato. Un risultato è un valore di tipo T o un errore. Un risultato può essere generato da un T o da un Eccezione.
class Result<T>
{
private readonly Exception error;
private readonly T value;
public Result(Exception error)
{
if (error == null) throw new ArgumentNullException("error");
this.error = error;
}
public Result(T value) { this.value = value; }
public Exception Error
{
get { return this.error; }
}
public bool IsError
{
get { return this.error != null; }
}
public T Value
{
get
{
if (this.error != null) throw this.error;
return this.value;
}
}
}
metodi di estensione:
implementazioni per i metodi Select
e SelectMany
. Le firme del metodo sono fornite nella specifica C#, quindi tutto ciò di cui ti devi preoccupare sono le loro implementazioni. Questi vengono abbastanza naturalmente se si tenta di combinare tutti gli argomenti del metodo in modo significativo.
static class ResultExtensions
{
public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return new Result<TResult>(selector(source.Value));
}
public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return selector(source.Value);
}
public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
{
if (source.IsError) return new Result<TResult>(source.Error);
var intermediate = intermediateSelector(source.Value);
if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
}
}
È possibile modificare liberamente il Risultato <T> classe ed i metodi di estensione, ad esempio, per implementare regole più complesse. Solo le firme dei metodi di estensione devono essere esattamente come indicato.
Non è molto facile da leggere .... –
100% d'accordo. purtroppo, è ancora un miglioramento significativo rispetto a quello che ho iniziato. un po 'più di formattazione potrebbe aiutare ... potrebbe ... –
Ahi, i miei occhi .... –