ForEachAsyncAnalyzer 1.0.4

dotnet add package ForEachAsyncAnalyzer --version 1.0.4
NuGet\Install-Package ForEachAsyncAnalyzer -Version 1.0.4
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ForEachAsyncAnalyzer" Version="1.0.4" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ForEachAsyncAnalyzer --version 1.0.4
#r "nuget: ForEachAsyncAnalyzer, 1.0.4"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ForEachAsyncAnalyzer as a Cake Addin
#addin nuget:?package=ForEachAsyncAnalyzer&version=1.0.4

// Install ForEachAsyncAnalyzer as a Cake Tool
#tool nuget:?package=ForEachAsyncAnalyzer&version=1.0.4

This package contains a Roslyn C# code analyzer and fixer for the following pattern where a ForEach statement is combined with an async method. The ForEach method can be a custom extension method like in the code below. ForEach is also a given method in the great MoreLinq nuget package. The MoreLinq package also has methods like Cartesian, Partition and Permutations which are really nice!

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ForEachAsyncAnalyzerTestCode
{
    public static class IEnumerableExtensions
    {
        public static async Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> action)
        {
            if(source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if(action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            foreach(var element in source)
            {
                await action(element).ConfigureAwait(false);
            }
        }

        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            if(source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if(action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            foreach(var element in source)
            {
                action(element);
            }
        }

        class Program
        {
            private static readonly int[] _numbers = { 1, 2, 3 };

            static async Task Main(string[] args)
            {
                // Flavour one. This is already detected as compiler warning in CS4014.
                // See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs4014.
                // After fixing CS4014, you can end up in flavour two (more or less).
                // _numbers
                //     .ForEach(i => DoAsyncAction(i));

                // Flavour two.
                _numbers
                    .ForEach(async i => await DoAsyncAction(i).ConfigureAwait(false));

                // Just wait for all the tasks to finish their async tasks.
                await Task.Delay(2400).ConfigureAwait(false);
            }

            private static async Task DoAsyncAction(int i)
            {
                // The first number waits 700 ms, the second number 400 ms and number 3 waits 100 ms.
                var waitingTime = 1000 - i * 300;
                await Task.Delay(waitingTime).ConfigureAwait(false);
                Console.WriteLine($"{i}, {waitingTime}");

                // Throwing an exception at this point can give you a 'weird' call stack when this method is not awaited.
                // throw new Exception("Just a crash.");
            }
        }
    }
}

The code above just prints the numbers 1, 2 and 3 to the console. It depends on the case what you want: A) exactly this order B) any given order. When you want case A you will have to await the async task. I encountered some nasty bugs when this await wasn't done and for example an exception was thrown within the async method. Normally I always want to await the async task so that is why I build this analyzer.

It does detect the case above and advises you to fix it. So this line:

_numbers
    .ForEach(async i => await DoAsyncAction(i).ConfigureAwait(false));

becomes this line

await _numbers
    .ForEachAsync(i => DoAsyncAction(i)).ConfigureAwait(false);
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.4 12,706 11/17/2020
1.0.3 403 9/21/2020
1.0.2 464 9/1/2020
1.0.1 466 9/1/2020
1.0.0 478 9/1/2020

Added test to improve code quality.