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
- IgnoreIfEmpty不是一个好名字,因为"忽略"是模糊的,空的可枚举和空的可枚举是不同的。
- 如果您对GetEnumerable方法有控制权,那么实际上应该返回空集合而不是空集合。
- @达伦杨,我无法控制可枚举。加上null是预期的返回值。
- 如果它是空的,则不会忽略它;它正在将空序列转换为空序列。你应该叫它EmptyIfNull()或类似的名字。
- 我使用了这样的emptyifnull方法,它似乎适合于默认的ifempty命名样式。
- getEnumerable()可能会返回一个空的可枚举insted的空值http://stackoverflow.com/questions/196993/is-it-better-to-r&zwnj;&8203;返回空值或空值-&zwnj;&8203;集合
- @MSA我认为返回空值来代替可枚举值是意外的。微软指南同意msdn.microsoft.com/en-us/library/dn169389(v=vs.110).aspx
- stackoverflow.com/a/11267819/8155
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() ?? 0或GetEnumerable()?.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中的标准Where、Select方法,忘记了它们在其中的性能优化,那么这些方法基本上都是一个线性方法。
- 抱歉,我忘记提到我的目标框架是3.5
- return enumerable;不在我这边工作
- @泰瑞斯怎么了?
- @泰瑞斯,为什么不行?返回类型与参数类型完全相同。你还有一份yield break声明吗?
- @MSA答案中的所有内容都应适用于.NET 3.5,除了C 6旁注。请注意,您可能想升级您的工具集,它已经将近10年了!
- 实际上,我认为只要编译器兼容C 6:stackoverflow.com/questions/28921701/&hellip,空条件运算符就可以在.NET 3.5上正常工作。
- @噢,是的,我有收益率中断声明。令人惊叹的。
- @BAS,不幸的是这是一个遗留项目。我们现在正在从头开始重写,但必须保留旧的
- @MSA您使用什么版本的Visual Studio来编译项目?如果是Visual Studio 2010或更高版本,则您已经在使用比.NET 3.5更高版本的C语言。
- 如果您这样编写代码,那么就不需要扩展方法,因为调用方代码也可以简化为一行:int sum = (enumerable ?? Enumerable.Empty()).Sum()。
您可以跳过附加的扩展方法并使用空合并运算符-这就是它的用途,对空性的一次性检查应该比其他状态机更有效:
1 2 3 4
| IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
sum = (enumerable ?? Enumerable.Empty<int>()).Sum(); |
- 好吧,当我们说"更有效率"的时候,我们不能说超过几毫秒,对吗?
- @凯西-初始列表越大,对性能的影响就越大。
- 或者在C 6中,只有int sum = enumerable?.Sum() ?? 0。
- @噢,我以为操作只是返回列表,而不是重复它。你说的不错。
大多数时候,我们编写大量的代码仅仅是因为我们被我们的创造之美迷住了,而不是因为我们真的需要它,然后我们称之为抽象性、可重用性、可扩展性等。
此原始部件是否可读性较低、可扩展性较低、可重用性较低或速度较慢:
1
| var sum = GetEnumerable().Where(a => a != null).Sum(); |
你写的代码越少-你测试的代码越少-保持简单。顺便说一句,如果你能证明扩展方法是正确的,那么写它是很好的。
- 我同意你关于在某种程度上保持简单的说法,但是你给出的代码与问题的代码有很大的不同:你检查元素是null,而问题检查可枚举的本身是否是null。
- @amulwareEnumerable不应为空-如果为空,则开发人员将违反标准实践。返回空集合-不为空。如果可枚举为空,则返回-为什么要计算?参考:msdn.microsoft.com/en-us/library/dn169389(v=vs.110).aspx
- 为什么我的回答被否决了——任何一个具体的人有任何具体的抱怨?或者甚至可以否决所有的回应?