Является ли if (items! = Null) лишним перед foreach (T item in items)?


105

Я часто сталкиваюсь с таким кодом:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

По сути, ifусловие гарантирует, что foreachблок будет выполняться, только если itemsон не равен нулю. Мне интересно, ifдействительно ли это условие необходимо, или foreachон справится с ситуацией, если items == null.

Я имею в виду, могу я просто написать

foreach(T item in items)
{
    //...
}

не беспокоясь о том items, нулевое значение или нет? Является ли ifусловие излишним? Или это зависит от типа от itemsили , может быть , на Tа?



1
@ ответ kjbartel (по крайней « stackoverflow.com/a/32134295/401246 » является лучшим решением, потому что он не делает: а) включать ухудшение характеристик (даже если они не null) , обобщающей весь цикл на ЖК - дисплее Enumerable(как использование ??будет ), б) требовать добавления метода расширения к каждому проекту, или в) требовать исключения null IEnumerables (Pffft! Puh-LEAZE! SMH.) в начале (cuz, nullозначает N / A, тогда как пустой список означает, что он применяется, но есть в настоящее время, ну, пусто !, т. е. Empl. может иметь комиссионные, которые отсутствуют для не-продаж, или пустые для продаж, если они не заработали).
Том

Ответы:


117

Вам все равно нужно проверить, если (items! = Null), иначе вы получите NullReferenceException. Однако вы можете сделать что-то вроде этого:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

но вы можете проверить его работоспособность. Поэтому я по-прежнему предпочитаю сначала иметь if (items! = Null).

Основываясь на предложении Эрика Липперта, я изменил код на:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}

33
Симпатичная идея; пустой массив был бы предпочтительнее, потому что он потребляет меньше памяти и создает меньшую нагрузку на память. Enumerable.Empty <string> был бы даже более предпочтительным, потому что он кэширует пустой массив, который он генерирует, и повторно использует его.
Эрик Липперт

5
Я ожидаю, что второй код будет медленнее. Он вырождает последовательность в, IEnumerable<T>который, в свою очередь, превращается в перечислитель в интерфейс, что замедляет итерацию. Мой тест показал 5-кратную деградацию для итерации по массиву int.
CodesInChaos

11
@CodeInChaos: Вы обычно обнаруживаете, что скорость перечисления пустой последовательности является узким местом производительности вашей программы?
Эрик Липперт

14
Это снижает скорость перечисления не только пустой последовательности, но и всей последовательности. И если последовательность достаточно длинная, это может иметь значение. Для большей части кода мы должны выбирать идиоматический код. Но два упомянутых вами распределения будут проблемой производительности в еще меньшем количестве случаев.
CodesInChaos

15
@CodeInChaos: А, теперь я понимаю вашу точку зрения. Когда компилятор может обнаружить, что «foreach» выполняет итерацию по List <T> или массиву, он может оптимизировать foreach для использования перечислителей типов значений или фактически сгенерировать цикл «for». Когда его заставляют перечислять либо список, либо пустую последовательность, он должен вернуться к кодогенерации с «наименьшим общим знаменателем», который в некоторых случаях может быть медленнее и вызывать большую нагрузку на память. Это тонкий, но отличный момент. Конечно, мораль этой истории - как всегда - если у вас есть проблема с производительностью, профилируйте ее, чтобы выяснить, в чем настоящее узкое место.
Эрик Липперт

70

Используя C # 6, вы можете использовать новый условный оператор null вместе с List<T>.ForEach(Action<T>)(или ваш собственный IEnumerable<T>.ForEachметод расширения).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});

Элегантный ответ. Спасибо!
Стив

2
Это лучшее решение, потому что оно не: а) влечет за собой снижение производительности (даже если нет null) обобщения всего цикла на ЖК-дисплее Enumerable(как при использовании ??); б) требует добавления метода расширения в каждый проект; в) ) требуется избегать null IEnumerables (Pffft! Puh-LEAZE! SMH.), чтобы начать с (cuz, nullозначает N / A, тогда как пустой список означает, что он применяется, но в настоящее время, ну, пуст !, то есть Empl. может иметь Комиссии, которые N / A для не-продаж или пусто для продаж, если они не заработали).
Том

6
@Tom: Предполагается, что itemsэто List<T>хотя, а не просто так IEnumerable<T>. (Или у вас есть собственный метод расширения, о котором вы сказали, что не хотите, чтобы он был ...) Кроме того, я бы сказал, что действительно не стоит добавлять 11 комментариев, все в основном говорят, что вам нравится конкретный ответ.
Джон Скит,

2
@Tom: Я бы сильно отговаривал вас от этого в будущем. Представьте, что все, кто не согласен с вашим комментарием, добавили бы свои комментарии ко всем вашим . (Представьте, что я написал здесь свой ответ, но 11 раз.) Это просто непродуктивное использование Stack Overflow.
Джон Скит

