Existe uma convenção de nomenclatura definitiva para métodos retornando IAsyncEnumerable?

10

Depois que o C # 5 introduziu o modelo asynce awaitpara a programação assíncrona, a comunidade C # chegou a uma convenção de nomenclatura para adicionar um sufixo "Async" aos métodos que retornavam um tipo aguardável, como este:

interface Foo
{
   Task BarAsync();
}

Muitos analisadores de código estático (baseados em Roslyn e não em Roslyn) foram escritos para depender dessa convenção de nomenclatura ao detectar o cheiro do código em torno da programação assíncrona.

Agora que o C # 8 introduziu o conceito de enumeráveis ​​assíncronos, que não são aguardáveis, mas podem ser usados ​​em conjunto com await foreach, parece haver duas opções para retornar os métodos de nomeação IAsyncEnumerable:

interface Foo
{
    // No "Async" suffix, indicating that the return value is not awaitable.
    IAsyncEnumerable<T> Bar<T>();
}

ou

interface Foo
{
    // With "Async" suffix, indicating that the overall logic is asynchronous.
    IAsyncEnumerable<T> BarAsync<T>();
}

Houve uma diretriz definitiva da convenção de nomenclatura (da equipe de linguagem C #, da .NET Foundation ou de outras autoridades) em relação às opções acima, como a convenção de nomenclatura C # 5 foi padronizada de maneira inequívoca e não deixada ao critério de opinião dos programadores?

Hwaien
fonte
2
Os sons opção 2 corrigir aqui, já que o seu executar este código de forma assíncrona (método de marca como asynce uso awaitdentro), e amostras MSDN mostra o mesmo
Pavel Anikhouski
11
Em resposta ao fechamento da pergunta, editei a pergunta para perguntar se existe uma convenção de nomenclatura definitiva e para dar um exemplo de como uma convenção de nomenclatura definitiva no C # 5 resultou em desenvolvimentos na análise de código estático. Portanto, fazer o C # 8 seguir esse padrão resultará em melhorias mensuráveis ​​na qualidade do código, além das preferências de codificação baseadas em opiniões.
hwaien
O próprio .NET Core usa o Asyncsufixo para métodos que retornam IAsyncEnumerable, por exemplo, ChannelReader.ReadAllAsync . Em outros casos, por exemplo, no EF Core, AsAsyncEnumerable()é usado o que já está claro
Panagiotis Kanavos
Existem diretrizes de nomenclatura para facilitar o entendimento do código por seres humanos. Se a finalidade do código não estiver clara, adicione o sufixo.
Panagiotis Kanavos

Respostas:

1

Não há orientação melhor do que o que as equipes do .NET fazem:

  • ChannelReader.ReadAllAsync retorna umIAsyncEnumerable<T>
  • No EF Core 3, os resultados são retornados como um IAsyncEnumerablechamando AsAsyncEnumerable ()
  • Em System.Linq.Async, ToAsyncEnumerable () converte IEnumerables, Tasks e Observables em IAsyncEnumerables
  • Todos os outros operadores System.Linq.Asyncmantêm seus nomes. Não há SelectAsyncou SelectAsAsyncEnumerableapenas Select.

Em todos os casos, fica claro qual é o resultado do método. Em todos os casos, é necessário aguardar os resultados do método await foreachantes de poderem ser utilizados.

Portanto, a diretriz real permanece a mesma - verifique se o nome deixa o comportamento claro :

  • Quando o nome já estiver claro, por exemplo, com AsAsyncEnumerable()ou ToAsyncEnumerable(), não há necessidade de adicionar nenhum sufixo.
  • Em outros casos, adicione o Asyncsufixo para que os desenvolvedores saibam que precisam await foreachdo resultado.

Os analisadores e geradores de código não se importam com os nomes dos métodos, eles detectam odores ao inspecionar o próprio código. Um analisador de código irá dizer-lhe que você se esqueceu de aguardar uma tarefa ou await foreachum IAsyncEnumerablenão importa como você chamar os métodos e as variáveis. Um gerador pode simplesmente usar a reflexão para verificar IAsyncEnumerablee emitirawait foreach

São os analisadores de estilo que verificam os nomes. O trabalho deles é garantir que o código use um estilo consistente para que os desenvolvedores possam entender o código. O analisador de estilos informará que um método não segue o estilo escolhido. Esse estilo pode ser o guia de estilo da equipe ou geralmente aceito.

E, claro, todo mundo sabe que o prefixo comum para os campos de instância privada é _:)

