Чи є (items! = Null) зайвим перед foreach (T item in items)?


103

Я часто стикаюся з таким кодом:

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(як використання ??буде ), b) вимагають додавання методу розширення до кожного проекту, або c) вимагають уникати null IEnumerables (Pffft! Puh-LEAZE! SMH.) для початку (cuz, nullозначає N / A, тоді як порожній список означає, що це додаток, але є в даний час добре, порожньо !, тобто, Емпл. може мати комісії, які не відповідають продажам, або порожні для продажів, коли вони не заробляли жодних).
Том,

Відповіді:


115

Вам все одно потрібно перевірити, чи (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();
}

31
Симпатична ідея; порожній масив був би кращим, оскільки він споживає менше пам'яті і виробляє менше тиску в пам'яті. Численні. Порожній <string> був би ще кращим, оскільки він кешує порожній масив, який він генерує та повторно використовує.
Ерік Ліпперт

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

11
@CodeInChaos: Ви зазвичай вважаєте, що швидкість перерахування порожньої послідовності є вузьким місцем у вашій програмі?
Ерік Ліпперт

14
Це зменшує не тільки швидкість перерахування порожньої послідовності, але й повної послідовності. І якщо послідовність досить довга, це може мати значення. Для більшості кодів слід вибрати ідіоматичний код. Але два розподіли, про які ви згадали, будуть проблемою продуктивності в ще меншій кількості випадків.
CodesInChaos

15
@CodeInChaos: Ах, зараз я бачу вашу думку. Коли компілятор може виявити, що "foreach" повторюється над списком <T> або масивом, він може оптимізувати foreach для використання перелічувачів типу значень або фактично генерувати цикл "for". Примушений перераховувати або список, або порожню послідовність, він повинен повернутися до кодегена "найнижчого загального знаменника", який у деяких випадках може бути повільнішим і створювати більше тиску в пам'яті. Це тонкий, але відмінний момент. Звичайно, мораль цієї історії - як завжди - якщо у вас є проблеми з парфумом, то профіліруйте її, щоб дізнатися, що таке справжнє вузьке місце.
Ерік Ліпперт

68

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

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

Елегантна відповідь. Дякую!
Стів

2
Це найкраще рішення, оскільки воно не: а) передбачає погіршення продуктивності (навіть коли це не null) узагальнення всього циклу на РК Enumerable(як це ??було б), б) вимагає додавання методу розширення до кожного проекту, або c ) вимагають уникати null IEnumerables (Pffft! Puh-LEAZE! SMH.), щоб почати з (cuz, nullозначає N / A, тоді як порожній список означає, що це додаток, але в даний час, ну, порожній !, тобто Empl. може мати комісії, які Не застосовується для непродажних або порожній для розпродажів, коли вони не заробили)
Том

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

2
@Tom: Я настійно не рекомендую вам робити це в майбутньому. Уявіть, якщо всі, хто не погодився з вашим коментарем, додали свої коментарі до всіх ваших . (Уявіть, я написав тут свою відповідь, але 11 разів.) Це просто не продуктивне використання переповнення стека.
Джон Скіт,

1
Я також припускаю, що відбудеться хіт про ефективність, який викликає делегата порівняно зі стандартом foreach. Зокрема для списку, який, на мою думку, перетворюється на forцикл.
kjbartel

37

Справжній винос тут повинен бути послідовністю, майже ніколи не повинен бути нульовим в першу чергу . Просто зробіть його інваріантом у всіх ваших програмах: якщо у вас є послідовність, вона ніколи не буде нульовою. Він завжди ініціалізується як порожня послідовність або якась інша справжня послідовність.

Якщо послідовність ніколи не є нульовою, очевидно, її не потрібно перевіряти.


1
Як щодо того, якщо ви отримаєте послідовність від послуги WCF? Це може бути нулем, правда?
Nawaz

4
@Nawaz: Якщо я мав службу WCF, яка повертала мені нульові послідовності, маючи на увазі, що вони будуть порожніми послідовностями, то я би повідомив про це як про помилку. Це говорило: якщо вам доведеться мати погано сформований вихід з аргументовано баггі-сервісів, то так, вам доведеться зіткнутися з цим, перевіривши на нуль.
Ерік Ліпперт

7
Якщо, звичайно, нульові та порожні означають зовсім інші речі. Іноді це справедливо для послідовностей.
конфігуратор

@Nawaz Як щодо DataTable.Rows, що повертає null замість порожньої колекції. Може, це помилка?
Ніл Б,

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

10

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

І відповідь цілком логічна:

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


Гадаю, в цьому є плюси і мінуси, тому вони вирішили зберегти це так, як це було розроблено в першу чергу. зрештою, передбачення - це лише якийсь синтаксичний цукор. якби ви закликали items.GetEnumerator (), який також зазнав би аварійного завершення, якби елементи були нульовими, тому вам довелося тестувати це спочатку.
Маріус Бансіла

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

Тест необхідний, тому що якщо колекція нульова, 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.