By default we cannot use ForEach()
on IEnumerable
but only on List
. However, with extension methods to the rescue, it is easy to roll your own:
public static void ForEach<TSource> ( this IEnumerable<TSource> source, Action<TSource> predicate ) { // ... }
Regarding the implementation I saw a few options: I could implement ForEach()
with a traditional for
loop, parallelise the operation, use ToList()
to convert the list or other LINQ extensions such as Any()
and All()
. But which one would be the fastest? This would certainly depend on several factors such as the size of the list and the operation I was performing on the items in general.
But I was curious if this would differ significantly at all. So I created some simple benchmarks with BenchmarkDoNet and gave it a try. For this I created a static list with some pseudo-randomised strings which I would then test for IsNullOrEmpty()
(which I consider a relatively fast operation).
Then I wrote a few different extensions …
public static class EnumerableExtensions { public static void ForEachViaToList<TSource> ( this IEnumerable<TSource> source, Action<TSource> predicate ) { Contract.Requires(null != source); Contract.Requires(null != predicate); source.ToList().ForEach(predicate); } public static void ForEachViaAny<TSource> ( this IEnumerable<TSource> source, Action<TSource> predicate ) { Contract.Requires(null != source); Contract.Requires(null != predicate); source.Any(e => { predicate(e); return false; }); } public static void ForEachViaAll<TSource> ( this IEnumerable<TSource> source, Action<TSource> predicate ) { Contract.Requires(null != source); Contract.Requires(null != predicate); source.All(e => { predicate(e); return false; }); } public static void ForEachViaAsParallel<TSource> ( this IEnumerable<TSource> source, Action<TSource> predicate ) { Contract.Requires(null != source); Contract.Requires(null != predicate); source.AsParallel().ForEach(predicate); } }
… and the benchmarks:
[Benchmark] public static void ForEachViaToList() { Data._enumerable.ForEachViaToList(e => string.IsNullOrEmpty(e)); } [Benchmark] public static void ForEachViaAny() { Data._enumerable.ForEachViaAny(e => string.IsNullOrEmpty(e)); } [Benchmark] public static void ForEachViaAll() { Data._enumerable.ForEachViaAll(e => string.IsNullOrEmpty(e)); } [Benchmark] public static void ForEachViaParallel() { Data._enumerable.ForEachViaParallel(e => string.IsNullOrEmpty(e)); } [Benchmark] public static void ForEachManual() { Data._enumerable.ToList().ForEach(e => string.IsNullOrEmpty(e)); } [Benchmark] public static void ForEachViaFor() { foreach (var item in Data._enumerable) { string.IsNullOrEmpty(item); } }
The results shown below were tested in DEBUG
configuration (which we should normally not use for benchmarking), so we can expect the RELEASE
results to be a bit faster. I actually do not care about the absolute numbers, but its relative speeds. As it seems the implementation via manual ToList()
was the fastest. And I found it surprising that the manual for
loop was slower. But of course, your actual results may vary.
IEnumerable with 1000 items
Method | Mean | StdDev | Gen 0 | Allocated | ------------------- |----------- |---------- |------- |---------- | ForEachViaToList | 20.2590 us | 0.3298 us | 0.6307 | 4.04 kB | ForEachViaAny | 64.5220 us | 1.1361 us | - | 82 B | ForEachViaAll | 61.9468 us | 1.6833 us | - | 82 B | ForEachViaParallel | 61.1964 us | 1.7248 us | - | 154 B | ForEachManual | 20.6026 us | 0.4428 us | 0.6144 | 4.04 kB | ForEachViaFor | 36.9611 us | 0.8986 us | - | 25 B |
IEnumerable with 16 items
Method | Mean | StdErr | StdDev | Gen 0 | Allocated | ------------------- |-------------- |----------- |----------- |------- |---------- | ForEachViaToList | 651.5331 ns | 3.1449 ns | 12.1803 ns | 0.0066 | 100 B | ForEachViaAny | 1,394.3225 ns | 4.8660 ns | 18.8461 ns | - | 80 B | ForEachViaAll | 1,360.9192 ns | 6.8639 ns | 26.5837 ns | - | 80 B | ForEachViaParallel | 1,685.4376 ns | 13.0217 ns | 46.9504 ns | - | 152 B | ForEachManual | 642.3019 ns | 7.1587 ns | 32.0148 ns | 0.0269 | 100 B | ForEachViaFor | 782.2964 ns | 7.5939 ns | 38.7214 ns | - | 24 B |