在foreach循环中检查null


91

有没有更好的方法来执行以下操作:
我需要在进行循环之前检查file.Header是否为null

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

简而言之,由于我的代码中出现的缩进级别,在if中编写foreach看起来有点难看。

是可以评估为

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

可能?




1
@AdrianFaciu我认为那是完全不同的。该问题先检查集合是否为null,然后再进行for-each。您的链接检查集合中的项目是否为空。
rikitikitik


1
C#8可以简单地使用某种空条件的foreach,例如这样的语法:foreach?(集合中的var i){}我认为这是一个足够普遍的情况,可以证明这一点,并且鉴于最近对该语言添加了空条件添加,在这里有意义吗?
2013年

Answers:


121

就像对符文(Rune)的建议的一点装饰性补充一样,您可以创建自己的扩展方法:

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

然后您可以编写:

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

根据口味更改名称:)


75

假设file.Headers中元素的类型为T,则可以执行此操作

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

如果file.Headers为null,则将创建T的空可枚举。但是,如果文件的类型是您拥有的类型,请考虑改为更改getter Headersnull是unknown的值,因此如果可能(而不是)将null实际(/最初)解释为“我不知道是否有任何元素”,请尽可能使用空集代替您知道集合中没有元素。那也将是DRY'er,因为您不必经常进行空检查。

作为Jons建议的后续编辑,您还可以创建一个扩展方法,将上述代码更改为

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”)是最好的解决方案,因为它没有:a)导致性能下降(即使不是null)也会退化到整个LCD回路IEnumerable<T>(如使用??)。将),b)要求在每个项目中添加扩展方法,或c)要求避免以null IEnumerables(Pffft!Puh-LEAZE!SMH。)开头(cuz null表示不适用,而空列表表示适用,但目前是,好吧,这是空的!也就是说,一名雇员可能获得的佣金对于非销售人员而言是N / A,而在没有收入的情况下对于销售人员而言则是空的。
汤姆(Tom)

@Tom除了一个 null检查外,对于枚举数不为null的情况也没有任何惩罚。在确保可枚举不为空的同时避免该检查是不可能的。上面的代码要求Headers设置为an IEnumerable ,它比您foreach要求的限制要严格,但要比List<T>您链接到的答案中的要求要少。在测试可枚举是否为null方面具有相同的性能损失。
符文FS

我将“ LCD”问题基于埃里克·利珀特(Eric Lippert)对弗拉德·贝兹登(Vlad Bezden)的回答与kjbartel的回答位于同一线程的评论:“ @CodeInChaos:啊,我现在明白了。当编译器可以检测到“ foreach”正在迭代时在List <T>或数组上,则它可以优化foreach以使用值类型的枚举器或实际生成一个“ for”循环。当被迫对列表或空序列进行枚举时,它必须返回到“最低公分母”代码生成器,在某些情况下可能会变慢并产生更大的内存压力。同意它的要求List<T>
汤姆(Tom),

@tom答案的前提是该文件。标头是IEnumerable <T>,在这种情况下,编译器无法进行优化。然而,很直接扩展扩展方法解决方案以避免这种情况。参见编辑
符文FS,

19

坦率地说,我建议您:参加null考试。甲null测试是只是一个brfalsebrfalse.s; 一切将涉及更多的工作(测试,作业,额外的方法调用,不必要的GetEnumerator()MoveNext()Dispose()在迭代器等)。

一个if测试是简单的,明显的,高效的。


1
您确实提出了一个有趣的观点,Marc,但是,我目前正在寻求减少代码的缩进级别。但是当我需要注意性能时,我会记住您的评论。
阿姆(Eminem)2012年

3
在我问了这个问题并需要实现一些性能增强之后,仅对此Marc ..的几年做一个简短的注释,您的建议就非常方便了。谢谢
Eminem

12

如果迭代前的“ if”很好,那么这些“ pretty”语义很少会使您的代码可读性降低。

无论如何,如果压痕打扰了您,您可以更改是否检查:

if(file.Headers == null)  
   return;

并且只有在headers属性中有一个真值时,您才会进入foreach循环。

我可以考虑的另一种选择是在foreach循环中使用null-coalescing运算符,并完全避免进行null检查。样品:

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

(用您的真实对象/类型替换集合)


如果if语句外没有任何代码,则第一个选项将不起作用。
rikitikitik

我同意,仅用于代码美化,与创建新列表相比,if语句易于实现且便宜。
安德烈亚斯·约翰逊


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语句缩短为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();
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.