受另一个询问缺少Zip
功能的问题的启发:
为什么班上没有ForEach
扩展方法Enumerable
?还是任何地方?唯一获得ForEach
方法的类是List<>
。是否有其丢失(性能)的原因?
Parallel.ForEach
了Enumerable.ForEach
只找回后者不存在。C#错过了使事情变得容易的技巧。
受另一个询问缺少Zip
功能的问题的启发:
为什么班上没有ForEach
扩展方法Enumerable
?还是任何地方?唯一获得ForEach
方法的类是List<>
。是否有其丢失(性能)的原因?
Parallel.ForEach
了Enumerable.ForEach
只找回后者不存在。C#错过了使事情变得容易的技巧。
Answers:
语言中已经包含了一个foreach语句,该语句在大多数情况下会起作用。
我不愿看到以下内容:
list.ForEach( item =>
{
item.DoSomething();
} );
代替:
foreach(Item item in list)
{
item.DoSomething();
}
后者在大多数情况下更清晰易读,尽管键入时间可能更长一些。
但是,我必须承认我在这个问题上改变了立场。在某些情况下,ForEach()扩展方法确实很有用。
这是语句和方法之间的主要区别:
这些都是许多人在这里提出的要点,我可以理解为什么人们缺少此功能。我不介意Microsoft在下一个框架迭代中添加标准的ForEach方法。
foreach
如果可枚举参数化,则在编译时检查类型。它只缺少针对非通用集合的编译时类型检查,就像假设的ForEach
扩展方法一样。
col.Where(...).OrderBy(...).ForEach(...)
。语法简单得多,没有多余的局部变量或foreach()中的长括号引起的屏幕污染。
list.ForEach(item => item.DoSomething())
。谁说这是清单?显而易见的用例是在链的末端。使用foreach语句,您必须将整个链放在之后in
,并将要执行的操作放在该块内,这远不自然或一致。
在LINQ之前添加了ForEach方法。如果添加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;}
numbers.Select(n => n*2);
Select()
说函数应用到每一个元素,并返回其中函数的值Apply()
表示一个适用Action
于每个元素,并返回原来的元素。
Select(e => { action(e); return e; })
这里的讨论给出了答案:
实际上,我所看到的具体讨论实际上取决于功能的纯度。在表达式中,经常会假设没有副作用。拥有ForEach尤其会带来副作用,而不仅仅是忍受它们。-Keith Farmer(合作伙伴)
基本上,决定使扩展方法在功能上保持“纯”。使用Enumerable扩展方法时,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); }
我一直想知道自己,这就是为什么我总是随身携带这个:
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);
}
}
不错的扩展方法。
因此,对于ForEach扩展方法不合适,因为它不会像LINQ扩展方法那样返回值,因此有很多评论。尽管这是事实陈述,但并非完全正确。
LINQ扩展方法都返回一个值,因此可以将它们链接在一起:
collection.Where(i => i.Name = "hello").Select(i => i.FullName);
但是,仅仅因为LINQ是使用扩展方法实现的,并不意味着必须以相同的方式使用扩展方法并返回值。编写扩展方法以暴露不返回值的通用功能是一种完全有效的用法。
关于ForEach的特定论点是,基于对扩展方法的限制(即,扩展方法将永远不会覆盖具有相同签名的继承方法),可能存在这样的情况:自定义扩展方法可用于所有妨碍实现的类IEnumerable <T
>“列表”除外<T
。当方法根据调用扩展方法还是继承方法而开始表现不同时,这可能引起混乱。
您可以使用(可链接,但懒惰地求值)Select
,首先进行操作,然后返回身份(或者您愿意的其他方式)
IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });
您将需要使用Count()
(最便宜的枚举afaik的操作)或无论如何需要的其他操作来确保仍对其进行评估。
我很乐意将其引入标准库:
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所要求的解决方案-但关于主题的可读性:没有人会期望select会执行功能。
Select
不执行应执行的操作Select
,则是实现的问题,而不是调用的代码的问题,这对执行的操作Select
没有影响Select
。
注意,MoreLINQ NuGet提供了ForEach
您要查找的扩展方法(以及Pipe
执行委托并产生其结果的方法)。看到:
@钱币
foreach扩展方法的真正强大之处在于Action<>
无需在代码中添加不必要的方法即可重用。假设您有10个列表,并且要对它们执行相同的逻辑,并且相应的函数不适合您的类,因此不会被重用。您可以将所有逻辑放在一个位置(而不是使用十个for循环或一个显然不属于辅助函数的通用函数)(Action<>
因此,数十行被替换为
Action<blah,blah> f = { foo };
List1.ForEach(p => f(p))
List2.ForEach(p => f(p))
等等...
逻辑合二为一,您没有污染课堂。
f(p)
,请省略{ }
速记:for (var p in List1.Concat(List2).Concat(List2)) f(p);
。
大多数LINQ扩展方法都返回结果。ForEach不适合此模式,因为它不返回任何内容。
public IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
如果您有F#(它将在.NET的下一版本中),则可以使用
Seq.iter doSomething myIEnumerable
部分原因是语言设计者从哲学角度不同意它。
https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/
您可以在需要返回内容时使用select。如果不这样做,则可以先使用ToList,因为您可能不想修改集合中的任何内容。
我写了一篇关于它的博客文章:http : //blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
如果您想在.NET 4.0中看到此方法,可以在这里投票:http : //connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093
在3.5中,所有添加到IEnumerable的扩展方法都支持LINQ(请注意,它们是在System.Linq.Enumerable类中定义的)。在这篇文章中,我解释了为什么foreach不属于LINQ: 现有的LINQ扩展方法类似于Parallel.For?
是我还是List <T>。Linq几乎将Foreach淘汰了。最初有
foreach(X x in Y)
其中Y只需是IEnumerable(Pre 2.0),并实现GetEnumerator()。如果您查看生成的MSIL,您会发现它与
IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
int i = enumerator.Current;
Console.WriteLine(i);
}
(有关MSIL,请参见http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx)
然后在DotNet2.0中出现了泛型和列表。我一直认为Foreach是Vistor模式的实现,(请参阅Gamma,Helm,Johnson和Vlissides的设计模式)。
当然,现在在3.5版本中,我们可以改为使用Lambda达到相同的效果,例如,请尝试 http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-我/
我想扩展一下Aku的答案。
如果您要仅出于副作用的目的而调用方法,而无需首先遍历整个可枚举,则可以使用以下方法:
private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
foreach (var x in xs) {
f(x); yield return x;
}
}
Select(x => { f(x); return x;})