0Pricing
C# Academy · レッスン

カスタム列挙可能型

イテレーターを使ってIEnumerableを実装します。

「カスタム列挙可能型」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

独自の列挙可能オブジェクトを構築する

クラスに IEnumerable<T> を実装すると、foreach で使用できるようになります。イテレーターメソッドと組み合わせれば、わずかなコードで独自のコレクションや範囲を作成できます。

yield を使って GetEnumerator を実装する

IEnumerable<T> を実装する最も簡単な方法は、GetEnumerator を yield return を使ったイテレーターとして記述することです。列挙子はコンパイラーが作成してくれます。

using System;
using System.Collections;
using System.Collections.Generic;

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
            yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new IntRange(5, 4))
            Console.WriteLine(n);
    }
}

GetEnumerator メソッドが 2 つある理由

IEnumerable<T> は非ジェネリックの IEnumerable を継承するため、両方を実装する必要があります。明示的に実装する非ジェネリック版は、単にジェネリック版へ処理を委譲します。

using System;
using System.Collections;
using System.Collections.Generic;

public class Letters : IEnumerable<char>
{
    public IEnumerator<char> GetEnumerator()
    {
        yield return 'x';
        yield return 'y';
        yield return 'z';
    }
    // Required because IEnumerable<T> extends IEnumerable
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var c in new Letters())
            Console.WriteLine(c);
    }
}

独自のロジックをカプセル化する

独自の列挙可能オブジェクトを使うと、複雑な生成ルールをわかりやすい型の背後に隠せます。ここでは、指定した長さのフィボナッチ数列を生成するクラスを作成します。

using System;
using System.Collections;
using System.Collections.Generic;

public class Fibonacci : IEnumerable<int>
{
    private readonly int _count;
    public Fibonacci(int count) { _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        int a = 0, b = 1;
        for (int i = 0; i < _count; i++)
        {
            yield return a;
            int next = a + b; a = b; b = next;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Fibonacci(8))
            Console.WriteLine(n);
    }
}

内部コレクションをラップする

独自の列挙可能オブジェクトは、内部ストレージをラップし、項目の追加方法やフィルタリング方法を制御しながら反復処理を公開することがよくあります。

using System;
using System.Collections;
using System.Collections.Generic;

public class EvenBag : IEnumerable<int>
{
    private readonly List<int> _items = new List<int>();
    public void Add(int n) { if (n % 2 == 0) _items.Add(n); }

    public IEnumerator<int> GetEnumerator() => _items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var bag = new EvenBag();
        bag.Add(1); bag.Add(2); bag.Add(3); bag.Add(4);
        foreach (var n in bag) Console.WriteLine(n);
    }
}

独自の列挙可能オブジェクトで LINQ を使う

IEnumerable<T> を実装する型は、すぐに LINQ のすべての機能を利用できるようになります。独自のコレクションに対して、フィルタリング、射影、集計をそのまま適用できます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++) yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var range = new IntRange(1, 10);
        Console.WriteLine(range.Where(n => n % 3 == 0).Sum());
    }
}

各 foreach は新しい列挙子を取得する

反復するたびに GetEnumerator が呼び出されるため、独自の列挙可能オブジェクトは何度でも列挙でき、毎回最初から新しく処理を開始できます。

using System;
using System.Collections;
using System.Collections.Generic;

public class IntRange : IEnumerable<int>
{
    private readonly int _count;
    public IntRange(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var r = new IntRange(3);
        foreach (var n in r) Console.Write(n);
        Console.WriteLine();
        foreach (var n in r) Console.Write(n); // fresh again
        Console.WriteLine();
    }
}

ジェネリックな独自の列挙可能オブジェクト

列挙可能オブジェクトをジェネリックにすると、任意の要素型を格納できます。ここでは、単純なリングが、指定したオフセットから始めて項目を順番に公開します。

using System;
using System.Collections;
using System.Collections.Generic;

public class Ring<T> : IEnumerable<T>
{
    private readonly T[] _items;
    private readonly int _start;
    public Ring(T[] items, int start) { _items = items; _start = start; }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < _items.Length; i++)
            yield return _items[(_start + i) % _items.Length];
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var ring = new Ring<string>(new[] { "a", "b", "c", "d" }, 2);
        foreach (var s in ring) Console.WriteLine(s);
    }
}

遅延実行される独自の列挙可能オブジェクト

GetEnumerator 自体がイテレーターであるため、独自の列挙可能オブジェクトはデフォルトで遅延実行されます。項目はコンシューマーが取得したときにだけ生成されます。

using System;
using System.Collections;
using System.Collections.Generic;

public class Squares : IEnumerable<int>
{
    private readonly int _count;
    public Squares(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++)
        {
            Console.WriteLine("computing " + i);
            yield return i * i;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Squares(3))
            Console.WriteLine("got " + n);
    }
}

独自の列挙可能オブジェクトを構築するタイミング

反復順序や生成ロジックが型の中心的な要素である場合は、独自の列挙可能オブジェクトを使用します。たとえば、ツリーの走査、ページ分割されたデータソース、数学的な数列などです。それ以外の場合は、通常のイテレーターメソッドで十分です。

using System;
using System.Collections;
using System.Collections.Generic;

public class Countdown : IEnumerable<int>
{
    private readonly int _from;
    public Countdown(int from) { _from = from; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = _from; i >= 0; i--) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Countdown(5)) Console.WriteLine(n);
    }
}

自分で試す

等比数列を生成する独自の列挙可能オブジェクトを作成し、それに LINQ を使ってみましょう。作成した型をエコシステム全体にそのまま組み込めます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class Geometric : IEnumerable<int>
{
    private readonly int _start, _ratio, _count;
    public Geometric(int start, int ratio, int count) { _start = start; _ratio = ratio; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        int value = _start;
        for (int i = 0; i < _count; i++) { yield return value; value *= _ratio; }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var seq = new Geometric(1, 2, 6);
        Console.WriteLine(string.Join(", ", seq));
        Console.WriteLine("sum: " + seq.Sum());
    }
}

クイックチェック

IEnumerable の契約を思い出してください。

まとめ

独自の列挙可能オブジェクトは IEnumerable<T> を実装するため、foreach と LINQ で使用できます。

  • GetEnumerator を yield return を使ったイテレーターとして実装します。
  • 非ジェネリック版の GetEnumerator も明示的に実装します。
  • 反復するたびに新しい列挙子が取得されます。
  • 反復処理はデフォルトで遅延実行されます。

よくある質問

「カスタム列挙可能型」レッスンは無料ですか?

はい。「カスタム列挙可能型」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「カスタム列挙可能型」で何を学びますか?

イテレーターを使ってIEnumerableを実装します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「カスタム列挙可能型」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. yield returnによるイテレーターメソッド
  2. yield breakと早期終了
  3. 遅延評価のセマンティクス
  4. カスタム列挙可能型
← C# Academyに戻る