Существует ли определенное соглашение об именах для методов, возвращающих IAsyncEnumerable?

10

После C # 5 введены asyncиawait модель для асинхронного программирования, сообщество C # пришло к соглашению об именах, чтобы добавить суффикс "Async" к методам, возвращающим ожидаемый тип, например так:

interface Foo
{
   Task BarAsync();
}

С тех пор было написано, что многие статические анализаторы кода (как на основе Roslyn, так и на основе Roslyn) зависят от этого соглашения об именах при обнаружении запаха кода вокруг асинхронного программирования.

Теперь, когда в C # 8 введена концепция асинхронных перечислимых элементов, которые сами по себе не являются ожидаемыми, но могут использоваться вместе с ними await foreach, кажется, есть два варианта возврата методов именования IAsyncEnumerable:

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

или

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

Существовало ли определенное руководство по соглашению об именах (от группы по языку C #, .NET Foundation или других авторитетов) в отношении описанных выше вариантов, например, как соглашение по именованию C # 5 было однозначно стандартизировано и не оставлено на усмотрение программистов?

hwaien
источник
2
Вариант 2 здесь звучит правильно, так как вы запускаете этот код асинхронно (пометьте метод как asyncи используйте awaitвнутри), и пример
msdn
1
В ответ на закрытие вопроса я отредактировал вопрос, чтобы спросить, существует ли соглашение об окончательном именовании, и привести пример того, как соглашение об окончательном именовании в C # 5 привело к развитию статического анализа кода. Следовательно, следование этому шаблону C # 8 приведет к измеримым улучшениям качества кода по сравнению с предпочтениями кодирования на основе мнения.
Хваин
Само .NET Core использует Asyncсуффикс для методов, которые возвращают IAsyncEnumerable, например, ChannelReader.ReadAllAsync . В других случаях, например в EF Core, AsAsyncEnumerable()используется уже ясно
Panagiotis Kanavos
Правила именования существуют, чтобы людям было легче понять код. Если цель кода не ясна, добавьте суффикс.
Панайотис Канавос

Ответы:

1

Нет лучшего руководства, чем то, что уже делают команды .NET :

  • ChannelReader.ReadAllAsync возвращаетIAsyncEnumerable<T>
  • В EF Core 3 результаты возвращаются в виде IAsyncEnumerableвызова AsAsyncEnumerable ()
  • В System.Linq.Async ToAsyncEnumerable () преобразует IEnumerables, Tasks и Observables в IAsyncEnumerables
  • Все остальные операторы System.Linq.Asyncсохраняют свои имена. Там нет SelectAsyncили SelectAsAsyncEnumerableпросто Select.

Во всех случаях ясно, каков результат метода. Во всех случаях результаты метода следует ожидать сawait foreach прежде чем их можно будет использовать.

Таким образом, реальная рекомендация остается прежней - убедитесь, что имя проясняет поведение :

  • Когда имя уже ясно, например, с помощью AsAsyncEnumerable()илиToAsyncEnumerable() , нет необходимости добавлять суффикс.
  • В других случаях добавьте Asyncсуффикс, чтобы разработчики знали, что им нужен await foreachрезультат.

Анализаторы и генераторы кода на самом деле не заботятся об именах методов, они обнаруживают запахи, проверяя сам код. Анализатор кода покажет вам , что вы забыли не ждать Задачу или независимо от того , как вы называете методы и переменные. Генератор может просто использовать отражение, чтобы проверить и испуститьawait foreachIAsyncEnumerableIAsyncEnumerableawait foreach

Это анализаторы стиля, которые проверяют имена. Их работа заключается в обеспечении того, чтобы код использовал согласованный стиль, чтобы разработчики могли понимать код. Анализатор стиля скажет вам, что метод не соответствует выбранному вами стилю. Этот стиль может быть командным или общепринятым руководством по стилю.

И, конечно же, все знают, что общий префикс для полей частного экземпляра есть _:)

Панагиотис Канавос
источник
Спасибо за указание на ChannelReader.ReadAllAsyncпример. Поскольку это исходит от команды .NET, трудно пойти не так, как следует.
Хвайен
Пакеты анализатора часто поставляются с комбинированным анализатором стиля и нестандартных анализаторов. Например, пакет Microsoft.VisualStudio.Threading.Analyzers имеет анализатор стилей, который проверяет соглашение об именах (VSTHRD200), и анализатор не стилей, который обнаруживает опасные ситуации, которые потенциально могут привести к тупику (VSTHRD111). Чтобы сослаться на пакет, чтобы воспользоваться преимуществами VSTHRD111, проект также закончится VSTHRD200.
Хвайен
2

