受另一个关于缺少Zip
函数的问题启发:
为什么IEnumerable
接口上没有ForEach
扩展方法,或者其他地方也没有呢?唯一拥有ForEach
方法的类是List<>
。是否有某些原因导致它缺失,比如性能问题?
受另一个关于缺少Zip
函数的问题启发:
为什么IEnumerable
接口上没有ForEach
扩展方法,或者其他地方也没有呢?唯一拥有ForEach
方法的类是List<>
。是否有某些原因导致它缺失,比如性能问题?
语言中已经包含了一个foreach
语句,它通常可以胜任大部分工作。
我不想看到以下情况的发生:
list.ForEach( item =>
{
item.DoSomething();
} );
改为:
foreach(Item item in list)
{
item.DoSomething();
}
在大多数情况下,后者更清晰易读,尽管可能需要输入更长的代码。
然而,我必须承认我在这个问题上改变了我的立场; ForEach()
扩展方法在某些情况下确实是有用的。
以下是语句和方法之间的主要区别:
ForEach()
是在编译时进行的(非常好!)这些都是许多人在这里提出的很好的观点,我可以理解为什么人们会想念这个函数。我不介意微软在下一个框架迭代中添加一个标准的 ForEach 方法。
foreach
在编译时进行类型检查。只有非泛型集合缺少编译时类型检查,就像一个假设的 ForEach
扩展方法一样。 - Richard Poolecol.Where(...).OrderBy(...).ForEach(...)
。语法更简单,可以避免使用冗余本地变量或在 foreach() 中使用冗长括号导致屏幕混乱。 - Jacek Gorgońlist.ForEach(item => item.DoSomething())
。谁说它一定是列表?显然使用场景是在链的末尾;使用 foreach 语句的话,你需要将整个链放在 in
后面,并在块内执行操作,这远不如自然和一致。 - Jim BalterForEach方法是在LINQ之前添加的。如果您添加了ForEach扩展,由于扩展方法的限制,它将永远不会被List实例调用。我认为没有添加它的原因是为了不干扰现有的方法。
然而,如果你真的想念这个小巧的函数,你可以自己编写一个版本。
public static void ForEach<T>(
this IEnumerable<T> source,
Action<T> action)
{
foreach (T element in source)
action(element);
}
你可以编写这个扩展方法:
// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
foreach (var e in source)
{
action(e);
yield return e;
}
}
优点
允许链接:
MySequence
.Apply(...)
.Apply(...)
.Apply(...);
缺点
除非进行强制迭代的操作,否则它实际上不会执行任何操作。因此,它不应该被称为.ForEach()
。你可以在结尾处写.ToList()
,或者也可以编写这个扩展方法:
// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
foreach (var e in source)
{
// do nothing
;
}
return source;
}
这可能与C#库的发货方式有太大的不同;那些不熟悉您的扩展方法的读者将不知道如何处理您的代码。
{foreach (var e in source) {action(e);} return source;}
,则可以在不使用“realize”方法的情况下使用它。 - jjnguynumbers.Select(n => n*2);
- rasmusvhansenSelect()
表示对每个元素应用一个函数并返回该函数的值,而 Apply()
表示对每个元素应用一个 Action
并返回__原始__元素。 - NetMageSelect(e => { action(e); return e; })
。 - Jim Balter这里的讨论给出了答案:
实际上,我目睹的具体讨论确实是关于函数纯度的。在一个表达式中,通常会假设不存在副作用。使用ForEach会明确地引发副作用,而不仅仅是忍受它们。-- Keith Farmer (合伙人)
基本上,决定保持扩展方法的功能“纯粹”。对于可枚举的扩展方法,使用ForEach将鼓励副作用,而这并不是想要的。
虽然我同意在大多数情况下最好使用内置的foreach
构造,但我发现使用这个变体的ForEach<>扩展比自己在常规的foreach
中管理索引要更好一些:
public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
if (action == null) throw new ArgumentNullException("action");
var index = 0;
foreach (var elem in list)
action(index++, elem);
return index;
}
抱歉,我只能使用英文回答您的问题。var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));
我可以为你提供:
Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
一种解决方法是编写 .ToList().ForEach(x => ...)
。
优点:
易于理解 - 读者只需要知道C#自带的内容,而不需要了解任何其他扩展方法。
语法噪音非常小(只增加了一些不必要的代码)。
通常不会额外消耗内存,因为本地的.ForEach()
本来就需要实现整个集合。
缺点:
操作顺序不理想。我更喜欢先实现一个元素,然后对其进行操作,然后重复。这段代码首先实现所有元素,然后按顺序对它们进行操作。
如果实现列表时出现异常,则无法对单个元素进行操作。
如果枚举是无限的(例如自然数),那么你没有办法。
Enumerable.ForEach<T>
方法,但有一个List<T>.ForEach
方法,这个响应会更清楚。
c) “通常不会额外消耗内存,因为本地的 .ForEach() 方法无论如何都需要实现整个集合。” —— 完全是胡说八道。下面是 C# 提供的 Enumerable.ForEach<T> 实现:public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
- Jim Balter我一直都有这个疑问,所以我一直随身携带这个:
public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
foreach (var item in col)
{
action(item);
}
}
null
可能是可枚举对象中的一个有效元素,而该操作可以处理它:想象一下,可枚举对象是一个字符串列表,而该操作是一个修剪操作-它可以决定将null
修剪为空字符串,这样一切都很好,你绝对不希望在这种情况下出现异常。 - Christoph最近关于 ForEach 扩展方法不适用的评论很多,因为它不像 LINQ 扩展方法那样返回一个值。虽然这是事实,但并不完全正确。
LINQ 扩展方法都返回一个值,因此它们可以链接在一起使用:
collection.Where(i => i.Name = "hello").Select(i => i.FullName);
Select
,首先执行您的操作,然后返回身份(或其他您喜欢的内容)。IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });
Count()
(据我所知,这是最便宜的枚举操作)或其他你需要的操作来实现。我希望看到它被引入标准库中。static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
return src.Select(i => { action(i); return i; } );
}
people.WithLazySideEffect(p => Console.WriteLine(p))
,它实际上等同于foreach,但是是延迟和可链接的。Select
已经不再执行它原本应该执行的操作了。虽然它确实是解决 OP 所提出问题的一种方法,但就主题可读性而言:没有人会期望选择操作会执行一个函数。 - Matthias BurgerSelect
没有按照预期执行,那么问题出在 Select
的实现上,而不是调用 Select
的代码上,因为调用方对 Select
的执行没有任何影响。 - MartijnForEach
扩展方法(以及执行委托并产生其结果的Pipe
方法)。请参阅以下链接:
Parallel.ForEach
换成Enumerable.ForEach
,只发现后者根本不存在。在这里,C#错过了一个让事情变得容易的技巧。 - Colonel Panic