1
Я также предполагаю, что вызов делегата по сравнению со стандартом снизится foreach. В частности, для списка, который, как мне кажется, преобразуется в forцикл.
kjbartel

37

Настоящий вывод здесь должен заключаться в том, что последовательность почти никогда не должна быть нулевой . Просто сделайте неизменным во всех ваших программах, что если у вас есть последовательность, она никогда не будет нулевой. Он всегда инициализируется пустой последовательностью или какой-либо другой подлинной последовательностью.

Если последовательность никогда не равна нулю, то, очевидно, вам не нужно ее проверять.


2
Как насчет того, чтобы получить последовательность из службы WCF? Это может быть null, правда?
Nawaz

4
@Nawaz: Если бы у меня была служба WCF, которая возвращала мне нулевые последовательности, предполагающие, что они будут пустыми последовательностями, я бы сообщил им об этом как об ошибке. Тем не менее: если вам приходится иметь дело с плохо сформированным выводом возможно ошибочных сервисов, тогда да, вы должны справиться с этим, проверяя на null.
Эрик Липперт

7
Если, конечно, null и empty означают совершенно разные вещи. Иногда это справедливо для последовательностей.
конфигуратор

@Nawaz Как насчет DataTable.Rows, который возвращает ноль вместо пустой коллекции. Может это ошибка?
Neil B

@ ответ kjbartel (по крайней « stackoverflow.com/a/32134295/401246 » является лучшим решением, потому что он не делает: а) включать ухудшение характеристик (даже если они не null) , обобщающей весь цикл на ЖК - дисплее Enumerable(как использование ??будет ), б) требовать добавления метода расширения к каждому проекту, или в) требовать исключения null IEnumerables (Pffft! Puh-LEAZE! SMH.) в начале (cuz, nullозначает N / A, тогда как пустой список означает, что он применяется, но есть в настоящее время, ну, пусто !, то есть Empl. может иметь Комиссионные, которые N / A для non-Sales или пустые для Sales, когда они не заработали).
Том

10

На самом деле для этого @Connect есть запрос функции: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

И ответ вполне логичный:

Я думаю, что большинство циклов foreach написано с целью повторения ненулевой коллекции. Если вы попытаетесь выполнить итерацию через null, вы должны получить исключение, чтобы вы могли исправить свой код.


Я предполагаю, что у этого есть плюсы и минусы, поэтому они решили сохранить его в том виде, в котором он был изначально разработан. в конце концов, foreach - это всего лишь синтаксический сахар. если бы вы вызвали items.GetEnumerator (), который также вылетел бы, если бы items был нулевым, поэтому вам нужно было сначала проверить это.
Marius Bancila

6

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

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

Если выражение имеет значение null, создается исключение System.NullReferenceException.


2

Это не лишнее. Во время выполнения элементы будут преобразованы в IEnumerable, и будет вызван его метод GetEnumerator. Это приведет к разыменованию элементов, которые не работают


1
1) Последовательность не обязательно будет преобразована IEnumerableи 2) Это дизайнерское решение сделать ее бросанием. C # мог легко вставить эту nullпроверку, если разработчики сочли это хорошей идеей.
CodesInChaos

2

Вы можете инкапсулировать нулевую проверку в методе расширения и использовать лямбда:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

Код становится:

items.ForEach(item => { 
  ...
});

Если может быть еще более кратким, если вы хотите просто вызвать метод, который принимает элемент и возвращает void:

items.ForEach(MethodThatTakesAnItem);

1

Вам это нужно. Вы получите исключение при foreachдоступе к контейнеру, чтобы в противном случае настроить итерацию.

Под обложками foreachиспользует интерфейс, реализованный в классе коллекции, для выполнения итерации. Общий эквивалентный интерфейс находится здесь .

Оператор foreach языка C # (для каждого в Visual Basic) скрывает сложность перечислителей. Поэтому рекомендуется использовать foreach вместо непосредственного управления перечислителем.


1
Так же, как примечание, он технически не использует интерфейс, он использует утиную печать : blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx интерфейсы гарантируют наличие правильных методов и свойств тем не менее, и помогает понять намерения. а также использовать вне foreach ...
ShuggyCoUk

0

Тест необходим, потому что, если коллекция имеет значение NULL, foreach вызовет исключение NullReferenceException. На самом деле попробовать это довольно просто.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}


0

Как упоминалось здесь, вам нужно проверить, не является ли он нулевым.

Не используйте выражение, значение которого равно нулю.


0

В C # 6 вы можете написать sth вот так:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Это в основном решение Влада Бездена, но с использованием ?? выражение, чтобы всегда генерировать массив, который не является нулем и, следовательно, выживает после foreach, а не имеет эту проверку внутри скобки foreach.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.