【问题标题】:Extending LINQ to accept nullable enumerables扩展 LINQ 以接受可为空的枚举
【发布时间】:2016-06-06 10:16:35
【问题描述】:

在使用 Linq 扩展时,看到这样的代码是很正常的:

IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
if (enumerable != null)
{
    sum = enumerable.Sum();
}

为了提高代码质量,我编写了以下扩展方法,用于检查可空枚举并中断 linq 执行。

public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) yield break;
    foreach (var item in enumerable)
    {
        yield return item;
    }
}

所以,我可以将代码重构为:

var sum = GetEnumerable().IgnoreIfEmpty().Sum();

我现在的问题:

  1. 我的扩展方法在运行时会受到哪些处罚?
  2. 以这种方式扩展 linq 是一种好习惯吗?

更新: 我的目标框架是:3.5

【问题讨论】:

  • IgnoreIfEmpty 不是一个好名字,因为“忽略”是模糊的,空的可枚举与空的不同。
  • 如果您可以控制 GetEnumerable 方法,您应该返回空集合而不是 null。
  • 如果它是空的,那不是忽略它;它将 null 转换为空序列。你应该叫它EmptyIfNull() 或类似的名字。
  • 我使用了类似的 EmptyIfNull 方法,它似乎符合 DefaultIfEmpty 的命名风格
  • @msa 我认为返回 null 代替可枚举是出乎意料的。 MS指南同意msdn.microsoft.com/en-us/library/dn169389(v=vs.110).aspx

标签: c# .net linq


【解决方案1】:

在运行时与我的扩展方法相关的惩罚是什么?

您的扩展方法已转换为状态机,因此开销最小,但这不应该引起注意。

以这种方式扩展 linq 是一种好习惯吗?

在你的问题中你说:

在使用 Linq 扩展时,看到这样的代码是很正常的(在此处插入可枚举的空检查

我不敢苟同。 常见的做法don't return null where an IEnumerable&lt;T&gt; is expected。大多数情况应该返回一个空集合(或IEnumerable),将null 留给异常,因为null is not empty。这将使您的方法完全多余。在需要的地方使用Enumerable.Empty&lt;T&gt;

【讨论】:

  • 完全同意空集合而不是空集合。 Null 应谨慎使用且清晰,适用于任何事物,而不仅仅是集合。数不清有多少错误来自空引用。
【解决方案2】:
  1. 您将有一个方法调用开销,除非您在紧密循环或性能关键场景中运行它,否则它可以忽略不计。与数据库调用或写入文件系统之类的东西相比,它只是一个影子。请注意,该方法可能不会被内联,因为它是一个枚举器。
  2. 一切都与可读性/可维护性有关。当我看到GetEnumerable().IgnoreIfEmpty().Sum(); 时会发生什么?在这种情况下,这是有道理的。

请注意,在 C# 6 中,我们可以使用以下语法:GetEnumerable()?.Sum(),它返回 int?。您可以编写 GetEnumerable()?.Sum() ?? 0GetEnumerable()?.Sum().GetValueOrDefault() 来获取默认为零的非空整数。

如果您真的关心性能,您还可以稍微重构您的方法,使其不是枚举器。这可能会增加内联的机会,尽管我不知道 JIT 编译器的“神秘”逻辑:

public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) return Enumerable.Empty<T>();
    return enumerable;
}

更一般地关于扩展 Linq,我认为只要代码有意义就可以了。 MSDN 甚至有关于它的an article。如果您查看 Linq 中的标准 Where, Select 方法,而忘记它们在其中的性能优化,这些方法大多都是单线方法。

【讨论】:

  • 抱歉我忘了说我的目标框架是3.5
  • @Tyress,为什么它不起作用?返回类型与参数类型完全相同。你那里还有yield break 声明吗?
  • @msa 答案中的所有内容都应适用于 .NET 3.5,但 C# 6 旁注除外。请注意,您可能想要升级您的工具集,它已经快 10 年了!
  • 实际上,只要您的编译器与 C#6 兼容,我认为 null 条件运算符应该可以在 .NET 3.5 上正常工作:stackoverflow.com/questions/28921701/…
  • 如果你这样写代码,那么你真的不需要扩展方法,因为调用者代码也可以简化为单行:int sum = (enumerable ?? Enumerable.Empty&lt;T&gt;()).Sum()
【解决方案3】:

您可以跳过额外的扩展方法并使用null coalescing operator - 这就是它的用途,一次性检查可空性应该比另一个状态机更有效:

IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;

sum = (enumerable ?? Enumerable.Empty<int>()).Sum();

【讨论】:

  • 好吧,当我们说“效率更高”时,我们不能说的不仅仅是毫秒,对吗?
  • @Casey - 初始列表越大,性能影响越大。
  • 或在 C#6 中,只需 int sum = enumerable?.Sum() ?? 0
  • @w.b.哦,我以为 OP 只是返回列表而不是再次对其进行迭代。你是对的。
【解决方案4】:

大多数时候,我们编写大量代码只是因为我们被自己的创作之美迷住了——不是因为我们真的需要它——然后​​我们称之为抽象、可重用、可扩展,等等。

这个原始部分是否可读性较差、可扩展性较差、可重用性较差或速度较慢:

var sum = GetEnumerable().Where(a => a != null).Sum(); 

您编写的代码越少 - 您测试的代码越少 - 保持简单。 顺便说一句 - 如果你能证明它是正确的,那么编写扩展方法是很好的。

【讨论】:

  • 我同意你关于在某种程度上保持简单的说法,但你给出的代码与问题的代码有很大的不同:你检查元素是null,而问题检查如果可枚举本身是null.
  • @amulware enumerable 不应为 null - 如果为 null,则开发人员违反了标准做法。返回空集合 - 不为空。如果 enumerable 为 null 则返回 - 为什么要计算?参考:msdn.microsoft.com/en-us/library/dn169389(v=vs.110).aspx
  • 为什么我的回答被否决了 - 任何人都有任何具体的抱怨?或者甚至可以随意对所有回复投反对票?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 2012-07-09
相关资源
最近更新 更多