Qual é a maneira mais elegante de escrever um método "Try" no C # 7?


21

Estou escrevendo um tipo de implementação de Fila que possui um TryDequeuemétodo que usa um padrão semelhante a vários TryParsemétodos do .NET , onde retorno um valor booleano se a ação for bem-sucedida e uso um outparâmetro para retornar o valor real de saída da fila.

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

Agora, gosto de evitar outparâmetros sempre que posso. O C # 7 nos fornece delcarações variáveis ​​para facilitar o trabalho com eles, mas ainda considero os parâmetros mais um mal necessário do que uma ferramenta útil.

O comportamento que eu quero desse método é o seguinte:

  • Se houver um item para desenfileirar, devolva-o.
  • Se não houver itens a serem desenfileirados (a fila está vazia), forneça ao chamador informações suficientes para agir adequadamente.
  • Não basta retornar um item nulo se não houver itens restantes.
  • Não lance uma exceção se estiver tentando desenfileirar de uma fila vazia.

No momento, um chamador desse método quase sempre usava um padrão como o seguinte (usando a sintaxe da variável C # 7):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

O que não é o pior, ao todo. Mas não posso deixar de sentir que pode haver maneiras mais agradáveis ​​de fazer isso (estou totalmente disposto a admitir que talvez não exista). Eu odeio como o chamador tem que interromper seu fluxo com um condicional quando tudo o que ele quer é obter um valor, se houver.

Quais são outros padrões existentes para realizar esse mesmo comportamento de "tentativa"?

Por contexto, esse método pode ser chamado potencialmente em projetos VB, portanto, pontos de bônus por algo que funciona bem em ambos. Esse fato deve ter muito pouco peso, no entanto.


9
Defina uma Option<T>estrutura e retorne-a. Essas bool Try(..., out data)funções são uma abominação.
CodesInChaos

2
Eu estava pensando semelhante ... Talvez <T>, opção <T>, se alguém é adverso aos parâmetros OUT.
Jon Raynor

1
@FrustratedWithFormsDesigner bem, eu não "tentei" outras coisas (trocadilhos são bem-vindos), mas pensei nelas. Tive a ideia de retornar um ValueTuple, mas, na melhor das hipóteses, acho que isso não ofereceu muita melhoria.
Eric Sondergard

@CodesInChaos Estamos na mesma página, é por isso que estou aqui! E eu gosto dessa ideia. Se você tiver tempo, gostaria de dar mais detalhes em uma resposta para que eu possa aceitá-lo?
Eric Sondergard

Respostas:


24

Use um tipo de opção, ou seja, um objeto de um tipo que tenha duas versões, normalmente chamado de "alguns" (quando um valor está presente) ou "nenhum" (quando não há um valor) ... Ou ocasionalmente eles são chamados Just and Nothing. Existem funções nesses tipos que permitem acessar o valor se estiver presente, testar a presença e, o mais importante, um método que permite aplicar uma função que retorna uma opção adicional ao valor se estiver presente (normalmente em C #, isso deve ser chamado FlatMap, embora em outros idiomas seja frequentemente chamado de Vincular ... O nome é crítico em C # porque, com o método s deste nome e tipo, você pode usar seus objetos Option nas instruções LINQ).

Recursos adicionais podem incluir métodos como IfPresent e IfNotPresent para invocar ações nas condições relevantes, e OrElse (que substitui um valor padrão quando nenhum valor está presente, mas não é um op op) e assim por diante.

Seu exemplo pode parecer algo como:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

Este é o padrão de mônada Opção (ou Talvez) e é extremamente útil. Existem implementações existentes (por exemplo, https://github.com/nlkl/Optional/blob/master/README.md ), mas também não é difícil.

(Você pode estender esse padrão para retornar uma descrição da causa do erro em vez de nada quando o método falhar ... Isso é totalmente viável e geralmente é chamado de Mônada Ou; como o nome indica, você pode usar o mesmo FlatMap padrão para facilitar o trabalho também nesse caso)


Esta é definitivamente uma boa opção, obrigado pela sugestão e pelos seus pensamentos. Eu estava realmente procurando explorar mais idéias aqui.
Eric Sondergard

2
O bom de Option/ Maybeé que ela é uma mônada (se implementada como uma, é claro), o que significa que ela pode ser encadeada, empacotada, desembrulhada e processada com segurança, sem precisar lidar diretamente com os diferentes casos, e esse encadeamento e o processamento pode ser feito com expressões de consulta LINQ.
Jörg W Mittag

3
A propósito, flatMap/ bindé chamado SelectManyno .NET.
Jörg W Mittag

5
Gostaria de saber se seria uma idéia melhor escolher um nome diferente, pois, ao fazer isso, você está violando as Try...convenções de nomenclatura no .NET (e potencialmente confuso mantenedores).
22417 Bob

@ Bob Esse é um ótimo ponto. Concordo.
Eric Sondergard

12

As respostas dadas são boas e eu iria com uma delas. Considere esta resposta apenas preenchendo alguns cantos com algumas idéias alternativas:

  • Faça subclasses de Messagecalls SomeMessagee NoMessage. Dequeueagora pode retornar NoMessagese não houver mensagem e SomeMessagese houver uma mensagem. Se o receptor tentar detectar em qual caso está, poderá fazê-lo facilmente por inspeção de tipo. Se não o fizerem, simplesmente NoMessagenão façam nada sempre que qualquer um de seus métodos for chamado e, ei, eles recebem o que pediram.

  • O mesmo que o acima, mas Messageconvertê- lo implicitamente em bool(ou implementar operator true / operator false). Agora você pode dizer if (message)e fazer com que seja verdade se a mensagem for boa e falsa se for ruim. (Essa é quase certamente uma má ideia, mas eu a incluo por completo!)

  • O C # 7 possui tuplas. Retornar uma (bool, Message)tupla.

  • Faça Messageum tipo de estrutura e retorne Message?.


Ei, obrigado pela sua resposta. Com essa pergunta, eu estava tentando me expor a idéias diferentes e tentando encontrar uma solução para meu problema específico. Felicidades!
Eric Sondergard

Quero dizer, se sua "má ideia" é usada pelo Unity, por que não podemos usá-lo?
Arturo Torres Sánchez

@ ArturoTorresSánchez: Sua pergunta é "alguém usou uma prática de programação muito ruim, então por que não posso?" A questão é incoerente. Ninguém está impedindo você de usar as mesmas práticas ruins de programação de outra pessoa. Você vai em frente. Isso não tornará uma boa prática em C #.
Eric Lippert

Era língua na bochecha. Eu acho que preciso usar "/ s" para marcá-lo.
Arturo Torres Sánchez

@ ArturoTorresSánchez: Este é um site de perguntas e respostas; Considero que perguntas são perguntas que procuram respostas .
Eric Lippert

10

No C # 7, você pode usar a correspondência de padrões para obter o mesmo de uma maneira mais elegante:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

Nesse caso em particular, eu provavelmente usaria eventos em vez de pesquisar a fila repetidamente.


3
Isso não requer TryDequeue que retorne alguma interface de marcador com uma Messageimplementação e alguma implementação "nada"? Claro, é mais elegante no site de chamadas, mas você está preso ao implementar três implementações no código real, o que pode ser impossível se Messagefor um tipo existente.
Telastyn 16/03/19

1
@Telastyn: Também funciona se retornar apenas nulo . Você também pode usar um tipo de opção.
JacquesB

@ JacquesB: mas e se nulo for um item em fila válido?
JBSnorro 22/04

5

Não há nada errado com o método Try ... (com um parâmetro out) para um cenário em que a falha (sem valor) é tão comum quanto o sucesso.

Mas, se você insistir em fazer as coisas de maneira diferente, poderá retornar uma mensagem vazia e apenas enviá-la (não é mais o seu problema) ou adiar a declaração if até que você realmente precise saber se tem uma mensagem com conteúdo ou não.

Observe que alguém tem que fazer o teste em algum momento de qualquer maneira. Eu diria que o teste deve ser feito quando e onde a pergunta é atual. Este é o momento da remoção da fila.


Você fez um bom ponto. Você ainda precisa testar, não importa o que faça. Honestamente, ainda posso estar sem parâmetros neste momento (na verdade, estou expondo várias implementações "TryDequeue" agora, apenas para mexer com cada uma delas). Eu realmente só queria discutir outras opções que possam estar por aí.
Eric Sondergard
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.