[C#] foreachについて


## はじめに

`foreach`は、C#で配列やコレクション（`List<T>`、`Collection<T>`など）を列挙する場合に使用する基本的な構文ですが、いざ自前で実装したクラスや構造体で行う場合、いくつかの条件を満たす必要があります。
今回は条件を実装例をもとに触れていきたいと思います。

## foreach ステートメント（仕様）

[反復ステートメント - `foreach` ステートメント](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/iteration-statements#the-foreach-statement)では以下のように記載されています。  
> foreach ステートメントは、次の例のように、System.Collections.IEnumerable または System.Collections.Generic.IEnumerable<T> インターフェイスを実装している型のインスタンス内の要素ごとに、ステートメントまたはステートメントのブロックを実行します。  
> foreach ステートメントは、これらの型に制限されません。 これは次の条件を満たす任意の型のインスタンスと共に使用できます。  
> - 型には、パラメーターなしのパブリック GetEnumerator メソッドがあります。 GetEnumerator メソッドは、型の拡張メソッドでもかまいません。
> - GetEnumerator メソッドの戻り値の型に、パブリック プロパティ Current と、戻り値の型が bool であるパラメーターなしのパブリック メソッド MoveNext がある。

## 確認環境
サンプルコードは、以下の環境で実行確認しています。
- Visual Studio 2022（Version 17.11.4）
- .NET 8（C# 12）

## 実装方法　その一（`IEnumerable<T>`インターフェイス）

上記の内容から一番わかりやすい実装方法としては `IEnumerable<T>` インターフェイスをする方法になります。（非ジェネリック版の`IEnumerable`もありますが、今回はジェネリック版のほうで進めます。）  
以下のようなSampleクラスでは、`IEnumerable<T>` インターフェイスを実装し、`IEnumerator<T>.GetEnumerator` メソッドの返却値には、`IEnumerator<T>` を実装したSampleEnumeratorクラスを返すことで1から引数に指定した数だけ数字を増加しながら列挙することができます。  

```csharp
public class Sample(int maxCount) : IEnumerable<int>
{
    private int maxCount = maxCount;

    public IEnumerator<int> GetEnumerator()
        => new SampleEnumerator(maxCount);

    IEnumerator IEnumerable.GetEnumerator()
        => new SampleEnumerator(maxCount);

    private sealed class SampleEnumerator(int maxCount) : IEnumerator<int>
    {
        private readonly int maxCount = maxCount;
        private int index;

        public int Current => index;

        object IEnumerator.Current => index;

        public void Dispose(){}

        public bool MoveNext()
            => index++ < maxCount;

        public void Reset()
           => index = 0;
    }
}
```

```csharp
var sample = new Sample(3);
foreach(var i in sample)
{
    Console.WriteLine($"{i}");
}
// Output;
// 1
// 2
// 3
```

いつもお世話になっている`List<T>` も同様に実装されていますが、こちらではメモリアロケーションを考慮して`IEnumerator<T>` の実装の型を struct にしているなど少しカスタマイズされています。
実際のアプリ作成時では、`IEnumerator<T>` を実装した自前のクラスや構造体を作成するよりも、例えば`List<T>`をフィールドに保持して、`List<T>.GetEnumerator` メソッドを返すほうが簡単だと思います。

```csharp
// 例えばこんな感じでList<T>のGetEnumeratorを利用する。
public class Sample(int maxCount) : IEnumerable<int>
{
    private List<int> list = new(Enumerable.Range(1, maxCount));

    public IEnumerator<int> GetEnumerator()
        => list.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator()
        => list.GetEnumerator();
}
```

## 実装方法　その二（ダックタイピング的なスタイル）

`IEnumerable<T>`インターフェイスを実装していないと`foreach` が使用できないというわけではなく、例えば`Span<T>` は`IEnumerable<T>` インターフェイスを実装していませんが、`foreach` を使用することができます。  

```csharp
Span<int> span = Enumerable.Range(1, 3).ToArray().AsSpan();

// Span<T>はIEnumerable<T>インターフェイスを実装していませんが、foreachを使用可能
foreach(var i in span)
{
    Console.WriteLine($"{i}");
}
// Output:
// 1
// 2
// 3
```

`foreach`は、`IEnumerable<T>` インターフェイスが必須というわけではなく、「パラメーターなしのパブリック `GetEnumerator` メソッドがある」と「`GetEnumerator` メソッドの戻り値の型に、パブリック プロパティ `Current` と、戻り値の型が bool であるパラメーターなしのパブリック メソッド `MoveNext` がある。」という指定した名前のメソッドおよびプロパティを実装していれば使用することができます。  
先ほどのSampleクラスでは以下のような形になります。  
ただし、この方法で実装した場合、`LINQ`を使用することができないため、その点は注意する必要があります。

```csharp
// ref structでも適用可能
public ref struct Sample(int maxCount)
{
    private int maxCount = maxCount;

    public SampleEnumerator GetEnumerator() // GetEnumeratorメソッドは必須
        => new SampleEnumerator(maxCount);

    public ref struct SampleEnumerator(int maxCount)
    {
        private readonly int maxCount = maxCount;
        private int index;

        public readonly int Current // Currentプロパティは必須
            => index;

        public bool MoveNext() // MoveNextメソッドは必須
            => index++ < maxCount;
    }
}
```

```csharp
// 同じようにforeachに使用可能です
var sample = new Sample(3);
foreach(var i in sample)
{
    Console.WriteLine($"{i}");
}
// Output:
// 1
// 2
// 3
```

この方法であれば、`Span<T>` のようにref 構造体にも`foreach`を使用することができますので、例えば `ReadOnlySpan<byte>` をソースに何かしらの値を列挙していくような処理を実装することができます。
拙作ライブラリ [Utf8StringSplitter](https://github.com/prozolic/Utf8StringSplitter) でも同様の方法を用いて実装しています。

:::note info
C# 13から制限事項はありますが、ref 構造体にインターフェイス実装を行うことが可能になるようです。
:::

```csharp
using System.Text;
using Utf8StringSplitter;

foreach(var s in Utf8Splitter.Split("1,2,3,4,5"u8, ","u8))
{
    Console.WriteLine($"{Encoding.UTF8.GetString(s)}");
}

// Output:
// 1
// 2
// 3
// 4
// 5
```

## あとがき

C#でプログラムを実装するときに普段何気なく書いている`foreach`ですが、実際に仕様を確認してみるとダックタイピング的なスタイルで書けるなどまだまだ勉強する必要があると感じました。  
あと上記でも少し出しましたが、その二（ダックタイピング的なスタイル）同様の方法で実装した拙作ライブラリ[Utf8StringSplitter](https://github.com/prozolic/Utf8StringSplitter)についても、興味があればぜひ使用してもらえればと思います。

## 参考・引用文献

1. [反復ステートメント - foreach ステートメント](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/iteration-statements#the-foreach-statement)
2. [IEnumerable<T> インターフェイス](https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerable-1?view=net-8.0)
3. [dotnet/runtime - List.cs](https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs#L1145)
4. [dotnet/runtime - Span.cs](https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Span.cs#L226)
5. [ref 構造体型](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/ref-struct)