Enumerable.Any () и возможные множественные перечисления - PullRequest
1 голос
/ 09 мая 2020

Rider / Resharper дает мне возможное предупреждение о множественном перечислении по этому поводу:

public void ProcessProductCodes(IEnumerable<string> productCodes) {
    if (productCodes.Any()) {
        DoStuff(productCodes);
    }
}

Это ложное срабатывание, или функция Any () действительно испортила перечисление коллекции?

Ответы [ 2 ]

3 голосов
/ 09 мая 2020

Вы можете создать вспомогательный метод (я создал его как метод расширения), чтобы гарантировать, что он повторяется только один раз:

public static class LinqExtensions
{
    public static bool DoIfAny<T>(this IEnumerable<T> collection, Action<IEnumerable<T>> action)
    {
        var enumerator = collection.GetEnumerator();
        if (!enumerator.MoveNext())
        {
            return false;
        }

        action(CreateEnumerableFromStartedEnumerable(enumerator));
        return true;
    }

    private static IEnumerable<T> CreateEnumerableFromStartedEnumerable<T>(IEnumerator<T> enumerator)
    {
        do
        {
            yield return enumerator.Current;
        }
        while (enumerator.MoveNext());
    }
}

По сути, это создаст перечислитель для коллекции, а затем попытается перейти к первому элементу. Если это не удается, метод не вызывается, а возвращается false.

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

Использование:

IEnumerable<string> values = new string[] { "a", "b", "c" };
bool delegateCalled = values.DoIfAny(e => DoStuff(e));
Console.WriteLine("Delegate called: " + delegateCalled.ToString());

Попробуйте онлайн

Примечание что это действительно будет работать, только если вы хотите .Any() в смысле «коллекция не пуста». Если он проверяет наличие определенного элемента c, тогда вам нужно сначала материализовать список, как в ответе Тао.

3 голосов
/ 09 мая 2020

Интерфейс IEnumerable представляет последовательность элементов, которые можно повторять, но не делает никаких предположений о происхождении последовательности. Например, это может быть запрос к базе данных. Если бы это было так, здесь вы бы сделали 2 вызова базы данных: один для проверки наличия каких-либо элементов в последовательности, а другой - для передачи их функции DoStuff, что, очевидно, не является оптимальным с точки зрения производительности, и Resharper предупреждает вас об этом.

Чтобы избежать этой проблемы, у вас есть 2 разных варианта. Если набор элементов уже находится в памяти, вы можете сделать это явным, изменив подпись вашей функции на:

public void ProcessProductCodes(ICollection<string> productCodes) { ... }

Если вы не можете этого гарантировать, вы можете сделать .ToList() или .ToArray в начале вашей функции:

public void ProcessProductCodes(IEnumerable<string> productCodes) {
  var productCodesList = productCodes.ToList();
  if (productCodesList .Any()) {
      DoStuff(productCodesList );
  }

Resharper сделает это за вас, просто выберите быстрый рефакторинг (обычно с Alt+Enter).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...