【问题标题】:Checking for null in a collection检查集合中的空值
【发布时间】:2013-02-07 20:52:48
【问题描述】:

哪种空值检查方式更好用,为什么?

var myVar = myCollection.FirstOrDefault(q => q.Id == 10);
if (myVar != null)
{
   anotherVar = myVar.MyName;
}

或:

var myVar = myCollection.Where(q => q.Id == 10);
if (myVar.Any())
{
   anotherVar = myVar.First().MyName;
}

还是没有区别?

【问题讨论】:

  • 您的第二个选项不涉及空值检查?
  • @Haxx:第二种方式不能为null,因为它是一个集合,不能为null,只能为空。
  • 我的观点完全正确。所以这不是关于“哪种空值检查方式更好用,为什么?”还是我仍然错过了重点? :)
  • 只是意见,但第一种方式对我来说更具可读性。虽然它不像第二个被混淆了。
  • @Gray:实际上它只是由于命名错误而被混淆了。第一个是对象,第二个是查询,但两者具有相同的名称。如果您将其命名为var itemsWithId10 = myCollection.Where(q => q.Id == 10);,那么if(itemsWithId10.Any()) 会更清晰。

标签: c# linq


【解决方案1】:

可能是过早的优化,但第一种方法只需要一次执行,因此效率更高。

当我稍后可能再次懒惰地需要查询时,我正在使用第二种方法。 FirstOrDefault“完成”它。

【讨论】:

  • 第一种方法不仅会迭代一次,而且一旦找到匹配项,它也会停止。即使在找到第一个匹配项之后,'where' 也会迭代到最后。 (linq to objects)
  • @ErenErsönmez 你的第二个陈述是错误的。 Where 使用延迟执行,所以它不会迭代任何东西,直到它需要,当它需要时。调用 Where(predicate).First() 迭代与 First(predicate) 相同数量的项目。
  • @ErenErsönmez:除了 Servy 的评论:Enumerable.Where 与循环中的 if 语句相当。你可以随时打破。所以Where(...).First()在第一个匹配元素之后中断,如果你使用Where(...).Take(10),它将执行循环,直到Where的谓词返回10次true
  • @ErenErsönmez:阅读以下链接以了解 Linq 扩展方法的顺序何时重要:1)Does the order of LINQ functions matter? 和(我自己的)2)Order of LINQ extension methods does not affect performance?
【解决方案2】:

我更喜欢第一种方式,因为它更清楚你打算做什么。

【讨论】:

  • 恕我直言,第二种方法更具可读性,因为它使用了有意义的方法。 Where 指定过滤器,Any 检查是否有 any 结果。但是FirstOrDefault 只需要执行一次。
【解决方案3】:

你想要一个元素。

所以一个带有空检查的FirstOrDefault()

Where
Any
First

就性能而言,在大多数情况下,这不会改变您的生活。但我会首先考虑“可读性”。

【讨论】:

  • 在性能方面,它肯定会改变您的生活。如果myCollection 是访问数据库的IQueryable<T>,那么将其迭代两次是一笔 交易。或者,如果它是一个包含数百万个项目的列表,并且第一个匹配的项目就在末尾附近,那么迭代它两次很重要很多。它也可以是在迭代时进行大量计算的可枚举,例如复杂的 Linq-to-objects 查询,或者是不能超快速迭代的数据结构(即 LinkedList)。
  • @Servy:我认为即使是命中数据库的 linq 提供程序也不会迭代“数百万个项目”两次,Any 已优化并缓存查询。
  • @Servy 是的,当然,我们可以找到极端情况(我不同意将 db 上的两次点击视为“主要”交易,但无论如何)。但这是优化。当出现问题时,我会负责优化,可读性通常是一个问题(我的观点)。
  • @RaphaëlAlthaus 在大多数情况下你是对的,优化并不是最重要的,但是做一个额外的数据库操作是为数不多的事情之一,即使做一个额外的查询也可能有人类明显的影响。如果您在程序中没有优化任何其他内容,请确保尽可能少地执行与数据库的往返。
  • @RaphaëlAlthaus:在我看来,第一个不是更清晰,而是更短(并且只需要执行一次)。 WhereAny 是有意义的,如果您想稍后进一步过滤查询或延迟使用它,它会很有用。 FirstOrDefault 完成查询,它有点笨拙,因为您使用 nullcheck 来确定查询是否具有 Any 元素。如果你选择一个值类型,它甚至很容易出错。
【解决方案4】:

第一个选项可能会因为 null 项目通过检查而被破坏,从而使您认为没有匹配的项目,即使它们是。它不适用于这个特定的例子,但它可以适用于一般情况。

