Domanda

Modifica:. Corretto diversi problemi di sintassi e di coerenza per rendere il codice un po 'più evidente e vicino a quello che in realtà sto facendo

Ho avuto qualche codice che assomiglia a questo:

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);

dove il metodo DoSomething è un metodo di estensione, e prevede un Funz passato in esso. Così, ciascuna delle chiamate di metodo in ciascuno dei rendimenti del DoSomething => di lambda un tipo di conseguenza.

questo è simile a un Forse monade . Tranne invece di controllare per i valori nulli, sto controllando lo stato della classe Risultato, e sia chiamando il Funz che è stato passato in DoSomething o restituendo il risultato precedente senza chiamare il Func

il volto problema che ho è che voglio avere questo tipo di composizione nel mio codice, ma ho anche bisogno di essere in grado di passare i dati da uno dei risultati delle chiamate composte nella chiamata di un altro, come si può vedere con la someClass variabile.

La mia domanda non è se questo è tecnicamente corretto ... So che questo funziona, perché attualmente sto facendo. La mia domanda è se questo è un abuso di chiusure, o la separazione di comando-query, o qualsiasi altri principi ... e poi per chiedere quali modelli migliori ci sono per la gestione di questa situazione, perché sono abbastanza sicuro che io sono bloccato in un modo "lucido nuovo martello" con questo tipo di codice, al momento.

È stato utile?

Soluzione

Come è già stato notato, hai quasi implementato una Monade qui.

Il codice è un po 'poco elegante in quanto le lambda hanno effetti collaterali. Monadi risolvono questo più elegante.

Quindi, perché non trasformare il vostro codice in una vera e propria Monade?

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 , questo primo esegue SomeThingHappensHere. Se questo riesce, si ottiene il valore della proprietà Data del risultato e esegue SomeOtherThingHappensHere. Se questo riesce, esegue AndYetAnotherThing, e così via.

Come si può vedere, si può facilmente catena di operazioni e fare riferimento ai risultati delle precedenti operazioni. Ogni operazione verrà eseguita una dopo l'altra, e l'esecuzione si arresta quando viene rilevato un errore.

Il from x in 'ogni linea è un po' rumoroso, ma IMO nulla di complessità paragonabile otterrà più leggibile di questo!


Come facciamo a fare questo lavoro?

Monadi in C # è composto da tre parti:

  • un tipo di Qualcosa-of-T ,

  • Select / SelectMany metodi di estensione per esso, e

  • un metodo per convertire un T in un Qualcosa-of-T .

Tutto quello che devi fare è creare qualcosa che assomiglia a una Monade, si sente come un Monade e odori come una monade, e tutto funzionerà automagicamente.


I tipi e metodi per LINQ to Risultati sono i seguenti.

Risultato Tipo:

Una classe semplice che rappresenta un risultato. Un risultato è un valore di tipo T , o un errore. Un risultato può essere costruito 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:

Le implementazioni per i metodi Select e SelectMany. Le firme metodo figurano nella C # spec, quindi tutto ciò che dovete preoccupare è loro implementazioni. Questi vengono del tutto naturale se si tenta di combinare tutti gli argomenti di 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 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.

Altri suggerimenti

mi sembra che hai costruito qualcosa di molto simile ad una monade qui.

Si potrebbe rendere una monade corretta facendo digitare il delegato un Func<SomeClass, SomeClass>, avere un modo per impostare il valore iniziale SomeClass a passare in, e hanno DoSomething passare il valore di ritorno di uno come il parametro del prossimo - questo Sarebbe per rendere l'esplicito concatenamento piuttosto che basarsi su stato condiviso con scope lessicale.

La debolezza di questo codice è l'accoppiamento implicita tra il primo e secondo lambda. Non sono sicuro che il modo migliore per risolvere il problema.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top