关于c#:扩展LINQ以接受可以为空的枚举

Extending LINQ to accept nullable enumerables

使用LINQ扩展时,通常会看到这样的代码:

1
2
3
4
5
6
IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
if (enumerable != null)
{
    sum = enumerable.Sum();
}

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

1
2
3
4
5
6
7
8
public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) yield break;
    foreach (var item in enumerable)
    {
        yield return item;
    }
}

因此,我可以将代码重构为这样:

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

我现在的问题:

  • 在运行时,与我的扩展方法相关联的惩罚是什么?
  • 这样扩展LINQ是一个好的实践吗?
  • 更新:我的目标框架是:3.5


    What penalties are associated with my extension method at runtime?

    您的扩展方法被转换成一个状态机,所以有最小的开销,但这不应该是明显的。

    Is it's a good practice to extend linq that way?

    在您的问题中,您声明:

    While working with Linq extensions it's normal to see code like this (insert enumerable null check here)

    我也不同意。通常的做法是,在预期使用IEnumerable的情况下,不要返回空值。大多数情况下应返回空集合(或IEnumerable),将null留给异常值,因为null不是空的。这将使您的方法完全多余。必要时使用Enumerable.Empty


  • 您将有一个方法调用开销,它将是可以忽略的,除非您在一个紧密循环或性能关键的场景中运行它。与数据库调用或文件系统写入相比,它只是一个阴影。注意,该方法可能不会被内联,因为它是一个枚举器。
  • 这都是关于可读性/可维护性的。当我看到GetEnumerable().IgnoreIfEmpty().Sum();时,我期望会发生什么?在这种情况下,这是有道理的。
  • 注意,使用c_6,我们可以使用以下语法:GetEnumerable()?.Sum(),它返回int?。您可以编写GetEnumerable()?.Sum() ?? 0GetEnumerable()?.Sum().GetValueOrDefault(),以获得默认为零的非空整数。

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

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

    更一般地说,关于扩展LINQ,我认为只要代码有意义,它就非常好。甚至还有一篇关于它的文章。如果你看一下linq中的标准WhereSelect方法,忘记了它们在其中的性能优化,那么这些方法基本上都是一个线性方法。


    您可以跳过附加的扩展方法并使用空合并运算符-这就是它的用途,对空性的一次性检查应该比其他状态机更有效:

    1
    2
    3
    4
    IEnumerable<int> enumerable = GetEnumerable();
    int sum = 0;

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


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

    此原始部件是否可读性较低、可扩展性较低、可重用性较低或速度较慢:

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

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