Это не асинхронный метод, поэтому имя не должно заканчиваться на «асинхронный». Этот суффикс метода является соглашением, позволяющим сделать очевидным, что метод следует ожидать или результат обрабатывается как задача.

Я думаю, что нормальное возвращающее коллекцию имя уместно. GetFoos () или аналогичный.

Дэвид Браун - Microsoft
источник
Все это аргументы в пользу использования Asyncсуффикса. Суффикс используется в самом .NET Ядро: ChannelReader.ReadAllAsync возвращает IAsyncEnumerable. IAsyncEnumerableДолжен быть использован с await foreach, поэтому суффикс имеет смысл.
Panagiotis Kanavos
1

Асинхронный суффикс и даже ключевое слово async- это просто шаблон, он не имеет смысла, кроме некоторых аргументов обратной совместимости. C # имеет всю информацию, чтобы различать эти функции так же, как он различает yieldметоды, которые возвращают IEnumerable, поскольку вы знаете, что вам не нужно добавлять что-то вродеenumerable или какое-либо другое ключевое слово в такие методы.

Вам нужно добавить суффикс Async только потому, что C # будет жаловаться на перегрузки, поэтому, по сути, эти два идентичных и они делают одно и то же по всем правилам полиморфизма (если мы не принимаем во внимание человеческий фактор намеренно искажающего поведения):

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

Но вы не можете написать это так, потому что компилятор будет жаловаться. Но эй! Это поглотит это чудовище:

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

и даже это:

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

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

Вот и все. Вы добавляете Async, чтобы компилятор не жаловался. Не уточнять вещи. Чтобы не сделать их лучше. Если нет жалоб - нет причин добавлять их, поэтому в вашем случае я этого избегу, если только это не станет Task<IAsyncEnumerable>.

PS: Просто для лучшего понимания всей картины здесь - если когда-нибудь в будущем кто-то добавит в C # расширения квантово-компьютерного метода (fantazy, да), которые будут работать в другой парадигме, нам, вероятно, понадобится другой суффикс, такой как Quant или что-то еще, потому что C # будет жаловаться иначе. Это будет точно такой же метод, но он будет работать по-другому. Так же, как и полиморфизм. Так же, как и интерфейсы.

eocron
источник
1
Извините, но я чувствую, что я не согласен, это не совсем другой подход к выполнению одного и того же - это больше, ожидается, что он будет называться по-разному, а результат будет собираться по-разному. Это самая большая разница между асинхронным и не асинхронным. Я твердо верю, что добавление асинхронности для ясности. Я думаю, что это также рекомендация Microsoft.
madoxdev
Да, я согласен, но не согласен. Как и в List, у вас есть простая функция сортировки, а не «QuickSort», «RadixSort», «BubbleSort», «MixOfSorts». Они отличаются, используются областью действия, но, очевидно, не нуждаются в разъяснениях, кроме как для целей отладки (я имею в виду, что вы заботитесь о них, только когда они влияют на производительность / ресурсы). В этом случае DoThing и DoThingAsync находятся в той же ситуации, что и любой другой шаблонный алгоритм, они не нуждаются в разъяснении, он либо поддерживается, либо нет, и используется только для отладки.
eocron
Это совсем другое, асинхронный означает, что вызывающий абонент должен убедиться, что вы справляетесь с этим корректно. Факта отмены Task недостаточно, чтобы сказать, что метод действительно Async. Вызывающий должен знать, что нужно ожидать или использовать GetAwaiter (). GetResilt (), чтобы получить тот же результат. Это не то, как все делается внутри.
madoxdev
Это целиком предназначено для анализаторов кода и компилятора. IntellySense может легко указать вам на это, не нужно использовать мозги разработчиков для выделения или даже запрета использования метода синхронизации над асинхронным в асинхронном коде. Исходя из моего опыта использования Async, мне крайне необходимо использовать GetResult (), но мне часто приходится загрязнять всю кодовую базу с помощью Async.
eocron
Как кто-то это отметил - это мнение. Мы можем продолжать верить в то, во что верим, и быть счастливыми :)
madoxdev