Проверить наличие null в цикле foreach

97

Есть ли более приятный способ сделать следующее:
мне нужно проверить наличие null в файле. Заголовки, прежде чем продолжить цикл

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/…
Адриан Фачу,
stackoverflow.com/questions/872323/… - еще одна идея.
weismat
1
@AdrianFaciu Я думаю, что это совсем другое. Вопрос проверяет, является ли коллекция нулевой, прежде чем выполнять for-each. Ваша ссылка проверяет, является ли элемент в коллекции нулевым.
rikitikitik
Подобно stackoverflow.com/q/3088147/80161 и stackoverflow.com/q/6455311/80161
Натан Хартли,
1
В C # 8 может быть просто какой-то тип foreach с нулевым условием, то есть такой синтаксис: foreach? (var i in collection) {} Я думаю, что это достаточно распространенный сценарий, чтобы оправдать это, и, учитывая недавние дополнения к языку с нулевым условием, это имеет смысл?
mms

Ответы:

126

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

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

Тогда вы можете написать:

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

Меняем название по вкусу :)

Джон Скит
источник
80

Предполагая, что тип элементов в file.Headers равен T, вы можете сделать это

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

это создаст пустое перечислимое значение T, если file.Headers имеет значение null. Однако, если тип файла принадлежит вам, я бы Headersвместо этого рассмотрел возможность изменения получателя . nullявляется значением unknown, поэтому, если возможно, вместо использования 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>();
}
Руна FS
источник
Ответ @kjbartel (по адресу "stackoverflow.com/a/32134295/401246" - лучшее решение, потому что он не: а) вызывает снижение производительности (даже если нет null) вырождения всего цикла на ЖК-дисплей IEnumerable<T>(как при использовании ?? будет), б) потребует добавления метода расширения к каждому проекту или в) потребует избегать null IEnumerables(Pffft! Puh-LEAZE! SMH.) с самого начала (cuz nullозначает N / A, тогда как пустой список означает, что он применим, но в настоящее время, ну, пусто !, т. е. Сотрудник может иметь Комиссионные, которые Н / Д для не-продаж или пустые для Продаж, когда он их не заработал).
Том
@Tom, кроме одной проверки на null, нет штрафа для случаев, когда перечислитель не равен нулю. Невозможно избежать этой проверки, одновременно убедившись, что перечислимое значение не равно нулю. В приведенном выше коде требуется, чтобы заголовки были IEnumerable более строгими, чем foreachтребования, но менее строгими, чем требования List<T>в ответе, на который вы ссылаетесь. Которые имеют такое же снижение производительности при проверке, является ли перечисляемое значение null.
Rune FS
Я основывал проблему «LCD» на комментарии Эрика Липперта к ответу Влада Бездена в том же потоке, что и ответ kjbartel: «@CodeInChaos: А, теперь я понимаю вашу точку зрения. Когда компилятор может обнаружить, что« foreach »повторяется над List <T> или массивом, тогда он может оптимизировать foreach для использования перечислителей типа значения или фактически сгенерировать цикл "for". При принудительном перечислении по списку или пустой последовательности он должен вернуться к " наименьший общий знаменатель «кодогенерация, которая в некоторых случаях может быть медленнее и вызывать большую нагрузку на память ....». Согласитесь, это требует List<T>.
Том
@tom Предпосылка ответа состоит в том, что заголовки file.Headers являются IEnumerable <T>, и в этом случае компилятор не может выполнить оптимизацию. Однако довольно просто расширить решение метода расширения, чтобы избежать этого. См. Редактирование
Rune FS
19

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

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

Марк Гравелл
источник
1
Вы сделали интересный момент, Марк, однако в настоящее время я пытаюсь уменьшить уровни отступов кода. Но я учту ваш комментарий, когда мне нужно будет отметить производительность.
Eminem
3
Небольшая заметка об этом Марке. Спустя годы после того, как я задал этот вопрос и мне нужно было внести некоторые улучшения в производительность, ваш совет оказался очень кстати. Спасибо
Eminem
13

«if» перед итерацией - это нормально, некоторые из этих «красивых» семантик могут сделать ваш код менее читабельным.

в любом случае, если отступ вам мешает, вы можете изменить if, чтобы проверить:

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 легко и дешево реализовать по сравнению с созданием нового списка только для украшения кода.
Андреас Йоханссон
11

Использование оператора Null-conditional и 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();
    }
Н. Кудрявцев
источник