所以我经常遇到这种情况…其中Do.Something(...)返回一个空集合,如下所示:
1
| int[] returnArray = Do.Something(...); |
然后,我试着这样使用这个集合:
1 2 3 4
| foreach (int i in returnArray)
{
// do some more stuff
} |
我只是好奇,为什么foreach循环不能对空集合进行操作?在我看来,使用空集合执行0次迭代是合乎逻辑的…相反,它抛出了一个NullReferenceException。有人知道为什么会这样吗?
这很烦人,因为我使用的API并不清楚它们返回的确切内容,所以我最终在所有地方使用if (someCollection != null)……
编辑:感谢大家解释foreach使用GetEnumerator,如果没有枚举器,foreach将失败。我想我是在问为什么语言/运行时在获取枚举器之前不能或不会执行空检查。在我看来,这种行为仍将得到很好的定义。
- 把一个数组称为集合有点不对劲。但也许我只是个老派。
- 是的,我同意…我甚至不知道为什么这个代码基返回数组中有这么多方法
- 我想同样的道理,当给定null值时,C中的所有语句都可以定义为无操作。你是建议只使用foreach循环还是其他语句?
- @ Ken…我只是在考虑foreach循环,因为在我看来,程序员很明显,如果集合是空的或不存在,就不会发生任何事情。
- 类似于stackoverflow.com/q/6455311/80161和stackoverflow.com/a/11734449/80161
好吧,简短的回答是"因为编译器设计人员就是这样设计的。"但实际上,集合对象是空的,所以编译器没有办法让枚举器在集合中循环。
如果确实需要这样做,请尝试使用空合并运算符:
1 2 3 4 5 6
| int[] array = null;
foreach (int i in array ?? Enumerable.Empty<int>())
{
System.Console.WriteLine(string.Format("{0}", i));
} |
- 请原谅我的无知,但这有效率吗?它不会导致每次迭代的比较吗?
- 我不相信。查看生成的IL,循环在is-null比较之后。
- @希米早就被罗巴蒂卡斯回答了……一年多以前:—)
- 不明白他为什么要费心看伊利诺伊州
- 圣灵…有时,您必须查看IL,以了解编译器正在做什么,以确定是否存在任何效率方面的问题。用户919426询问是否对每个迭代进行了检查。虽然答案对某些人来说可能是显而易见的,但对每个人来说并不是显而易见的,并且提供了一个提示,即查看IL将告诉您编译器在做什么,帮助人们在未来为自己钓鱼。
- @Robaticus(甚至后来的原因)IL看起来是这样的,因为规范如此说。句法糖(又称foreach)的扩展是对"in"右边的表达式进行评估,并在结果上调用GetEnumerator。
- @符文-没错。理解规范或查看IL是找出"原因"的一种方法,或者评估两种不同的C方法是否归结为相同的IL。从本质上说,这是我对希米的观点。
- 可能与未来的读者有关-为什么你不能为一本字典做同样的事情:stackoverflow.com/questions/10431281/&hellip;
- 您也可以将它们称为语言设计器,而不是将它们称为编译器设计器。官方的C语言规范特别指出:如果x的值为null,那么在运行时会在foreach语句的描述中抛出System.NullReferenceException,其中x就是您前面提到的。即使没有这句话,它也会遵循规范,即情况就是这样。所以任何正确的C实现都必须具有这种行为。
- @Kjbartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及将整个循环推广到Enumerable的LCD的性能下降(即使不是null)(使用??)或(这里的其他答案)b)需要向每个项目添加扩展方法,或c)要求避免nullIEnumerables(pffft!加油!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有非销售佣金或销售佣金为空)。
foreach循环调用GetEnumerator方法。如果集合是null,则此方法调用将导致NullReferenceException。
返回null集合是不好的做法;您的方法应该返回空集合。
- 我同意,空的收藏品应该总是被退回…但是我没有写这些方法:)
- @北极星,空聚结操作员来救援!int[] returnArray = Do.Something() ?? new int[] {};
- 哈哈,我喜欢那些双问号
- 或:... ?? new int[0]。
- +1类似于返回空集合而不是空集合的提示。谢谢。
- 我不同意坏做法:看到了吗?如果一个函数失败了,它可以返回一个空的集合——它是对构造函数、内存分配的调用,也可能是一组要执行的代码。要么返回&171;空&187;→显然只有一个要返回的代码,要检查的代码很短,参数是&171;空&187;。只是一场表演。
- 我还想补充一点:ofc合并操作符以任何方式创建一个空列表。但这已经是用户的决定了:如果有人在某个地方调用了一个函数,例如在一个性能不重要的GUI内部,他们可能会决定这样做。但是,如果他们正在做一些需要性能的事情,他们只需插入一个检查&171;如果结果不为空,则执行foreach&187;。虽然高性能通常使用C++:
- @嗨,安吉尔,你有多少次可能会有一个方法失败(从而构造一个对象/空集合),而你有多少次会做一个空检查(最后一个当然是总是如果你返回空)。因此,如果一般情况下方法没有失败,那么您的建议将存在性能问题。
- 这应该是正确的答案。原始海报的函数应返回空的IEnumerable,而不是空的。
- @Kjbartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及将整个循环推广到Enumerable的LCD的性能下降(即使不是null)(使用??),b)需要向每个项目添加扩展方法,或c)需要避免EDOCX12〕IEnumerables(pffft!加油!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有非销售佣金或销售佣金为空)。
空集合和对集合的空引用之间有很大的区别。
在内部使用foreach时,这将调用IEnumerable的getEnumerator()方法。当引用为空时,将引发此异常。
但是,使用空的IEnumerable或IEnumerable是完全有效的。在这种情况下,foreach不会"迭代"任何东西(因为集合是空的),但也不会抛出,因为这是一个完全有效的场景。
编辑:
就个人而言,如果您需要解决此问题,我建议使用扩展方法:
1 2 3 4
| public static IEnumerable<T> AsNotNull<T>(this IEnumerable<T> original)
{
return original ?? Enumerable.Empty<T>();
} |
然后你可以打电话给:
1 2 3 4
| foreach (int i in returnArray.AsNotNull())
{
// do some more stuff
} |
- 是的,但是为什么foreach在获取枚举器之前不进行空检查呢?
- @Polaris878:因为它从未打算与空集合一起使用。这是一件好事,因为空引用和空集合应该分别处理。如果你想解决这个问题,有很多方法……。我将编辑以显示另一个选项…
- @Polaris878:我建议改写您的问题:"为什么运行库在获取枚举器之前应该进行空检查?"
- 我想我在问"为什么不呢?"哈哈,看起来行为还是很明确的
- @北极星878:我想,按照我的想法,为一个集合返回空值是一个错误。现在的情况是,在这种情况下,运行时会给您一个有意义的异常,但如果您不喜欢这种行为,则很容易解决(即:上面)。如果编译器对您隐瞒了这一点,您将在运行时丢失错误检查,但没有办法"关闭它"…
- @Polaris878:枚举空引用的行为是定义良好的。它被定义为抛出一个NullReferenceException;我认为它不允许做任何其他事情。
- 很好的解决方案,作为扩展在这里实现。
- @Kjbartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及将整个循环推广到Enumerable的LCD的性能下降(即使不是null)(就像使用??会),b)需要向每个项目添加扩展方法,或c)需要避免null1〕IEnumerables(pffft!加油!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有非销售佣金或销售佣金为空)。
解决此问题的另一种扩展方法:
1 2 3 4 5
| public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
if(items == null) return;
foreach (var item in items) action(item);
} |
以多种方式消费:
(1)采用接受T的方法:
1
| returnArray.ForEach(Console.WriteLine); |
(二)有下列用语:
1
| returnArray.ForEach(i => UpdateStatus(string.Format("{0}% complete", i))); |
(3)多行匿名方法
1 2 3 4 5 6 7
| int toCompare = 10;
returnArray.ForEach(i =>
{
var thisInt = i;
var next = i++;
if(next > 10) Console.WriteLine("Match: {0}", i);
}); |
- 在第三个示例中缺少右括号。否则,漂亮的代码可以以有趣的方式进一步扩展(用于循环、反转、跳跃等)。谢谢分享。
- 感谢您提供了如此出色的代码,但我不理解第一个方法,为什么要将console.writeline作为参数传递,尽管它打印数组元素,但不理解。
- @AjaysingConsole.WriteLine只是一个采用一个参数(Action的方法的例子。项目1、2和3显示了向.ForEach扩展方法传递函数的示例。
- @Kjbartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及将整个循环推广到Enumerable的LCD的性能下降(即使不是null)(使用??),b)需要向每个项目添加扩展方法,或c)需要避免EDOCX11〕IEnumerables(pffft!加油!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有非销售佣金或销售佣金为空)。
这是很早以前的答案,但我尝试用以下方法来避免空指针异常,并且可能对使用C空检查运算符的人有用?.
1 2 3 4 5
| //fragments is a list which can be null
fragments?.ForEach((obj) =>
{
//do something with obj
}); |
- @kjbartel一年多就打败了你(见"stackoverflow.com/a/32134295/401246")。;)这是最好的解决方案,因为它不包括:a)涉及(即使不是null的情况下)将整个循环推广到Enumerable的LCD(使用??会),b)要求在每个项目中添加一个扩展方法,c)要求避免nullIEnumerables(pffft)!加油!首先。
因为空集合与空集合不同。空集合是没有元素的集合对象;空集合是不存在的对象。
这里有一些尝试:声明任意类型的两个集合。通常初始化一个,使其为空,并为另一个赋值null。然后尝试向两个集合中添加一个对象,看看会发生什么。
只需编写一个扩展方法来帮助您:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static class Extensions
{
public static void ForEachWithNull<T>(this IEnumerable<T> source, Action<T> action)
{
if(source == null)
{
return;
}
foreach(var item in source)
{
action(item);
}
}
} |
是Do.Something()的错。这里的最佳实践是返回一个大小为0的数组(这是可能的),而不是一个空数组。
因为在幕后,foreach获得了一个枚举器,相当于:
1 2 3 4 5 6
| using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
while (enumerator.MoveNext()) {
int i = enumerator.Current;
// do some more stuff
}
} |
- 那么?为什么不能先检查它是否为空并跳过循环呢?又名,扩展方法中到底显示了什么?问题是,如果为空,最好默认跳过循环,还是抛出异常?我觉得最好跳过!似乎空容器应该被跳过而不是循环,因为如果容器为非空,则循环应该做一些事情。
- @abstractdisconce您可以对所有null引用(例如访问成员时)进行相同的争论。通常,这是一个错误,如果不是,那么就可以简单地处理这个问题,例如使用另一个用户提供的扩展方法作为答案。
- 我不这么认为.forEach用于对集合进行操作,与直接引用空对象不同。尽管有人可能会这么说,但我敢打赌,如果您分析了世界上所有的代码,您会让大多数foreach循环前面都有某种类型的空检查,只是在集合为"空"时绕过循环(因此将其视为空)。我不认为有人认为循环空集合是他们想要的,如果集合为空,他们宁愿忽略循环。也许,更确切地说,是前臂?(C中的var x)可以使用。
- 我主要想说明的一点是,它在代码中创建了一些垃圾,因为每次都必须检查一次,没有任何好的理由。当然,扩展是有效的,但是可以添加一个语言特性来避免这些问题,而不会有太多问题。(主要我认为当前的方法会产生隐藏的错误,因为程序员可能会忘记进行检查,因此会出现一个异常……因为他期望检查发生在循环之前的其他地方,或者认为检查是预初始化的(可能更改了,也可能更改了)。但在这两个原因中,行为都会像空的一样。
- @抽象不一致性很好,通过一些适当的静态分析,您知道哪里可以有空值,哪里不能。如果你得到了一个你不期望的空值,最好是失败,而不是默默地忽略问题imho(本着快速失败的精神)。因此我觉得这是正确的行为。
我认为对于为什么抛出异常的解释非常清楚,这里提供了答案。我只是想补充一下我通常处理废弃收藏品的方式。因为,有时,我会多次使用集合,每次都必须测试是否为空。为了避免出现这种情况,我会执行以下操作:
1 2 3 4 5 6
| var returnArray = DoSomething() ?? Enumerable.Empty<int>();
foreach (int i in returnArray)
{
// do some more stuff
} |
这样,我们就可以随心所欲地使用集合,而不必担心异常,也不会用过多的条件语句来争论代码。
使用空校验操作符?.也是一种很好的方法。但是,对于数组(如问题中的示例),应该在以下时间之前将其转换为列表:
1 2 3 4 5 6
| int[] returnArray = DoSomething();
returnArray?.ToList().ForEach((i) =>
{
// do some more stuff
}); |
- 在代码库中,转换为一个列表以访问foreach方法是我讨厌的事情之一。
- 我同意…我尽量避免那样。:(
1 2 3 4
| SPListItem item;
DataRow dr = datatable.NewRow();
dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty; |