8

Edycja: poprawiono kilka błędów składni i spójności, aby kod był trochę bardziej widoczny i bliski temu, co faktycznie robię.Nadużycie zamknięć? Naruszenia różnych zasad? Lub ok?

Mam niektóre kodu, który wygląda tak:

SomeClass someClass; 
var finalResult = 
    DoSomething(() => 
    { 
    var result = SomeThingHappensHere(); 
    someClass = result.Data; 
    return result; 
    }) 
    .DoSomething(() => return SomeOtherThingHappensHere(someClass)) 
    .DoSomething(() => return AndYetAnotherThing()) 
    .DoSomething(() => return AndOneMoreThing(someClass)) 
    .Result; 

HandleTheFinalResultHere(finalResult); 

gdzie metoda DoSomething stanowi metodę rozszerzenia, i oczekuje Func przeszedł do niego. Tak więc każde wywołanie metody w każdym z parametrów DoSomething => lambda zwraca typ wyniku.

Jest to podobne do Maybe monad. Z wyjątkiem zamiast sprawdzania wartości zerowych sprawdzam status klasy Wynik i albo wywołuję Func, który został przekazany DoSomething, albo zwraca poprzedni Wynik bez wywoływania Func Problemem jest to, że chcę mieć to rodzaj składu w moim kodzie, ale muszę też móc przekazywać dane z jednego z złożonych wyników wywołania do wywołania innego, jak widać przy zmiennej someClass.

Moje pytanie nie brzmi, czy jest to technicznie poprawne ... wiem, że to działa, ponieważ obecnie to robię. Moje pytanie dotyczy tego, czy jest to nadużycie zamknięcia, rozkazu separacji poleceń, czy jakiekolwiek inne zasady ... a następnie zapytać, jakie są lepsze wzorce postępowania z tą sytuacją, ponieważ jestem dość pewna, że ​​jestem utknął w trybie "błyszczący nowy młotek" z tego typu kodem, właśnie teraz.

+4

Nie jest to łatwe do odczytania ... –

+0

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 ... –

+2

Ojej, moje oczy .... –

Odpowiedz

11

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:

  • A Typ coś-of-T,

  • Select/SelectMany sposobów rozszerzania dla niego i

  • się metoda przekształcania T w Something-of-T.

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.

+0

Syn-z-a ... Zmarnowałem godzinę, pracując nad tym. Dobry towar. –

+0

Tak, uwielbiam to. –

+0

Awesome. Przez jakiś czas zajmowałem się zagadkowymi monadami, co miało dużo sensu i na pewno pomogło mi zrozumieć. Czy nie możesz przenieść dużej części logiki w Select/SelectMany rozszerzeń do Bind (Func ) lub podobnej? Nie do końca pewna składnia, ale usunie wiele powielania metod rozszerzenia. – Neal

2

Wygląda na to, że zbudowałeś coś podobnego do monady.

Można zrobić to właściwa monada poprzez swoją delegat wpisać Func<SomeClass, SomeClass>, mieć jakiś sposób, aby ustawić wartość początkową SomeClass przejść w, i DoSomething przechodzą zwracanej wartości jednego parametru jako następny - spowodowałoby to, że łańcuchy byłyby wyraźne, a nie polegałyby na leksykalnym wspólnym zasięgu.

+0

Tak, badałem/uczę się ostatnio monady i próbowałem stworzyć coś podobnego. Problem z twoją sugestią polega na tym, że zawsze muszę zwrócić "wynikową" klasę z funkcji, aw jednym przypadku muszę również pobrać dane "someClass" z jednej funkcji do następnej. –

+0

+1 Efekty uboczne są trudne do zrozumienia i przetestowania. Podziękujesz sobie później, jeśli zmienisz na 'Func' zamiast' Action' i jawnie wykonasz operacje łańcuchowe. –

+0

Dlaczego typ wyniku jest tak krytyczny? Czy przekazanie go przez czyjś kod nie może być modyfikowane przed powrotem do kolejnego linku w łańcuchu kompozycji? –

0

Słabością tego kodu jest niejawne powiązanie między pierwszą a drugą lambdą. Nie jestem pewien najlepszego sposobu, aby to naprawić.

+0

zdecydowanie. to zły projekt i sprzężenie semantyczne - musisz wiedzieć, co dzieje się pod maską, aby zrozumieć konsekwencje wyciągania wartości z jednego do drugiego. –

Powiązane problemy