Panagiotis Kanavos
fonte
Obrigado por apontar o ChannelReader.ReadAllAsyncexemplo. Como isso vem diretamente da equipe do .NET, é difícil dar errado seguindo o exemplo.
hwaien
Os pacotes de analisadores geralmente vêm com uma mistura de analisadores de estilo e não-estilo. Por exemplo, o pacote Microsoft.VisualStudio.Threading.Analyzers possui um analisador de estilo que verifica a convenção de nomenclatura (VSTHRD200) e um analisador não-estilo que detecta situações perigosas que podem levar a um conflito (VSTHRD111). Para fazer referência ao pacote para aproveitar o VSTHRD111, um projeto também terminaria com VSTHRD200.
hwaien
2

Como não é um método assíncrono, o nome não deve terminar em 'Assíncrono'. Esse sufixo do método é uma convenção para tornar óbvio que o método deve ser aguardado ou o resultado tratado como uma tarefa.

Eu acho que um nome normal de retorno de coleção é apropriado. GetFoos () ou similar.

David Browne - Microsoft
fonte
Todos esses são argumentos para usar o Asyncsufixo. O sufixo é usado no próprio .NET Core: ChannelReader.ReadAllAsync retorna um IAsyncEnumerable. Um IAsyncEnumerabledeve ser usado com await foreach, portanto o sufixo faz sentido.
Panagiotis Kanavos
1

O sufixo assíncrono e até a palavra async- chave são apenas clichês, não têm sentido além de alguns argumentos de compatibilidade com versões anteriores. O C # possui todas as informações para distinguir essas funções, assim como se distingue yieldnos métodos que retornam IEnumerable, como você sabe que não precisa adicionar nada parecido enumerableou alguma outra palavra-chave a esses métodos.

Você precisa adicionar o sufixo Async apenas porque o C # se queixará de sobrecargas, portanto, essencialmente, esses dois são idênticos e fazem a mesma coisa com todas as regras do polimorfismo (se não levarmos em consideração o fator humano de comportamento intencionalmente corrompido):

public interface IMyContract
{
    Task<int> Add(int a, int b);
    int Add(int a, int b);
}

Mas você não pode escrever assim, porque o compilador irá reclamar. Mas ei! Engolirá essa monstruosidade:

public interface IMyContract : IEnumerable<int>, IEnumerable<long>
{
}

e até isso:

public interface IMyContractAsync
{
    Task<int> Add(int a, int b);
}

public interface IMyContract : IMyContractAsync
{
    int Add(int a, int b);
}

Então é isso. Você adiciona o Async apenas para parar o compilador para reclamar. Não para esclarecer as coisas. Não para torná-los melhores. Se não houver reclamação - não há razão para adicioná-lo, então, no seu caso, evitarei isso, a menos que se torne Task<IAsyncEnumerable>.

PS: Apenas para uma melhor compreensão da imagem inteira aqui - se em algum momento no futuro alguém adicionar extensões de método de computador quântico C # (fantazy, hein) que operam em paradigmas diferentes, provavelmente precisaremos de outro sufixo como Quant ou algo assim, porque C # reclamará o contrário. Será exatamente o mesmo método, mas funcionará de outra maneira. Assim como o polimorfismo. Assim como as interfaces fazem.

eocron
fonte
11
Desculpe, mas sinto que discordo, não é realmente uma abordagem diferente de fazer a mesma coisa - é mais - espera-se que seja chamado de forma diferente e que o resultado seja coletado de maneira diferente. Essa é a maior diferença entre assíncrono e não assíncrono. Eu acredito fortemente que adicionar o assíncrono é para maior clareza. Eu acho que essa também é uma recomendação da Microsoft.
Madoxdev 21/12/19
Sim, concordo, mas discordo. Assim como na lista, você tem a função de classificação simples e não "QuickSort", "RadixSort", "BubbleSort", "MixOfSorts". Elas são diferentes, usadas pelo escopo, mas obviamente não precisam de esclarecimentos, exceto para fins de depuração (quero dizer, você só se importa com elas quando causam impacto no desempenho / recursos). Nesse caso, o DoThing e o DoThingAsync estão na mesma situação que qualquer outro algoritmo de modelo, eles não precisam de esclarecimentos, são suportados ou não e são úteis apenas para depuração.
eocron
Isso é diferente, significa assíncrono - heu chamador certifique-se de lidar com isso corretamente. O fato de se aposentar a tarefa não é suficiente para dizer que o método é verdadeiramente assíncrono. O chamador deve saber aguardar ou usar GetAwaiter (). GetResilt () para obter o mesmo oitput. Isso é diferente, não como as coisas são feitas por dentro.
Madoxdev 21/12/19
Este é inteiramente o objetivo dos analisadores de código e compilador. O IntellySense pode facilmente apontar isso para você, não há necessidade de usar cérebros de desenvolvedores para destacar ou até banir o uso do método de sincronização sobre o método assíncrono no código assíncrono. Pela minha experiência ao usar o Async, raramente preciso usar GetResult (), mas frequentemente poluo toda a base de código com o Async.
eocron
Como alguém marcou isso - é opinião. Podemos manter a acreditar no que acreditamos e ser feliz :)
madoxdev