The foreach statement allows iterating over a collection, but it is not limited to types that implement IEnumerable<T>. As explained in a previous post, you can use it with any type that has a public parameterless GetEnumerator method returning a type with a public Current property and a public parameterless MoveNext method that returns Boolean.
In C# 9, the GetEnumerator method can be provided via an extension method. For instance, IEnumerator<T> is not compatible with foreach because it does not have a GetEnumerator method. You can add one through an extension method to make this possible.
Before using this feature, you need to update the language version to 9.0 or preview:
csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
</Project>
Then, you can create the extension method and use it:
C#
static class Extensions
{
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
}
class Program
{
void Main()
{
IEnumerator<int> enumerator = Enumerable.Range(0, 10).GetEnumerator();
// Does not compile in C# 8
// Possible with C# 9 and the extension method
foreach (var item in enumerator)
{
Console.WriteLine(item);
}
}
}
The same applies for await foreach. You can now use an extension method to provide the GetAsyncEnumerator method:
C#
static class Extensions
{
public static IAsyncEnumerator<T> GetAsyncEnumerator<T>(this IAsyncEnumerator<T> enumerator) => enumerator;
}
class Program
{
static async Task Main()
{
IAsyncEnumerator<int> enumerator = GetAsyncEnumerator();
await foreach (var item in enumerator)
{
Console.WriteLine(item);
}
}
static async IAsyncEnumerator<int> GetAsyncEnumerator()
{
yield return 0;
await Task.Delay(1);
yield return 1;
}
}
Last but not least, you can create extension methods for any type. For instance, you could iterate on ValueTuple using a foreach by creating the following extension method:
C#
class Program
{
static void Main()
{
foreach (var item in (1, 2, 3))
{
Console.WriteLine(item);
}
}
}
static class Extensions
{
public static IEnumerator<object> GetEnumerator<T1, T2, T3>(this ValueTuple<T1, T2, T3> tuple)
{
yield return tuple.Item1;
yield return tuple.Item2;
yield return tuple.Item3;
}
}
This is not the most anticipated change in C#, but it is a nice addition. Be sure to use it only when it makes sense to iterate over a type.
#Additional resources
Do you have a question or a suggestion about this post? Contact me!