0Pricing
C# Academy · レッスン

拡張メソッドの解決

コンパイラーが拡張メソッドを見つける仕組みを理解します。

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

コンパイラーが拡張メソッドを見つける仕組み

value.Method() と記述したときに一致するインスタンスメソッドがない場合、コンパイラーは拡張メソッドを探します。ただし、using ディレクティブによって、その拡張メソッドを含む名前空間がスコープ内にある場合だけが対象です。

using ディレクティブで拡張メソッドをスコープに取り込む

拡張メソッドは、その名前空間をインポートした場合にのみ参照できます。適切な using がなければ、アセンブリにコンパイルされているにもかかわらず、メソッドが存在しないように見えます。

using System;
using MyHelpers; // brings StringExtensions into scope

namespace MyHelpers
{
    public static class StringExtensions
    {
        public static string Reverse(this string s)
        {
            var arr = s.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
        }
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("hello".Reverse());
    }
}

インスタンスメソッドが最初に確認される

コンパイラーは段階的に呼び出しを解決します。まず、一致するインスタンスメソッド(継承されたものを含む)を探します。適合するものがない場合にのみ、拡張メソッドを探します。

using System;
using System.Collections.Generic;

public static class ListExt
{
    // Ignored: List<int> already has Add(int)
    public static void Add(this List<int> list, int a, int b)
        => list.Add(a + b);
}

public class Program
{
    public static void Main()
    {
        var list = new List<int>();
        list.Add(5); // the built-in instance Add wins for this signature
        Console.WriteLine(list[0]);
    }
}

より近い名前空間が優先される

一致する拡張メソッドが 2 つある場合、名前空間の入れ子構造でより近い方が選ばれます。コンパイラーが解消できない曖昧さがあるとコンパイルエラーになり、呼び出しを明示的に区別する必要があります。

using System;

namespace Outer
{
    public static class A { public static string Tag(this int n) => "A" + n; }

    namespace Inner
    {
        public static class B { public static string Tag(this int n) => "B" + n; }

        public class Program
        {
            public static void Main()
            {
                // B is in the nearer (Inner) namespace, so it is preferred
                Console.WriteLine(7.Tag());
            }
        }
    }
}

静的呼び出しで区別する

拡張メソッドが競合する場合や明示的に呼び出したい場合は、完全なクラス名を使って静的メソッドを直接呼び出します。これにより、拡張メソッドの解決を完全に回避できます。

using System;

public static class FormatExt
{
    public static string Bracket(this string s) => "[" + s + "]";
}

public class Program
{
    public static void Main()
    {
        // Explicit static call, no ambiguity possible
        Console.WriteLine(FormatExt.Bracket("x"));
    }
}

拡張メソッド間のオーバーロード解決

複数の拡張メソッドが同じ名前を持つ場合は、通常のオーバーロード解決により、通常のメソッドオーバーロードと同じようにパラメーター型に基づいて最適なものが選ばれます。

using System;

public static class PrintExt
{
    public static string Describe(this int n) => "int:" + n;
    public static string Describe(this string s) => "str:" + s;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(42.Describe());
        Console.WriteLine("hi".Describe());
    }
}

解決時のジェネリック型推論

ジェネリック拡張メソッドでは、コンパイラーがレシーバーと引数から型引数を推論します。推論に失敗した場合、その拡張メソッドは候補になりません。

using System;
using System.Collections.Generic;

public static class SeqExt
{
    public static T FirstItem<T>(this IEnumerable<T> source)
    {
        foreach (var item in source) return item;
        throw new InvalidOperationException("empty");
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 5, 6, 7 };
        // T inferred as int from the List<int> receiver
        Console.WriteLine(nums.FirstItem());
    }
}

global using と暗黙のインポート

最新の C# では、global using ディレクティブを宣言して、プロジェクト全体で名前空間をスコープ内に置くことができます。これにより、インポートを繰り返さなくても、共有の拡張メソッドライブラリをどこからでも利用できます。

using System;

// In a real project a separate file might say: global using MyHelpers;
namespace MyHelpers
{
    public static class IntExt { public static int Twice(this int n) => n * 2; }
}

public class Program
{
    public static void Main()
    {
        // Works here because MyHelpers is referenced explicitly
        Console.WriteLine(MyHelpers.IntExt.Twice(8));
    }
}

レシーバーの型が重要になる場合

メソッドの解決は、レシーバーの実行時型ではなく静的型に基づきます。変数が派生クラスのインスタンスを保持していても、基底型に対して宣言された拡張メソッドが適用されます。ただし、より具体的な拡張メソッドがスコープ内にある場合は除きます。

using System;

public class Animal { }
public class Cat : Animal { }

public static class AnimalExt
{
    public static string Kind(this Animal a) => "animal";
}

public class Program
{
    public static void Main()
    {
        Animal a = new Cat();
        // Chosen by static type Animal
        Console.WriteLine(a.Kind());
    }
}

解決のメンタルモデル

解決の順序を次のように考えてください。(1) インスタンスメソッド、次に (2) インポートされた名前空間の拡張メソッド(近い名前空間から)、最後に (3) 候補となるメソッド間のオーバーロード解決です。この順序を知っていれば、ほとんどの意外な結果の理由を説明できます。

using System;

public static class Ext
{
    public static string Wrap(this string s) => "<" + s + ">";
}

public class Program
{
    public static void Main()
    {
        // No instance Wrap exists, so the extension is used
        Console.WriteLine("node".Wrap());
    }
}

自分で試す

名前空間内に拡張メソッドを定義し、インポートして呼び出してみましょう。次に、同じメソッドを静的メソッドとして明示的に呼び出し、どちらの方法でも動作することを確認してください。

using System;
using Text.Helpers;

namespace Text.Helpers
{
    public static class StringExtensions
    {
        public static int Vowels(this string s)
        {
            int count = 0;
            foreach (var c in s.ToLower())
                if ("aeiou".IndexOf(c) >= 0) count++;
            return count;
        }
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("education".Vowels());
        Console.WriteLine(Text.Helpers.StringExtensions.Vowels("education"));
    }
}

クイックチェック

拡張メソッドが参照可能になる条件を考えてみましょう。

まとめ

拡張メソッドの解決には明確な順序があります。

  • 最初にインスタンスメソッドが試され、拡張メソッドは不足する機能を補う場合にのみ使われます。
  • 拡張メソッドは、using または global using によって名前空間がスコープ内にある場合にのみ参照できます。
  • より近い名前空間が優先され、解消できない同順位はコンパイルエラーになります。
  • 静的メソッドを明示的に呼び出して、いつでも曖昧さを解消できます。
  • 解決にはレシーバーの静的型が使われます。

よくある質問

「拡張メソッドの解決」レッスンは無料ですか?

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

「拡張メソッドの解決」で何を学びますか?

コンパイラーが拡張メソッドを見つける仕組みを理解します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「拡張メソッドの解決」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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