Обработка предупреждение для возможного многократного перечисления интерфейс IEnumerable



в моем коде нужно использовать IEnumerable<> несколько раз таким образом получить ошибку Resharper "возможно многократное перечисление IEnumerable".



пример кода:



public List<object> Foo(IEnumerable<object> objects)
{
if (objects == null || !objects.Any())
throw new ArgumentException();

var firstObject = objects.First();
var list = DoSomeThing(firstObject);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);

return list;
}



  • изменить objects параметр должен быть List а затем избежать возможного множественного перечисления, но тогда я не получаю самый высокий объект, который я могу обрабатывать.

  • еще одна вещь, которую я могу сделать, это преобразовать IEnumerable до List в начале метода:




 public List<object> Foo(IEnumerable<object> objects)
{
var objectList = objects.ToList();
// ...
}


но это просто неудобно.



что бы вы сделали в этом случае?

852   6  

6 ответов:

проблема с приемом IEnumerable в качестве параметра является то, что он говорит абонентам "я хочу перечислить это". Он не говорит им, сколько раз вы хотите перечислить.

Я могу изменить параметр objects на List, а затем избежать возможного множественного перечисления, но тогда я не получаю самый высокий объект, который я могу справиться с.

изменив сигнатуру метода, чтобы IList/ICollection, вы, по крайней мере, сделать это яснее для абонента, каковы ваши ожидания, и они могут избежать дорогостоящих ошибок.

в противном случае большинство разработчиков, рассматривающих метод, могут предположить, что вы повторяете только один раз. Если принимать IEnumerable - Это так важно, вы должны рассмотреть возможность сделать то .ToList() в начале метода.

жаль, что .NET не имеет интерфейса, который является IEnumerable + Count + Indexer, без добавления/удаления и т. д. методы, которые, как я подозреваю, решат эту проблему.

Если ваши данные всегда будут повторяемые, возможно, не беспокойтесь об этом. Однако вы также можете развернуть его - это особенно полезно, если входящие данные могут быть большими (например, чтение с диска / сети):

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

Примечание я немного изменил семантику DoSomethingElse, но это в основном, чтобы показать развернутое использование. Например, вы можете повторно обернуть итератор. Вы также можете сделать его блоком итератора, что может быть приятно; тогда нет list - и вы бы yield return элементы, как вы их получите, а не добавить в список, который будет возвращен.

если цель действительно состоит в том, чтобы предотвратить несколько перечислений, чем ответ Марка Гравелла-это тот, который нужно прочитать, но сохраняя ту же семантику, вы можете просто удалить избыточное Any и First звонит и идет с:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

обратите внимание, что это предполагает, что вы IEnumerable не является универсальным или, по крайней мере, ограничен ссылочным типом.

Я обычно перегружаю свой метод с IEnumerable и IList в этой ситуации.

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

Я позаботился объяснить в кратких комментариях методов, которые вызов IEnumerable будет выполнять .Список().

программист может выбрать .ToList () на более высоком уровне, если несколько операций объединяются, а затем вызывают перегрузку IList или позволяют моей перегрузке IEnumerable позаботиться об этом.

используя IReadOnlyCollection<T> или IReadOnlyList<T> в сигнатуре метода вместо IEnumerable<T>, имеет преимущество сделать явным, что вам может потребоваться проверить счетчик перед итерацией или повторить несколько раз по какой-то другой причине.

у них есть огромный недостаток, что может вызвать проблемы при попытке выполнить рефакторинг кода, чтобы использовать интерфейсы, например, чтобы сделать его более проверяемым и дружественные динамическое проксирование. Ключевым моментом является то, что IList<T> не наследует IReadOnlyList<T> и аналогично для других коллекций и их соответствующие интерфейсы только для чтения. (Короче говоря, это связано с тем, что .NET 4.5 хотел сохранить совместимость ABI с более ранними версиями. но они даже не воспользовались возможностью изменить это в .NET core.)

это означает, что если вы получаете IList<T> из какой-то части программы и хотите передать его в другую часть, что ожидает IReadOnlyList<T>, ты не можешь! однако вы можете передать IList<T> как IEnumerable<T>.

в итоге IEnumerable<T> - это единственный интерфейс только для чтения, поддерживаемый всеми коллекциями .NET, включая все интерфейсы коллекций. Любая другая альтернатива вернется, чтобы укусить вас, когда вы поймете, что вы заблокировали себя от некоторых вариантов архитектуры. Поэтому я думаю, что это правильный тип для использования в сигнатурах функций, чтобы выразить, что вам просто нужна коллекция только для чтения.

(обратите внимание, что вы всегда можете написать IReadOnlyList<T> ToReadOnly<T>(this IList<T> list) метод расширения, что простой скинется, если базовый тип поддерживает оба интерфейса, но вы должны добавить его вручную везде при рефакторинге, где а IEnumerable<T> всегда совместимы.)

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

во-первых, это предупреждение не всегда так много значат. Я обычно отключал его, убедившись, что это не горлышко бутылки с производительностью. Это просто означает IEnumerable оценивается дважды, что обычно не является проблемой, если evaluation само по себе занимает много времени. Даже если это займет много времени, в этом случае вы используете только один элемент в первый раз.

в этом сценарии вы также можете использовать мощные методы расширения linq еще больше.

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

это можно только оценить IEnumerable один раз в этом случае с некоторыми хлопотами, но сначала профиль и посмотреть, если это действительно проблема.

Comments

    Ничего не найдено.