我经常遇到如下代码:
1 2 3 4 5 6 7
| if ( items != null)
{
foreach(T item in items)
{
//...
}
} |
基本上,if条件确保只有当items不为空时,foreach块才会执行。我想知道是否真的需要if条件,或者如果items == null条件,foreach是否会处理这个案件。
我是说,我能简单地写吗
1 2 3 4
| foreach(T item in items)
{
//...
} |
号
不用担心EDOCX1[2]是否为空?if条件是否多余?或者这取决于items的类型,或者也可能取决于T的类型?
- 你试过了吗?
- 类似于stackoverflow.com/q/3088147/80161和stackoverflow.com/a/11734449/80161
- @KJBartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及将整个循环推广到Enumerable的LCD的性能下降(即使不是null)(就像使用??会),b)需要向每个项目添加扩展方法,或c)需要避免Enumerable0〕IEnumerables(pffft!普里兹!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有佣金,非销售的不适用,或没有任何收入的空的销售)。
您仍然需要检查(项目!=null)否则将获得NullReferenceException。但是,您可以这样做:
1 2 3 4 5
| List <string> items = null;
foreach (var item in items ?? new List <string>())
{
item .Dump();
} |
但是你可以检查它的性能。所以我还是喜欢有if(物品!=空)首先。
根据埃里克的利珀特建议,我将代码改为:
1 2 3 4 5
| List<string> items = null;
foreach (var item in items ?? Enumerable.Empty<string>())
{
item.Dump();
} |
。
- + 1。我喜欢这个技巧来避免出现if的情况。
- 好主意;空数组更可取,因为它消耗更少的内存,产生更少的内存压力。Enumerable.Empty更可取,因为它缓存生成的空数组并重新使用它。
- 我预计第二个代码会变慢。它将序列退化为一个IEnumerable,而该IEnumerable又退化为一个枚举器,从而使迭代速度变慢。我的测试显示在int数组上迭代的因子5下降。
- @codeinchaos实际上第二个代码更快。使用Enumerable.Range(1000),我的速度快了41%,使用Enumerable.Range(10000),我的速度快了7%。
- @codeinchaos:您通常会发现枚举空序列的速度是您程序的性能瓶颈吗?
- 它不仅降低了空序列的枚举速度,而且还降低了全序列的枚举速度。如果序列足够长,就很重要了。对于大多数代码,我们应该选择惯用代码。但是您提到的两个分配在更少的情况下会成为性能问题。
- 我必须说,我对Enumerable.Empty()的实现有点惊讶!我以为它只会定义自己的IEnumerator实现类,并在MoveNext()中返回false,然后就可以完成了。我很惊讶它分配了一个T[0]并委托它执行IEnumerable。即使空数组是"缓存的",为什么还要分配它呢?
- @啊,我明白你的意思了。当编译器检测到"foreach"正在列表或数组上迭代时,它可以优化foreach以使用值类型枚举器或实际生成"for"循环。当强制枚举一个列表或空序列时,它必须返回到"最小公分母"codegen,这在某些情况下可能会变慢并产生更多的内存压力。这是一个微妙但极好的观点。当然,这个故事的寓意是-一如既往-如果你有一个性能问题,然后分析它,找出真正的瓶颈是什么。
- 而且,可以枚举。空的看起来不是线程安全的!(从Reflector的3.5SP1框架来看)。去检验假设
- @詹姆斯,怎么可能不是螺纹安全?如果快速连续调用,它可能返回数组的两个不同实例,但这不会违反它的约定。或者,如果它使用静态初始值设定项,则会自动执行线程安全,并始终返回相同的实例。
- @埃里克是的,我说的是把名单改成IEnumerable。虽然我同意我们需要分析以发现瓶颈分析并不能告诉我们如何改进性能。
- @codeinchaos:我说它似乎不是线程安全的,仅仅是因为静态属性中的静态字段初始化不同步。它不使用静态初始值设定项,这也是奇数。现在我看到它只会在最坏的情况下分配多个数组实例,如您所指出的。永远不会:)
- @KJBartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及(即使不是null时)将整个循环推广到Enumerable的LCD(使用??时),b)需要添加扩展方法对于每个项目,或c)要求避免nullIEnumerables(pffft!普里兹!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有佣金,非销售的不适用,或没有任何收入的空的销售)。
使用c_6,您可以将新的空条件运算符与List.ForEach(Action)一起使用(或使用您自己的IEnumerable.ForEach扩展方法)。
1 2 3 4 5
| List<string> items = null;
items?.ForEach(item =>
{
// ...
}); |
。
- 优雅的回答。谢谢!
- 这是最好的解决方案,因为它不包括:a)将整个循环推广到EDOCX1的LCD(即使不是null时),b)要求在每个项目中添加一个扩展方法,或c)要求避免nullIEnumerables(pffft!普里兹!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有佣金,非销售的不适用,或没有任何收入的空的销售)。
- @汤姆:假设items是List,而不是任何IEnumerable。(或者有一个自定义的扩展方法,你说过你不想有……)另外,我认为添加11条评论是不值得的,这些评论基本上都是说你喜欢一个特定的答案。
- @琼斯基特:A)说得好。另外,我应该提到的是,大多数其他答案都是在添加空条件运算符之前发布的(尽管我确实说"是"(不是"是")"最好的")。b)imho,是的,是的!;P这是游说/营销(为了增加阅读最佳答案的机会,减少/增加不值得阅读/应得的分数)。当然,人们应该读所有相同或相似的线索的答案,但人们离完美还差得远呢。
- @汤姆:我强烈建议你以后不要这样做。想象一下,如果每个人都不同意你的意见,然后将他们的意见添加到你所有人的意见中。(假设我在这里只写了11次回复。)这根本不是堆栈溢出的有效使用。
- 我还认为,与标准的foreach相比,呼叫代表会有一个性能上的冲击。尤其是对于一个列表,我认为它被转换成一个for循环。
这里真正的外卖应该是一个序列,一开始几乎不应该是空的。只需使它在所有程序中保持不变,即如果有序列,它就永远不会为空。它总是初始化为空序列或其他真正的序列。
如果序列从不为空,那么显然您不需要检查它。
- 如果您从WCF服务获取序列呢?可能是空的,对吧?
- @纳瓦兹:如果我有一个wcf服务返回空序列,打算让它们成为空序列,那么我会将其作为一个bug报告给它们。这就是说:如果您必须处理可能有问题的服务的格式错误的输出,那么是的,您必须通过检查空值来处理它。
- 当然,除非"空"和"空"的含义完全不同。有时这对序列有效。
- @nawaz如何处理dataTable.Rows,它返回空集合而不是空集合。也许是个虫子?
- @KJBartel的答案(在"stackoverflow.com/a/32134295/401246"上)是最好的解决方案,因为它不包括:a)涉及(即使不是null时)将整个循环推广到Enumerable的LCD(使用??时),b)需要添加扩展方法对于每个项目,或c)要求避免nullIEnumerables(pffft!普里兹!smh.)首先(cuz,null表示不适用,而空列表则表示适用。但现在是,嗯,空的!即一个模板。可能有佣金,非销售的不适用,或没有任何收入的空的销售)。
实际上,@connect:http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null上有一个功能请求
答案很合理:
I think that most foreach loops are
written with the intent of iterating a
non-null collection. If you try
iterating through null you should get
your exception, so that you can fix
your code.
号
- + 1。这很好。foreach必须检查null。
- 我想这是有利弊之分的,所以他们决定保持原样。毕竟,前臂只是一些句法上的糖分。如果您调用了items.getEnumerator(),那么如果items为空,也会崩溃,因此您必须首先测试它。
你可以用一个空列表来测试它…但这是我在msdn网站上找到的
1 2
| foreach-statement:
foreach ( type identifier in expression ) embedded-statement |
号
If expression has the value null, a System.NullReferenceException is thrown.
号
可以在扩展方法中封装空签入并使用lambda:
1 2 3 4 5 6 7 8 9
| public static class EnumerableExtensions {
public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
if (self != null) {
foreach (var element in self) {
action(element);
}
}
}
} |
代码变为:
1 2 3
| items.ForEach(item => {
...
}); |
。
如果您只想调用一个接受项并返回void的方法,那么if可以更简洁:
1
| items.ForEach(MethodThatTakesAnItem); |
。
它不是多余的。在运行时,项将强制转换为IEnumerable,并调用其GetEnumerator方法。这将导致取消对将失败的项的引用
- 1)序列不一定会被转换成IEnumerable,2)这是一个让它抛出的设计决策。c可以很容易地插入EDOCX1[1]检查开发人员是否认为这是一个好主意。
你确实需要这个。当foreach访问容器以设置迭代时,您将得到一个异常。
在封面下,foreach使用集合类上实现的接口来执行迭代。这里是通用等效接口。
The foreach statement of the C#
language (for each in Visual Basic)
hides the complexity of the
enumerators. Therefore, using foreach
is recommended instead of directly
manipulating the enumerator.
号
- 正如一个说明,它在技术上不使用界面,它使用duck类型:blogs.msdn.com/b/kcwalina/archive/2007/07/18/duckonation.as&zwnj;&8203;px界面确保了正确的方法和属性,并有助于理解意图。以及使用外部前臂…
在C 6中,你可以这样写:
1 2 3 4 5 6 7 8 9
| // some string from file or UI, i.e.:
// a) string s ="Hello, World!";
// b) string s ="";
// ...
var items = s ?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable .Empty<string>();
foreach (var item in items )
{
//..
} |
这基本上是VladBezden的解决方案,但是使用??表达式始终生成一个不为空的数组,从而使foreach存活,而不是在foreach括号内进行此检查。
如前所述,您需要检查它是否不为空。
Do not use an expression that evaluates to null.
号
第二个将抛出一个带有消息Object reference not set to an instance of an object.的NullReferenceException。
测试是必需的,因为如果集合为空,foreach将引发NullReferenceException。实际上,尝试一下很简单。
1 2 3 4 5
| List<string> items = null;
foreach(var item in items)
{
Console.WriteLine(item);
} |