但是,这里的第二个示例迭代源序列两次(有时),一次是查看是否有任何结果,然后再次获得该结果。如果源序列需要执行数据库查询以获取可能非常昂贵的结果。因此,只有在您确定自己有一个正在处理的内存中集合并且它不是特别大(或者您需要的第一个项目会很快找到)时,您才应该使用此选项。

如果您需要担心第一个选项的这种特殊边缘情况,或者您想获得使用 AnyFirst 的好处(因为它们对您想要的东西的高级语义表示)与FirstOrDefault 的性能优势可以使用这种模式:

var myVar = myCollection.Where(q => q.Id == 10)
    .Take(1)
    .ToList();
if (myVar.Any())
{
   anotherVar = myVar.First().MyName;
}

如果你愿意,你可以做一个扩展方法来缩短它:

public static IEnumerable<T> FirstOrEmpty<T>(this IEnumerable<T> source)
{
    //TODO: null check arguments
    using (var iterator = source.GetEnumerator())
    {
        if (iterator.MoveNext())
            return new T[] { iterator.Current };
        else
            return Enumerable.Empty<T>();
    }
}

public static IEnumerable<T> FirstOrEmpty<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    return FirstOrEmpty(source.Where(predicate));
}

这样你就可以写:

var myVar = myCollection.FirstOrEmpty(q => q.Id == 10);
if (myVar.Any())
{
   anotherVar = myVar.First().MyName;
}

【讨论】:

    【解决方案5】:

    我倾向于使用涉及 Where 后跟 FirstOrDefault 的表达式:

    var myVar = myCollection.Where(x => x.Id==10).FirstOrDefault();
    if (myVar != null)
    {
       anotherVar = myVar.MyName;
    }
    

    正如 Raphael Althaus 上面指出的那样,您希望对一个变量进行空值检查,在我看来,您应该首先使用条件进行查询,然后选择第一个(如果存在)并进行检查。

    【讨论】:

      【解决方案6】:

      这两种方法不同的,这里是IL:

      FirstOrDefault + if (myVar != null)

      IL_0067:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_006C:  brtrue.s    IL_007F
      IL_006E:  ldnull      
      IL_006F:  ldftn       b__0
      IL_0075:  newobj      System.Func<<>f__AnonymousType0<System.Int32,System.String>,System.Boolean>..ctor
      IL_007A:  stsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_007F:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_0084:  call        System.Linq.Enumerable.FirstOrDefault  <-----
      IL_0089:  stloc.2     // myVar
      IL_008A:  ldloc.2     // myVar
      IL_008B:  brfalse.s   IL_0094
      IL_008D:  ldloc.2     // myVar
      IL_008E:  callvirt    <>f__AnonymousType0<System.Int32,System.String>.get_MyName
      IL_0093:  stloc.1     // anotherVar
      IL_0094:  ldloc.1     // anotherVar
      
      • FirstOrDefault

      在哪里 + if (myVar.Any())

      IL_0067:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_006C:  brtrue.s    IL_007F
      IL_006E:  ldnull      
      IL_006F:  ldftn       b__0
      IL_0075:  newobj      System.Func<<>f__AnonymousType0<System.Int32,System.String>,System.Boolean>..ctor
      IL_007A:  stsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_007F:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
      IL_0084:  call        System.Linq.Enumerable.Where   <-----
      IL_0089:  stloc.2     // myVar
      IL_008A:  ldloc.2     // myVar
      IL_008B:  call        System.Linq.Enumerable.Any     <-----
      IL_0090:  brfalse.s   IL_009E
      IL_0092:  ldloc.2     // myVar
      IL_0093:  call        System.Linq.Enumerable.First   <-----
      IL_0098:  callvirt    <>f__AnonymousType0<System.Int32,System.String>.get_MyName
      IL_009D:  stloc.1     // anotherVar
      IL_009E:  ldloc.1     // anotherVar
      
      • 在哪里
      • 任何
      • 首先

      看起来像微优化,但第一个应该更快,因为FirstOrDefault 的单个枚举但如果Where 之后的枚举数包含q.Id == 10 的元素不多没关系。在两者之间,我肯定更喜欢最清晰的语法

      顺便说一句,我是null 的忠实粉丝......那么使用if (myVar != default(T)) 呢?

      【讨论】:

        猜你喜欢
        • 2019-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-24
        • 1970-01-01
        • 2021-12-31
        • 2017-05-28
        • 2016-06-20
        相关资源
        最近更新 更多