Перевірте наявність нуля в циклі foreach


97

Чи є більш приємний спосіб зробити наступне:
мені потрібна перевірка на наявність null у файлі. Headers перед тим, як продовжити цикл

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

Коротше кажучи, виглядає трохи потворно писати foreach всередині if, якщо через рівень відступу, що відбувається в моєму коді.

Це те, що можна оцінити

foreach(var h in (file.Headers != null))
{
  //do stuff
}

можливо?


3
Ви можете подивитися тут: stackoverflow.com/questions/6937407 / ...
Адріан Fâciu

stackoverflow.com/questions/872323/… - це ще одна ідея.
weismat

1
@AdrianFaciu Я думаю, це зовсім інше. Питання перевіряє, чи збірка спочатку є нульовою, перш ніж робити для кожного. Ваше посилання перевіряє, чи є елемент у колекції нульовим.
rikitikitik


1
C # 8 міг просто мати нульовий умовний foreach якогось роду, тобто такий синтаксис: foreach? (var i in collection) {} Я думаю, що це досить поширений сценарій, щоб виправдати це, і враховуючи нещодавні нульові умовні доповнення до мови, яка тут має сенс?
mms

Відповіді:


124

Просто як невелике косметичне доповнення до пропозиції Рун, ви можете створити власний метод розширення:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Тоді ви можете написати:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Змінюйте назву за смаком :)


80

Якщо припустити, що тип елементів у файлі. Headers - T, ви можете це зробити

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

це створить порожній перелік T, якщо file.Headers має значення null. Якщо тип файлу є типом, яким ви володієте, я, однак, міркував би про те, щоб замінити Headersзамість нього гетер . nullце значення невідомого, тому, якщо можливо, замість використання null як "я знаю, що немає елементів", коли null насправді (/ спочатку) слід інтерпретувати як "я не знаю, чи є якісь елементи", використовуйте порожній набір для показу що ви знаєте, що в наборі немає елементів. Це також буде DRY'er, оскільки вам не доведеться робити нульову перевірку так часто.

РЕДАКТУЙТЕ як продовження пропозиції Джонса, ви також можете створити метод розширення, змінивши наведений вище код на

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

У випадку, коли ви не можете змінити геттер, це було б моїм власним уподобанням, оскільки воно чіткіше виражає намір, даючи операції ім'я (OrEmptyIfNull)

Згаданий вище метод розширення може зробити неможливим виявлення певних оптимізацій. Зокрема, ті, які пов'язані з IList, використовуючи метод перевантаження, це можна усунути

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}

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

@ Том, крім одного нульової перевірки, немає штрафу за випадки, коли перерахувач не є нульовим. Уникнути цієї перевірки, одночасно переконавшись, що перелічене не є нульовим, неможливо. Наведений вище код вимагає заголовків до an, IEnumerable який є більш обмежувальним, ніж foreachвимоги, але менш обмежувальним, ніж вимога List<T>у відповіді, на яку ви посилаєтесь. Які мають однакову покарання за продуктивність, перевіряючи, чи нумерація є нульовою.
Rune FS

Я базував питання "LCD" на коментарі Еріка Ліпперта до відповіді Влада Бездена в тій самій темі, що і відповідь kjbartel: "@CodeInChaos: Ах, я бачу вашу думку зараз. Коли компілятор може виявити, що" foreach "повторюється над List <T> або масивом, тоді він може оптимізувати foreach для використання перечислювачів типу значення або насправді генерувати цикл "for". Коли змушений перераховувати список або порожню послідовність, він повинен повернутися до " найнижчий загальний знаменник "кодеген, який в деяких випадках може бути повільнішим і спричиняти більший тиск пам'яті ....". Погодьтеся, що це потрібно List<T>.
Том,

@tom Передумовою відповіді є той файл. Headers - це IEnumerable <T>, і в цьому випадку компілятор не може виконати оптимізацію. Однак досить просто розширити рішення методу розширення, щоб уникнути цього. Див. Редагування
Rune FS

19

Чесно кажучи, раджу: просто всмоктуй nullтест. nullТест тільки а , brfalseабо brfalse.s; все інше буде включати в себе набагато більше роботи (тести, завдання, додаткові виклики методів, немає необхідності GetEnumerator(), MoveNext(), Dispose()ітератора, і т.д.).

ifТест простий, очевидно, і ефективний.


1
Ти робиш цікаву думку, Марк, однак, я зараз намагаюся зменшити рівні відступу коду. Але я буду пам’ятати про ваш коментар, коли мені потрібно буде взяти до відома результативність.
Емінем

3
Лише коротка нотатка про цього Марка. Через кілька років після того, як я задав це запитання, і мені потрібно внести певні покращення продуктивності, ваша порада стала надзвичайно корисною. Дякую
Емінем

13

"якщо" перед ітерацією все в порядку, мало хто з тієї "симпатичної" семантики може зробити ваш код менш читабельним.

у будь-якому випадку, якщо відступ турбує вас, ви можете змінити, якщо потрібно перевірити:

if(file.Headers == null)  
   return;

і ви потрапите до циклу foreach лише тоді, коли у властивості headers є справжнє значення.

ще один варіант, про який я можу подумати, - це використання оператора нульового злиття всередині вашого циклу foreach і для повного уникнення перевірки нуля. зразок:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(замініть колекцію вашим справжнім об’єктом / типом)


Ваш перший варіант не спрацює, якщо поза оператором if є будь-який код.
rikitikitik

Погоджуся, твердження if легко та дешево реалізувати порівняно зі створенням нового списку, лише для покращення коду.
Андреас Йоханссон,

10

Використання Null-умовного оператора та ForEach (), який працює швидше, ніж стандартний цикл foreach.
Ви повинні передати колекцію до списку.

   listOfItems?.ForEach(item => // ... );

4
Будь-ласка, додайте пояснення до своєї відповіді, чітко зазначивши, чому це рішення працює, а не просто
однокласний

найкраще рішення для моєї справи
Йозеф Хенн

3

Я використовую хороший маленький метод розширення для цих сценаріїв:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Враховуючи те, що заголовки є списком типів, ви можете зробити наступне:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}

1
Ви можете використовувати ??оператор і скоротити оператор повернення доreturn list ?? new List<T>;
Rune FS

1
@wolfgangziegler, Якщо я правильно розумію, тест для nullвашого зразка file.Headers.EnsureNotNull() != nullне потрібен, і навіть помилковий?
Ремко Янсен

0

У деяких випадках я б віддав перевагу трохи іншому, загальному варіанту, припускаючи, що, як правило, конструктори колекції за замовчуванням повертають порожні екземпляри.

Краще було б назвати цей метод NewIfDefault. Це може бути корисно не тільки для колекцій, тому обмеження типу IEnumerable<T>може бути надлишковим.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.