0Pricing
C# Academy · レッスン

インターフェイスとジェネリックの拡張

多くの型に一度に機能を追加します。

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

インターフェイスの拡張

thisパラメーターにインターフェイスを指定した拡張メソッドを作成できます。そのインターフェイスを実装するすべての型が、すぐにそのメソッドを使えるようになります。LINQがIEnumerable<T>に多数のメソッドを追加しているのは、まさにこの仕組みです。

IEnumerable のジェネリック拡張

メソッドをTに対してジェネリックにし、IEnumerable<T>を拡張すると、リスト、配列、クエリ結果など、あらゆるシーケンスでヘルパーを使えるようになります。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static bool IsEmpty<T>(this IEnumerable<T> source)
    {
        foreach (var item in source) return false;
        return true;
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 1, 2, 3 };
        Console.WriteLine(nums.IsEmpty());
        Console.WriteLine(new int[0].IsEmpty());
    }
}

シーケンスを反復処理する

拡張メソッド内では、パラメーターを通常の IEnumerable<T> として扱えます。ここでは LINQ に頼らずに合計を計算します。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static int SumValues(this IEnumerable<int> source)
    {
        int total = 0;
        foreach (var n in source) total += n;
        return total;
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 10, 20, 30 };
        Console.WriteLine(nums.SumValues());
    }
}

新しいシーケンスを返す

拡張メソッドはシーケンスも返せます。yield return と組み合わせると、遅延評価される LINQ 風の演算子になります。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
    {
        bool take = true;
        foreach (var item in source)
        {
            if (take) yield return item;
            take = !take;
        }
    }
}

public class Program
{
    public static void Main()
    {
        var letters = new List<string> { "a", "b", "c", "d", "e" };
        foreach (var l in letters.EveryOther())
            Console.WriteLine(l);
    }
}

ジェネリック型制約

ジェネリックパラメーターに制約を付けて、特定の機能を要求できます。ここでの where T : IComparable<T> により、項目を比較して最大値を求められます。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static T MaxItem<T>(this IEnumerable<T> source) where T : IComparable<T>
    {
        bool started = false;
        T best = default;
        foreach (var item in source)
        {
            if (!started || item.CompareTo(best) > 0) { best = item; started = true; }
        }
        return best;
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 3, 9, 1, 7 };
        Console.WriteLine(nums.MaxItem());
    }
}

カスタムインターフェイスを拡張する

独自のインターフェイスも拡張できます。インターフェイスを定義し、各実装クラスに同じ処理を重複して記述する代わりに、拡張メソッドで共通の動作を追加します。

using System;

public interface INamed { string Name { get; } }

public class Dog : INamed { public string Name => "Rex"; }

public static class NamedExtensions
{
    public static string Greeting(this INamed n) => "Hello, " + n.Name;
}

public class Program
{
    public static void Main()
    {
        var d = new Dog();
        Console.WriteLine(d.Greeting());
    }
}

複数の型パラメーター

拡張メソッドでは、複数の型パラメーターをジェネリックにできます。この射影ヘルパーは、LINQ の Select と同様に、関数を使ってシーケンスを変換します。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static IEnumerable<TResult> Map<TSource, TResult>(
        this IEnumerable<TSource> source, Func<TSource, TResult> f)
    {
        foreach (var item in source) yield return f(item);
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 1, 2, 3 };
        foreach (var s in nums.Map(n => "#" + n))
            Console.WriteLine(s);
    }
}

組み込みの LINQ と組み合わせる

独自の拡張メソッドは LINQ と共存できます。独自の演算子を標準の演算子とつないで、1 つの流れるようなパイプラインを構築できます。

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

public static class EnumerableExtensions
{
    public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
    {
        bool take = true;
        foreach (var item in source) { if (take) yield return item; take = !take; }
    }
}

public class Program
{
    public static void Main()
    {
        var nums = Enumerable.Range(1, 10);
        var result = nums.Where(n => n > 2).EveryOther().ToList();
        Console.WriteLine(string.Join(", ", result));
    }
}

IEnumerable の拡張で多くの型に対応する

配列、リスト、ディクショナリ、ハッシュセット、クエリ結果はすべて IEnumerable<T> を実装しているため、1 つの拡張メソッドですべてに対応できます。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static int CountItems<T>(this IEnumerable<T> source)
    {
        int n = 0;
        foreach (var item in source) n++;
        return n;
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new[] { 1, 2, 3 }.CountItems());
        Console.WriteLine(new HashSet<string> { "a", "b" }.CountItems());
    }
}

このパターンがスケールする理由

インターフェイスの拡張により、継承を使わずにポリモーフィックなヘルパーを提供できます。インターフェイスに対して一度ロジックを書けば、現在および将来のすべての実装クラスが、その機能をそのまま利用できます。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static string JoinWith<T>(this IEnumerable<T> source, string sep)
    {
        return string.Join(sep, source);
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 1, 2, 3 };
        Console.WriteLine(nums.JoinWith(" -> "));
    }
}

自分で試す

IEnumerable<T> に対する小さなジェネリック演算子を作り、LINQ とつないでみましょう。どのようなシーケンス型でも動作することを確認してください。

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

public static class EnumerableExtensions
{
    public static IEnumerable<T> WithoutDuplicatesByKey<T, TKey>(
        this IEnumerable<T> source, Func<T, TKey> keySelector)
    {
        var seen = new HashSet<TKey>();
        foreach (var item in source)
            if (seen.Add(keySelector(item))) yield return item;
    }
}

public class Program
{
    public static void Main()
    {
        var words = new[] { "apple", "avocado", "banana", "cherry", "blueberry" };
        var firstPerLetter = words.WithoutDuplicatesByKey(w => w[0]);
        Console.WriteLine(string.Join(", ", firstPerLetter));
    }
}

クイックチェック

LINQ 風の拡張メソッドがどのように宣言されるか考えてみましょう。

まとめ

インターフェイス、特に IEnumerable<T> を拡張することは、LINQ 風プログラミングの基盤です。

  • メソッドをジェネリックにし、インターフェイスを this 型として使用します。
  • yield return を使って遅延実行される演算子を構築します。
  • where 制約を適用して、IComparable<T> のような機能を要求します。
  • 1 つのインターフェイス拡張で、将来追加される実装クラスを含むすべての実装クラスに対応できます。

よくある質問

「インターフェイスとジェネリックの拡張」レッスンは無料ですか?

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

「インターフェイスとジェネリックの拡張」で何を学びますか?

多くの型に一度に機能を追加します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「インターフェイスとジェネリックの拡張」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

  1. 拡張メソッドの定義
  2. インターフェイスとジェネリックの拡張
  3. 拡張メソッドの解決
  4. 適切な拡張の設計
← C# Academyに戻る