Jak już wspomniano, prawie zaimplementowano tu Monadę.
Twój kod jest nieco nieelegancki, ponieważ efekty lambda mają skutki uboczne. Monady rozwiązują to bardziej elegancko.
Dlaczego więc nie zamienić kodu w odpowiednią Monadę?
Bonus: możesz użyć składni LINQ!
przedstawiam:
LINQ Wyniki
przykład:
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);
z LINQ Wyniki ten wykonuje najpierw SomeThingHappensHere
. Jeśli to się powiedzie, otrzyma wartość właściwości Data
wyniku i wykona SomeOtherThingHappensHere
.Jeśli to się uda, wykonuje się AndYetAnotherThing
i tak dalej.
Jak widać, można łatwo łączyć operacje i odwoływać się do wyników poprzednich operacji. Każda operacja będzie wykonywana jedna po drugiej, a wykonanie zatrzyma się po napotkaniu błędu.
Bit każdej linii jest nieco hałaśliwy, ale IMO nic o porównywalnej złożoności stanie się bardziej czytelne niż to!
jaki sposób dokonać tej pracy?
Monady C# składa się z trzech części:
Wszystko, co musisz zrobić, to stworzyć coś, co wygląda jak monady, czuje się jak monady i pachnie jak monady, a wszystko będzie działać automagicznie.
rodzaje i sposoby LINQ wyniki są następujące.
Wynik <T> typ:
Prosta klasa, która reprezentuje wynik. Wynikiem jest albo wartość typu T, albo błąd. Wynik można skonstruować z T lub z Wyjątek.
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;
}
}
}
metody rozszerzeń:
implementacje metod Select
i SelectMany
. Sygnatury metody są podane w specyfikacji C#, więc wszystko, co musisz się martwić, to ich implementacje. Są one całkiem naturalne, jeśli spróbujesz połączyć wszystkie argumenty metodyczne w znaczący sposób.
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));
}
}
można dowolnie modyfikować Wynik <T> klasy i metody rozszerzenie, na przykład, w celu realizacji bardziej skomplikowanych zasad. Tylko podpisy metod rozszerzenia muszą być dokładnie takie, jak podano.
Nie jest to łatwe do odczytania ... –
100% uzgodnione. Niestety, wciąż jest to znacząca poprawa w stosunku do tego, od czego zacząłem. trochę więcej formatowania może pomóc ... może ... –
Ojej, moje oczy .... –