Question

Edit:. Plusieurs problèmes de syntaxe et de cohérence pour rendre le code un peu plus apparente et à ce que je suis en train de faire

J'ai un code qui ressemble à ceci:

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

lorsque la méthode de DoSomething est une méthode d'extension, et il attend un Func passé en elle. Ainsi, chacun des appels de méthode dans chacune des déclarations du DoSomething => lambda un type de résultat.

est semblable à un Peut-être monade . Sauf au lieu de vérifier pour les valeurs nulles, je vérifie l'état de la classe de résultats et soit en appelant le Func qui a été adoptée dans DoSomething ou retourner le résultat précédent sans appeler le Func

la face i problème est que, faute d'avoir ce genre de composition dans mon code, mais je dois aussi être en mesure de transmettre des données à partir de l'un des résultats des appels composés à l'appel d'un autre, comme vous pouvez le voir avec le someClass variable.

Ma question est pas si cela est techniquement correct ... Je sais que cela fonctionne, parce que je fais actuellement il. Ma question est de savoir si ou non est un abus de fermeture, ou la séparation de commande requête, ou d'autres principes ... et de demander ce que de meilleurs modèles, il y a pour gérer cette situation, parce que je suis assez sûr que je suis coincé dans un mode « brillant nouveau marteau » avec ce type de code, en ce moment.

Était-ce utile?

La solution

Comme il a été noté, vous avez presque mis en place un Monad ici.

Votre code est un peu inélégante en ce que les lambdas ont des effets secondaires. Monades résoudre ce plus d'élégance.

Alors, pourquoi ne pas transformer votre code dans un Monad approprié?

Bonus: vous pouvez utiliser la syntaxe LINQ


Je vous présente:

LINQ aux résultats


Exemple:

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

LINQ aux résultats , ce premier SomeThingHappensHere exécute. Si cela réussit, il obtient la valeur de la propriété Data du résultat et exécute SomeOtherThingHappensHere. Si cela réussit, il exécute AndYetAnotherThing, et ainsi de suite.

Comme vous pouvez le voir, vous pouvez facilement les opérations de la chaîne et consultez résultats des opérations précédentes. Chaque opération sera exécutée un après l'autre, et l'exécution s'arrête lorsqu'une erreur est rencontrée.

Le from x in bit chaque ligne est un peu bruyant, mais l'OMI rien de complexité comparable obtiendra plus lisible que cela!


Comment pouvons-nous faire ce travail?

Monads en C # se composent de trois parties:

  • type Quelque chose-de-T

  • Select / SelectMany méthodes d'extension pour elle, et

  • une méthode pour convertir un T dans Quelque chose-de-T .

Tout ce que vous devez faire est de créer quelque chose qui ressemble à une monade, se sent comme une monade et les odeurs comme une monade, et tout fonctionne automagiquement.


Les types et méthodes pour LINQ aux résultats sont les suivantes.

Résultat type:

Une classe simple qui représente un résultat. Un résultat est soit une valeur de type T , ou une erreur. Un résultat peut être construit à partir d'un T ou à partir d'un Exception .

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

Méthodes d'extension:

pour les méthodes mises en œuvre de Select et SelectMany. Les signatures de méthode sont données dans la spécification C #, donc tout ce que vous avez à vous soucier est leur mise en œuvre. Ceux-ci viennent tout naturellement si vous essayez de combiner de manière significative tous les arguments de la méthode.

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

Vous pouvez librement modifier le résultat et les méthodes d'extension, par exemple, de mettre en œuvre des règles plus complexes. doivent être exactement comme indiqué que les signatures des méthodes d'extension.

Autres conseils

Me semble que vous avez construit quelque chose de très semblable à une monade ici.

Vous pourriez faire une monade appropriée en faisant saisir votre délégué un Func<SomeClass, SomeClass>, avoir un moyen de mettre en place la valeur SomeClass initiale à passer, et ont DoSomething passer la valeur de retour d'un comme paramètre de la prochaine - ce Plût à faire l'enchaînement explicite plutôt que de compter sur l'état partagé scope lexicalement.

La faiblesse de ce code est le couplage implicite entre les première et seconde lambdas. Je ne suis pas sûr de la meilleure façon de le corriger.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top