Czy istnieje ostateczna konwencja nazewnictwa dla metod zwracających IAsyncEnumerable?

10

Po tym, jak C # 5 wprowadził model asynci awaitprogramowanie asynchroniczne, społeczność C # doszła do konwencji nazewnictwa, aby dodać sufiks „Async” do metod zwracających oczekiwany typ, taki jak ten:

interface Foo
{
   Task BarAsync();
}

Od tego czasu napisano wiele analizatorów kodu statycznego (zarówno opartych na Roslyn, jak i innych), aby polegały na tej konwencji nazewnictwa podczas wykrywania zapachu kodu wokół programowania asynchronicznego.

Teraz, gdy C # 8 wprowadził koncepcję asynchronicznych wyliczeń, które same w sobie nie są oczekiwane, ale mogą być używane w połączeniu z await foreach, wydaje się, że istnieją dwie opcje zwracania metod nazewnictwa IAsyncEnumerable:

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

lub

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

Czy istnieje ostateczna wytyczna dotycząca konwencji nazewnictwa (od zespołu językowego C #, .NET Foundation lub innych organów) dotycząca powyższych opcji, na przykład w jaki sposób konwencja nazewnictwa C # 5 została jednoznacznie ujednolicona i nie została pozostawiona do oceny programistów na podstawie opinii?

Hwaien
źródło
2
Opcja 2 brzmi tutaj poprawnie, ponieważ uruchamiasz ten kod asynchronicznie (oznacz metodę jako asynci użyj awaitwewnątrz), a próbka msdn pokazuje to samo
Pavel Anikhouski
1
W odpowiedzi na zamknięcie pytania zredagowałem pytanie, aby zapytać, czy istnieje ostateczna konwencja nazewnictwa, i podać przykład, w jaki sposób ostateczna konwencja nazewnictwa w C # 5 spowodowała rozwój statycznej analizy kodu. Dlatego posiadanie C # 8 zgodnie z tym wzorcem spowoduje mierzalną poprawę jakości kodu wykraczającą poza preferencje kodowania oparte na opiniach.
hwaien
Sam .NET Core używa Asyncsufiksu dla metod, które zwracają IAsyncEnumerable, np . ChannelReader.ReadAllAsync . W innych przypadkach, np. W EF Core, AsAsyncEnumerable()który jest już jasny
Panagiotis Kanavos,
Istnieją wytyczne dotyczące nazewnictwa, aby ułatwić ludziom zrozumienie kodu. Jeśli cel kodu nie jest jasny, dodaj przyrostek.
Panagiotis Kanavos,

Odpowiedzi:

1

Nie ma lepszej wytycznej niż to, co już robią zespoły .NET :

  • ChannelReader.ReadAllAsync zwraca anIAsyncEnumerable<T>
  • W EF Core 3 wyniki są zwracane jako IAsyncEnumerablepoprzez wywołanie AsAsyncEnumerable ()
  • W System.Linq.Async, ToAsyncEnumerable () konwertuje IEnumerables, Tasks and Observables na IAsyncEnumerables
  • Wszyscy pozostali operatorzy System.Linq.Asynczachowują swoje nazwy. Nie ma SelectAsynclub SelectAsAsyncEnumerablepo prostu Select.

We wszystkich przypadkach jasne jest, jaki jest wynik metody. We wszystkich przypadkach await foreachprzed użyciem można oczekiwać wyników tej metody .

Tak więc prawdziwa wytyczna pozostaje taka sama - upewnij się, że nazwa wyjaśnia zachowanie :

  • Gdy nazwa jest już jasna, np. Za pomocą AsAsyncEnumerable()lub ToAsyncEnumerable(), nie trzeba dodawać żadnego przyrostka.
  • W innych przypadkach dodaj Asyncprzyrostek, aby programiści wiedzieli, że potrzebują await foreachwyniku.

Analizatory kodu i generatory tak naprawdę nie dbają o nazwy metod, wykrywają zapachy, sprawdzając sam kod. Analizator kod powie Ci, że zapomniał czekają zadania lub nieważne jak nazwać metody i zmienne. Generator może po prostu użyć odbicia do sprawdzenia i emisjiawait foreachIAsyncEnumerableIAsyncEnumerableawait foreach

To styl analizatorów, które sprawdzają nazwiska. Ich zadaniem jest zapewnienie spójnego stylu kodu, aby programiści mogli go zrozumieć. Analizator stylów powie ci, że metoda nie jest zgodna z wybranym stylem. Ten styl może być przewodnikiem zespołu lub powszechnie akceptowanym stylem.

I oczywiście wszyscy wiedzą, że wspólny przedrostek dla pól prywatnych wystąpień to _:)

Panagiotis Kanavos
źródło
Dziękujemy za wskazanie ChannelReader.ReadAllAsyncprzykładu. Ponieważ pochodzi to prosto od zespołu .NET, ciężko jest pójść nie tak.
hwaien
Pakiety analizatorów często dostarczane są z mieszanym zestawem analizatorów stylowych i nie-stylowych. Na przykład pakiet Microsoft.VisualStudio.Threading.Analyzers ma analizator stylu, który sprawdza konwencję nazewnictwa (VSTHRD200), oraz analizator inny niż styl, który wykrywa niebezpieczne sytuacje, które mogą potencjalnie doprowadzić do impasu (VSTHRD111). Aby odwołać się do pakietu, aby skorzystać z VSTHRD111, projekt zakończyłby się również VSTHRD200.
hwaien
2

