Pregunta

Editar:. Corregido diversos problemas de sintaxis y consistencia para que el código un poco más aparente y cerca de lo que en realidad estoy haciendo

Tengo un código que es similar al siguiente:

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

donde el método DoSomething es un método de extensión, y que espera una Func pasó en ella. Así, cada una de las llamadas a métodos en cada uno de los retornos del HacerAlgo => lambda de un tipo de resultado.

esto es similar a un Tal vez mónada . Pero en lugar de la comprobación de valores nulos, estoy comprobando el estado de la clase de resultados, y, o bien llamando al Func que fue aprobada en HacerAlgo o devolver el resultado anterior sin llamar a la Func

la cara problema que es que quieren tener este tipo de composición en mi código, pero también tienen que ser capaces de pasar datos de uno de los resultados de llamadas compuestas en la llamada de otro, como se puede ver con el someClass variable.

Mi pregunta no es si es o no es técnicamente correcto ... Sé que esto funciona, porque estoy haciendo actualmente. Mi pregunta es si esto es o no un abuso de los cierres, o la separación de consulta de comandos, o cualquier otro principio ... y luego preguntar qué mejores patrones existen para el manejo de esta situación, porque estoy bastante seguro de que estoy atascado en un modo "nuevo y brillante martillo" con este tipo de código, en este momento.

¿Fue útil?

Solución

Como ya se ha señalado, se ha implementado una mónada casi aquí.

Su código es un poco elegante poco en que los lambdas tienen efectos secundarios. Mónadas resuelven este modo más elegante.

Así que, ¿por qué no convertir su código en una mónada adecuada?

Bono: se puede utilizar la sintaxis de LINQ


I presente:

LINQ a los resultados


Ejemplo:

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 a los resultados , este primer ejecuta SomeThingHappensHere. Si eso tiene éxito, se obtiene el valor de la propiedad Data del resultado y ejecuta SomeOtherThingHappensHere. Si que tiene éxito, se ejecuta AndYetAnotherThing, y así sucesivamente.

Como se puede ver, usted puede fácilmente operaciones de la cadena y se refieren a los resultados de las operaciones anteriores. Cada operación se ejecutará una tras otra, y la ejecución se detendrá cuando se detecta un error.

El from x in mordió cada línea es un poco ruidoso, pero En mi opinión nada de complejidad comparable obtendrá más legible que esto!


¿Cómo podemos hacer este trabajo?

Mónadas en C # constan de tres partes:

  • un tipo Algo-of-T ,

  • Select / SelectMany métodos de extensión para ello, y

  • un método para convertir un T en un Algo-of-T .

Todo lo que necesita hacer es crear algo que se parece a una Mónada, se siente como una mónada y huele como una mónada, y todo va a funcionar automágicamente.


Los tipos y métodos para LINQ a los resultados son como sigue.

Resultado , escriba:

Una clase sencilla que representa un resultado. Un resultado es o bien un valor de tipo T , o un error. Un resultado puede ser construido a partir de un T o de un Excepción .

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

Los métodos de extensión:

Las implementaciones para los métodos Select y SelectMany. Las firmas de los métodos se dan en la especificación C #, por lo que todo lo que tiene que preocuparse es de sus implementaciones. Estos vienen de forma natural si se intenta combinar todos los argumentos del método de una manera significativa.

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

Se puede modificar libremente el resultado clase y los métodos de extensión, por ejemplo, para implementar reglas más complejas. Sólo las firmas de los métodos de extensión deben ser exactamente como se indica.

Otros consejos

Me parece que usted ha construido algo muy similar a una mónada aquí.

Se podría hacer una mónada adecuada al hacer que su delegado escriba un Func<SomeClass, SomeClass>, tener alguna manera de configurar el valor inicial SomeClass pasar en, y tienen DoSomething pasar el valor de retorno de uno como el parámetro de la siguiente - este ¿Podría hacer que el encadenamiento explícita en lugar de depender de estado compartido ámbito léxico.

La debilidad de este código es el acoplamiento implícito entre la primera y segunda lambdas. No estoy seguro de la mejor manera de solucionarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top