クラスの拡張メソッドを実装する

完了

通常、既存の型にメソッドを追加する方法は 2 つあります。

  • その型のソース コードを変更します。 メソッドをサポートするプライベート データ フィールドも追加すると、ソースを変更すると破壊的変更が発生します。
  • 派生クラスで新しいメソッドを定義します。 構造体や列挙型など、他の型の継承を使用してメソッドをこの方法で追加することはできません。 また、シールされたクラスにメソッドを "追加" するために使用することもできません。

拡張メソッドを使用すると、型自体を変更したり、継承された型に新しいメソッドを実装したりせずに、メソッドを既存の型に "追加" できます。 拡張メソッドは、拡張する型と同じアセンブリ内に存在する必要はありません。 拡張メソッドは、型の定義されたメンバーであるかのように呼び出します。

拡張メソッド

拡張メソッドを使用すると、新しい派生型を作成したり、再コンパイルしたり、元の型を変更したりせずに、既存の型にメソッドを "追加" できます。 拡張メソッドは静的メソッドですが、拡張型のインスタンス メソッドであるかのように呼び出されます。 C#、F#、および Visual Basic で記述されたクライアント コードの場合、拡張メソッドの呼び出しと、型で定義されているメソッドの呼び出しに明らかな違いはありません。

コンパイル時に拡張メソッドをバインドする

拡張メソッドを使用してクラスまたはインターフェイスを拡張できますが、オーバーライドすることはできません。 クラス メソッドと同じ名前とシグネチャを持つ拡張メソッドは呼び出されません。 コンパイル時には、拡張メソッドの優先度は、型自体で定義されているインスタンス メソッドよりも常に低くなります。 つまり、型に Process(int i)という名前のメソッドがあり、同じシグネチャを持つ拡張メソッドがある場合、コンパイラは常にインスタンス メソッドにバインドします。 コンパイラは、メソッド呼び出しを検出すると、型のインスタンス メソッドで一致するものを検索します。 一致するインスタンス メソッドが見つからない場合、コンパイラは型に対して定義されている拡張メソッドを検索します。 コンパイラは、見つけた最初の拡張メソッドにバインドします。

次の例は、メソッド呼び出しを型のインスタンス メソッドにバインドするか拡張メソッドにバインドするかを決定する際に C# コンパイラが従う規則を示しています。


namespace ExtensionMethodDemo.PersonNamespace
{
    // Define a simple class with properties and methods
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }

        public void Introduce()
        {
            Console.WriteLine($"Hi, I'm {FirstName} {LastName}, and I'm {Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo.PersonExtensionsNamespace
{
    using ExtensionMethodDemo.PersonNamespace;

    // Define an extension method for the Person class
    public static class PersonExtensions
    {
        public static void DisplayFullName(this Person person)
        {
            Console.WriteLine($"Full Name: {person.FirstName} {person.LastName}");
        }

        public static bool IsAdult(this Person person)
        {
            return person.Age >= 18;
        }

        public static void Introduce()
        {
            Console.WriteLine($"Extension - Hi, it's nice to meet you.");
        }

        // Extension method attempting to override Introduce
        public static void Introduce(this Person person, string greeting)
        {
            Console.WriteLine($"{greeting}, I'm {person.FirstName} {person.LastName}, and I'm {person.Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo
{
    using ExtensionMethodDemo.PersonNamespace;
    using ExtensionMethodDemo.PersonExtensionsNamespace;

    class Program
    {
        static void Main(string[] args)
        {
            // Create instances of the Person class
            Person person1 = new Person { FirstName = "FName1", LastName = "LName1", Age = 25 };
            Person person2 = new Person { FirstName = "FName2", LastName = "LName2", Age = 16 };

            // Use the methods of the Person class
            person1.Introduce();
            person2.Introduce();

            // Use the extension methods
            person1.DisplayFullName();
            Console.WriteLine($"Is {person1.FirstName} an adult? {person1.IsAdult()}");

            person2.DisplayFullName();
            Console.WriteLine($"Is {person2.FirstName} an adult? {person2.IsAdult()}");

            // Use the extension method that attempts to override Introduce
            person1.Introduce("Hello");
            person2.Introduce("Greetings");
        }
    }
}
// Output:
//     Hi, I'm FName1 LName1, and I'm 25 years old.
//     Hi, I'm FName2 LName2, and I'm 16 years old.
//     Full Name: FName1 LName1
//     Is FName1 an adult? True
//     Full Name: FName2 LName2
//     Is FName2 an adult? False
//     Hello, I'm FName1 LName1, and I'm 25 years old.
//     Greetings, I'm FName2 LName2, and I'm 16 years old.

この例では、Person クラスには、FirstNameLastName、および Ageの 3 つのプロパティがあります。 Person クラスには、コンソールにメッセージを書き込むインスタンス メソッド Introduce もあります。

PersonExtensions クラスは、Person クラスの拡張メソッドを定義します。 PersonExtensions クラスには、次の拡張メソッドが含まれています。

  • DisplayFullName: ユーザーの完全な名前をコンソールに書き込みます。
  • IsAdult: 成人かどうかを示すブール値を返します。
  • Introduce: コンソールにメッセージを書き込みます。 このメソッドには、パラメーターのないオーバーロードと文字列パラメーターを持つオーバーロードの 2 つがあります。
  • Introduce(this Person person, string greeting): カスタム あいさつ文を含むメッセージをコンソールに書き込みます。
  • Introduce(): 既定のメッセージをコンソールに書き込みます。

Main メソッドは、Person クラスの 2 つのインスタンスを作成し、各インスタンスで Introduce メソッドを呼び出します。 Introduce クラスで定義されている Person メソッドが実行されます。 Introduce クラスには既に同じシグネチャを持つインスタンス メソッド PersonExtensions があるため、Person クラスのパラメーターのない Introduce メソッドは呼び出されません。 コンパイラは、コンパイル時に拡張メソッドをバインドするための規則に従い、型自体で定義されているインスタンス メソッドに優先順位を付けます。 また、Main メソッドは、DisplayFullNameIsAdult、およびオーバーロードされた Introduce メソッドをカスタム あいさつで呼び出します。 拡張メソッドは、想定どおりに実行されます。

一般的なガイドライン

拡張メソッドは、.NET エコシステム全体で再利用可能な機能を作成するための重要なオプションです。 ただし、オブジェクトのコードを変更したり、適切で可能な場合は常に新しい型を派生させる方が望ましいと考えられます。 拡張メソッドは、元のソースが制御下にない場合、派生オブジェクトが不適切または不可能な場合、または機能が該当するスコープを超えて公開されないようにする場合に最適です。

拡張メソッドを使用してソース コードを管理していない型を拡張する場合、型の実装が変更されると拡張メソッドが中断するリスクが発生します。 特定の型の拡張メソッドを実装する場合は、次の点に注意してください。

  • 拡張メソッドは、型で定義されているメソッドと同じシグネチャを持つ場合は呼び出されません。
  • 拡張メソッドは、名前空間レベルでスコープに取り込まれます。 たとえば、Extensionsという名前の 1 つの名前空間に拡張メソッドを含む複数の静的クラスがある場合、それらはすべて using Extensions; ディレクティブによってスコープに取り込まれます。

実装したクラス ライブラリでは、アセンブリのバージョン番号を増やさないように拡張メソッドを使用しないでください。 ソース コードを所有するライブラリに重要な機能を追加する場合は、アセンブリのバージョン管理に関する .NET ガイドラインに従ってください。