To nie jest metoda asynchroniczna, więc nazwa nie powinna kończyć się na „Async”. Ten przyrostek metody jest konwencją mającą na celu wyjaśnienie, że należy oczekiwać metody lub że wynik powinien być traktowany jako zadanie.

Myślę, że normalna nazwa zwracająca kolekcję jest odpowiednia. GetFoos () lub podobny.

David Browne - Microsoft
źródło
Wszystkie te argumenty przemawiają za użyciem Asyncsufiksu. Sufiks jest używany w samym .NET Core: ChannelReader.ReadAllAsync zwraca IAsyncEnumerable. IAsyncEnumerableMusi być stosowany z await foreach, więc przyrostek sens.
Panagiotis Kanavos
1

Sufiks asynchroniczny, a nawet słowo kluczowe, asyncjest po prostu bojlerem, nie ma znaczenia poza niektórymi argumentami wstecznej kompatybilności. C # ma wszystkie informacje do odróżnienia tych funkcji, tak jak odróżnia się yieldw metodach, które zwracają IEnumerable, ponieważ wiesz, że nie musisz dodawać niczego takiego enumerableani żadnego innego słowa kluczowego do takich metod.

Musisz dodać sufiks Async tylko dlatego, że C # będzie narzekać na przeciążenia, więc zasadniczo te dwa są identyczne i robią to samo według wszystkich reguł polimorfizmu (jeśli nie bierzemy pod uwagę czynnika umyślnego korupcji):

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

Ale nie można tego tak napisać, ponieważ kompilator będzie narzekał. Ale hej! Połknie tę potworność:

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

a nawet to:

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

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

Więc to jest to. Dodasz ASYNC tylko do zatrzymania kompilatora narzekać. Nie wyjaśniać rzeczy. Nie po to, żeby były lepsze. Jeśli nie ma na co narzekać - nie ma powodu, aby go dodawać, więc w twoim przypadku uniknę tego, chyba że stanie się Task<IAsyncEnumerable>.

PS: Dla lepszego zrozumienia całego obrazu tutaj - jeśli kiedyś w przyszłości ktoś doda rozszerzenia metody kwantowo-komputerowej C # (fantazy, huh), które będą działały w innym paradygmacie, prawdopodobnie będziemy potrzebować innego sufiksu jak Quant lub coś takiego, ponieważ C # będzie narzekać inaczej. Będzie to dokładnie ta sama metoda, ale zadziała to w inny sposób. Podobnie jak polimorfizm. Podobnie jak interfejsy.

eocron
źródło
1
Przykro mi, ale czuję, że się nie zgadzam, nie jest to tak naprawdę odmienne podejście do robienia tego samego - to więcej - oczekuje się, że będzie się nazywać inaczej, a wynik będzie zbierany inaczej. To jest największa różnica między asynchronizacją a niesynchronizacją. Mocno wierzę, że dodanie asynchronu służy przejrzystości. Myślę, że to także zalecenie Microsoftu.
madoxdev
Tak, zgadzam się, ale się nie zgadzam. Podobnie jak na liście, masz prostą funkcję sortowania, a nie „QuickSort”, „RadixSort”, „BubbleSort”, „MixOfSorts”. Są różne, używane przez zakres, ale oczywiście nie wymagają wyjaśnień innych niż do celów debugowania (mam na myśli, że dbasz o nie tylko wtedy, gdy wpływają na wydajność / zasoby). W takim przypadku DoThing i DoThingAsync znajduje się w takiej samej sytuacji, jak każdy inny algorytm szablonowy, nie wymagają wyjaśnienia, jest obsługiwany lub nie i jest użyteczny tylko do debugowania.
eocron,
To inne, asynchroniczne oznacza - heu dzwoniący upewnij się, że sobie z tym poradzisz. Fakt wycofania Zadanie nie wystarczy, aby powiedzieć, że metoda jest naprawdę asynchroniczna. Dzwoniący musi wiedzieć, aby oczekiwać lub skorzystać z GetAwaiter (). GetResilt (), aby uzyskać ten sam wynik. Różni się to nie tak, jak rzeczy są wykonywane w środku.
madoxdev
Jest to całkowicie cel analizatorów kodu i kompilatora. IntellySense może łatwo to dla Ciebie wskazać, nie musisz używać mózgów programistów do wyróżniania, a nawet banowania przy użyciu metody synchronizacji zamiast asynchronicznej w kodzie asynchronicznym. Z mojego doświadczenia w korzystaniu z Async rzadko korzystam z GetResult (), ale często muszę zanieczyszczać całą bazę kodu za pomocą Async.
eocron,
Jak ktoś to zaznaczył - to opinia. Możemy nadal wierzyć w to, w co wierzymy i być szczęśliwym :)
madoxdev