Bry*_*her 152 .net c# generics loops enumeration
显然有很多方法可以迭代集合.好奇,如果有任何差异,或为什么你使用一种方式而不是另一种方式.
第一种:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Run Code Online (Sandbox Code Playgroud)
另一种方式:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Run Code Online (Sandbox Code Playgroud)
我认为,除了我上面使用的匿名代表之外,我还有一个你可以指定的可重用代理......
小智 126
两者之间有一个重要且有用的区别.
因为.ForEach使用for循环来迭代集合,所以这是有效的(编辑:在.net 4.5之前 - 实现更改并且它们都抛出):
someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); });
Run Code Online (Sandbox Code Playgroud)
而foreach使用枚举器,所以这是无效的:
foreach(var item in someList)
if(item.RemoveMe) someList.Remove(item);
Run Code Online (Sandbox Code Playgroud)
tl; dr:请勿将此代码复制到您的应用程序中!
这些例子不是最好的做法,他们只是为了演示之间的差异ForEach()和foreach.
从for循环内的列表中删除项目可能会产生副作用.最常见的一个在这个问题的评论中描述.
通常,如果您要从列表中删除多个项目,则需要将确定要从实际删除中删除的项目分开.它不会保持您的代码紧凑,但它保证您不会错过任何项目.
Ant*_*ony 74
我们在这里度过了一段代码(在VS2005和C#2.0)在先前工程师走出自己使用的方式list.ForEach( delegate(item) { foo;});,而不是foreach(item in list) {foo; };对所有他们编写的代码.例如,用于从dataReader读取行的代码块.
我仍然不知道为什么他们这样做.
缺点list.ForEach()是:
它在C#2.0中更加冗长.但是,在C#3之后,您可以使用" =>"语法来制作一些非常简洁的表达式.
它不太熟悉.必须维护此代码的人会想知道为什么你这样做.我花了一段时间才决定没有任何理由,除了可能让作家看起来很聪明(其余代码的质量破坏了这一点).它的可读性也较低,})代理代码块末尾的" ".
另请参阅Bill Wagner的书"有效的C#:50改进C#的具体方法",其中他谈到为什么foreach比其他循环更喜欢for或while循环 - 主要的一点是你让编译器决定构建的最佳方法循环.如果未来版本的编译器设法使用更快的技术,那么您将通过使用foreach和rebuilding而不是更改代码来免费获得此技术.
一个foreach(item in list)结构允许你使用break或者continue如果你需要退出迭代或循环.但是你不能改变foreach循环中的列表.
我很惊讶地看到它list.ForEach稍快一些.但这可能不是一直使用它的正当理由,那将是不成熟的优化.如果您的应用程序使用的数据库或Web服务,而不是循环控制,几乎总是会在时间的推移.你有没有对它进行基准测试for?将list.ForEach可能会更快,由于使用内部和for没有包装循环甚至会更快.
我不同意该list.ForEach(delegate)版本在任何重要方面都"更具功能性".它确实将函数传递给函数,但结果或程序组织没有太大差异.
我不认为foreach(item in list)"确切地说你想要它做什么" - 一个for(int 1 = 0; i < count; i++)循环就是这样,一个foreach循环将控制选择留给了编译器.
我的感觉是,在一个新项目中,foreach(item in list)为了坚持常用和可读性而使用大多数循环,并且list.Foreach()只能用于短块,当你可以使用C#3" =>"操作符更优雅或更紧凑地执行操作时.在这种情况下,可能已经有一个更具体的LINQ扩展方法ForEach().看看Where(),Select(),Any(),All(),Max()或许多其他LINQ方法中的一种尚不你从循环想要的东西.
pli*_*nth 16
为了好玩,我将List弹出到反射器中,这就是生成的C#:
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
action(this._items[i]);
}
}
Run Code Online (Sandbox Code Playgroud)
同样,foreach使用的Enumerator中的MoveNext是这样的:
public bool MoveNext()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
if (this.index < this.list._size)
{
this.current = this.list._items[this.index];
this.index++;
return true;
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
Run Code Online (Sandbox Code Playgroud)
List.ForEach比MoveNext更加严格 - 处理更少 - 更有可能JIT变成高效的东西.
另外,无论如何,foreach()都会分配一个新的枚举器.GC是你的朋友,但如果你反复做同样的事情,这会产生更多的一次性物品,而不是重复使用同一个代表 - 但是 - 这实际上是一个边缘情况.在典型的使用中,您将看到很少或没有区别.
Sta*_*itz 13
正如他们所说,魔鬼在细节中......
两种集合枚举方法最大的区别是foreach带状态,而不带状态ForEach(x => { })。
但是让我们深入挖掘一下,因为有些事情您应该注意会影响您的决定,并且在为任何一种情况编码时都应该注意一些注意事项。
让我们List<T>在我们的小实验中使用来观察行为。对于这个实验,我使用 .NET 4.7.2:
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
Run Code Online (Sandbox Code Playgroud)
让我们首先迭代这个foreach:
foreach (var name in names)
{
Console.WriteLine(name);
}
Run Code Online (Sandbox Code Playgroud)
我们可以将其扩展为:
using (var enumerator = names.GetEnumerator())
{
}
Run Code Online (Sandbox Code Playgroud)
有了枚举器,看看幕后我们得到:
public List<T>.Enumerator GetEnumerator()
{
return new List<T>.Enumerator(this);
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default (T);
}
public bool MoveNext()
{
List<T> list = this.list;
if (this.version != list._version || (uint) this.index >= (uint) list._size)
return this.MoveNextRare();
this.current = list._items[this.index];
++this.index;
return true;
}
object IEnumerator.Current
{
{
if (this.index == 0 || this.index == this.list._size + 1)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
return (object) this.Current;
}
}
Run Code Online (Sandbox Code Playgroud)
两件事立即变得显而易见:
这当然不是线程安全的。正如上面所指出的,在迭代时更改集合只是糟糕的魔力。
但是,在迭代过程中,我们之外的人在迭代过程中对集合进行了处理,从而导致集合在迭代过程中变得无效的问题呢?最佳实践建议在操作和迭代期间对集合进行版本控制,并检查版本以检测底层集合何时发生变化。
这就是事情变得非常模糊的地方。根据微软文档:
如果对集合进行了更改,例如添加、修改或删除元素,则枚举器的行为是未定义的。
嗯,这是什么意思?举例来说,仅仅因为List<T>实现了异常处理并不意味着所有实现的集合IList<T>都会做同样的事情。这似乎明显违反了 Liskov 替换原则:
超类的对象可以用其子类的对象替换,而不会破坏应用程序。
另一个问题是枚举器必须实现IDisposable——这意味着潜在的内存泄漏的另一个来源,不仅是调用者弄错了,而且作者没有Dispose正确实现模式。
最后,我们有一个生命周期问题……如果迭代器有效,但底层集合消失了,会发生什么?我们现在是什么的快照......当你分离一个集合的生命周期和它的迭代器时,你是在自找麻烦。
现在让我们检查ForEach(x => { }):
names.ForEach(name =>
{
});
Run Code Online (Sandbox Code Playgroud)
这扩展为:
public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int version = this._version;
for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
action(this._items[index]);
if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
Run Code Online (Sandbox Code Playgroud)
重要的注意事项如下:
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
此代码不分配任何枚举数(不分配给Dispose),并且在迭代时不会暂停。
请注意,这也会执行底层集合的浅拷贝,但该集合现在是时间快照。如果作者没有正确执行对集合更改或“陈旧”的检查,则快照仍然有效。
这并不能以任何方式保护您免受生命周期问题的影响……如果基础集合消失了,您现在有一个浅表副本,指向什么……但至少您没有Dispose问题处理孤立的迭代器...
是的,我说的是迭代器……有时拥有状态是有利的。假设你想维护类似于数据库游标的东西......也许多个foreachstyleIterator<T>是要走的路。我个人不喜欢这种设计风格,因为有太多的终生问题,而且你依赖于你所依赖的集合作者的恩惠(除非你真的从头开始写所有的东西)。
总有第三种选择……
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
Run Code Online (Sandbox Code Playgroud)
它不性感,但它有牙齿(向汤姆克鲁斯和电影公司道歉)
这是你的选择,但现在你知道了,它可以是一个知情者。
Dan*_*ker 12
我知道两件让他们与众不同的晦涩难懂的事物.去吧!
首先,存在为列表中的每个项目制作委托的经典错误.如果使用foreach关键字,则所有代理最终都可以引用列表的最后一项:
// A list of actions to execute later
List<Action> actions = new List<Action>();
// Numbers 0 to 9
List<int> numbers = Enumerable.Range(0, 10).ToList();
// Store an action that prints each number (WRONG!)
foreach (int number in numbers)
actions.Add(() => Console.WriteLine(number));
// Run the actions, we actually print 10 copies of "9"
foreach (Action action in actions)
action();
// So try again
actions.Clear();
// Store an action that prints each number (RIGHT!)
numbers.ForEach(number =>
actions.Add(() => Console.WriteLine(number)));
// Run the actions
foreach (Action action in actions)
action();
Run Code Online (Sandbox Code Playgroud)
List.ForEach方法没有此问题.迭代的当前项通过值作为参数传递给外部lambda,然后内部lambda在其自己的闭包中正确捕获该参数.问题解决了.
(遗憾的是我相信ForEach是List的成员,而不是扩展方法,虽然很容易自己定义它,所以你可以在任何可枚举的类型上使用它.)
其次,ForEach方法方法有局限性.如果使用yield return实现IEnumerable,则无法在lambda中执行yield return.因此,通过此方法无法循环遍历集合中的项目以产生返回事物.您必须使用foreach关键字并通过手动在循环内复制当前循环值来解决闭包问题.
Joa*_*mer 10
我想这个someList.ForEach()调用可以很容易地并行化,而法线foreach并不容易并行.您可以轻松地在不同的核心上运行几个不同的代理,这对于普通代码来说并不容易foreach.
只需2美分
您可以命名匿名委托 :-)
您可以将第二个写为:
someList.ForEach(s => s.ToUpper())
Run Code Online (Sandbox Code Playgroud)
我更喜欢,并且可以节省很多打字时间。
正如 Joachim 所说,并行性更容易应用于第二种形式。
List.ForEach() 被认为更具功能性。
List.ForEach()说你想做什么。 foreach(item in list)还准确地说明了您希望如何完成。这使得将来可以自由地更改“如何”List.ForEach部分的实现。例如,假想的 .Net 未来版本可能始终并行运行,假设此时每个人都有许多通常处于闲置状态的 cpu 核心。 List.ForEach
另一方面,foreach (item in list)让您对循环有更多的控制。例如,您知道项目将以某种顺序进行迭代,如果项目满足某些条件,您可以很容易地在中间中断。
关于这个问题的一些最新评论可以在这里找到:
| 归档时间: |
|
| 查看次数: |
206718 次 |
| 最近记录: |