Pergunta

Editar: Corrigido vários problemas de sintaxe e consistência para tornar o código um pouco mais aparente e próximo ao que estou fazendo.

Eu tenho algum código que se parece com o seguinte:

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

onde o DoSomething O método é um método de extensão e espera que uma funce tenha passado para ele. Portanto, cada um dos métodos chama em cada um dos retornos do dosomething => Lambda um tipo de resultado.

Isso é semelhante a um Talvez Mônada. Exceto, em vez de verificar os nulos, estou verificando o status da classe de resultado e chamando o func que foi passado para o que devolvendo ou retornando o resultado anterior sem ligar para o func

O problema que enfrento é que deseja ter esse tipo de composição no meu código, mas também preciso poder passar dados de um dos resultados da chamada composta na chamada de outro, como você pode ver com o someClass variável.

Minha pergunta não é se isso é tecnicamente correto ... Eu sei que isso funciona, porque atualmente estou fazendo isso. Minha pergunta é se isso é ou não um abuso de fechamentos, ou separação de comando, ou quaisquer outros princípios ... e depois perguntar que melhores padrões existem para lidar com essa situação, porque tenho certeza de que estou Preso no modo "novo martelo brilhante" com esse tipo de código, agora.

Foi útil?

Solução

Como já foi observado, você quase implementou uma mônada aqui.

Seu código é um pouco deselegante, pois os Lambdas têm efeitos colaterais. Mônadas resolvem isso de maneira mais elegante.

Então, por que não transformar seu código em uma mônada adequada?

Bônus: você pode usar a sintaxe do LINQ!


Eu apresento:

Linq para resultados

 
Exemplo:

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

Com Linq para resultados, isso primeiro executa SomeThingHappensHere. Se isso for bem -sucedido, obtém o valor do Data propriedade do resultado e executa SomeOtherThingHappensHere. Se isso for bem -sucedido, ele executa AndYetAnotherThing, e assim por diante.

Como você pode ver, você pode facilmente encadear operações e consultar os resultados de operações anteriores. Cada operação será executada uma após a outra e a execução será interrompida quando um erro for encontrado.

o from x in Bit cada linha é um pouco barulhenta, mas na IMO nada de complexidade comparável ficará mais legível que isso!


Como fazemos isso funcionar?

Mônadas em C# consistem em três partes:

  • um tipo Algo de t,

  • Select/SelectMany métodos de extensão para isso e

  • um método para converter um T dentro de Algo de t.

Tudo o que você precisa fazer é criar algo que parece uma mônada, parece uma mônada e cheira a uma mônada, e tudo funcionará automaticamente.


Os tipos e métodos para Linq para resultados são como segue.

Resultadou003CT> modelo:

Uma classe direta que representa um resultado. Um resultado é um valor do tipo T, ou um erro. Um resultado pode ser construído a partir de um T ou de um Exceção.

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étodos de extensão:

Implementações para o Select e SelectMany métodos. As assinaturas de método são fornecidas nas especificações C#, então tudo o que você precisa se preocupar são as implementações deles. Isso vem naturalmente se você tentar combinar todos os argumentos do método de maneira 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));
    }
}

Você pode modificar livremente o resultadou003CT> Classe e os métodos de extensão, por exemplo, para implementar regras mais complexas. Somente as assinaturas dos métodos de extensão devem ser exatamente como declarados.

Outras dicas

Parece -me que você construiu algo muito parecido com uma mônada aqui.

Você pode torná -lo uma mônada adequada, fazendo seu delegado tipo A Func<SomeClass, SomeClass>, tem alguma maneira de configurar o inicial SomeClass valor para passar e ter DoSomething Passe o valor de retorno de um como o parâmetro do próximo - isso seria explícito do encadeamento, em vez de confiar no estado compartilhado com escopo lexicamente.

A fraqueza deste código é o acoplamento implícito entre o primeiro e o segundo Lambdas. Não tenho certeza da melhor maneira de corrigi -lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top