Implementowanie metod rozszerzeń dla klasy

Zakończone

Zazwyczaj istnieją dwa sposoby dodawania metody do istniejącego typu:

  • Zmodyfikuj kod źródłowy tego typu. Modyfikowanie źródła powoduje zmianę powodującą niezgodność, jeśli dodasz również dowolne pola danych prywatnych do obsługi metody .
  • Zdefiniuj nową metodę w klasie pochodnej. Nie można dodać metody w ten sposób przy użyciu dziedziczenia dla innych typów, takich jak struktury i wyliczenia. Nie może też służyć do "dodawania" metody do zapieczętowanej klasy.

Metody rozszerzeń umożliwiają "dodawanie" metody do istniejącego typu bez modyfikowania samego typu lub implementowania nowej metody w dziedziczonego typu. Metoda rozszerzenia nie musi również znajdować się w tym samym zestawie, co typ, który rozszerza. Metoda rozszerzenia jest wywoływana tak, jakby była zdefiniowaną składową typu.

Metody rozszerzeń

Metody rozszerzeń umożliwiają "dodawanie" metod do istniejących typów bez tworzenia nowego typu pochodnego, ponownego komkompilowania lub modyfikowania oryginalnego typu. Metody rozszerzenia to metody statyczne, ale są wywoływane tak, jakby były metodami wystąpień w typie rozszerzonym. W przypadku kodu klienta napisanego w języku C#, F# i Visual Basic nie ma wyraźnej różnicy między wywoływaniem metody rozszerzenia a metodami zdefiniowanymi w typie.

Wiązanie metod rozszerzeń w czasie kompilacji

Można użyć metod rozszerzeń, aby rozszerzyć klasę lub interfejs, ale nie zastąpić ich. Metoda rozszerzenia o tej samej nazwie i sygnaturze co metoda klasy nigdy nie jest wywoływana. W czasie kompilacji metody rozszerzeń zawsze mają niższy priorytet niż metody wystąpień zdefiniowane w samym typie. Innymi słowy, jeśli typ ma metodę o nazwie Process(int i)i masz metodę rozszerzenia z tym samym podpisem, kompilator zawsze wiąże się z metodą wystąpienia. Gdy kompilator napotka wywołanie metody, szuka dopasowania w metodach wystąpienia typu. Jeśli nie zostanie znaleziona żadna zgodna metoda wystąpienia, kompilator wyszukuje wszelkie metody rozszerzenia zdefiniowane dla typu. Kompilator wiąże się z pierwszą metodą rozszerzenia, którą znajduje.

W poniższym przykładzie pokazano reguły, które następują w kompilatorze języka C#, określając, czy powiązać wywołanie metody z metodą wystąpienia w typie, czy z metodą rozszerzenia.


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.

W tym przykładzie klasa Person ma trzy właściwości: FirstName, LastNamei Age. Klasa Person ma również metodę wystąpienia Introduce, która zapisuje komunikat w konsoli.

Klasa PersonExtensions definiuje metody rozszerzenia dla klasy Person. Klasa PersonExtensions zawiera następujące metody rozszerzenia:

  • DisplayFullName: zapisuje pełne imię i nazwisko osoby w konsoli.
  • IsAdult: zwraca wartość logiczną wskazującą, czy dana osoba jest osobą dorosłą.
  • Introduce: zapisuje komunikat w konsoli programu . Ta metoda ma dwa przeciążenia: jeden bez parametrów i jeden z parametrem ciągu.
  • Introduce(this Person person, string greeting): zapisuje komunikat w konsoli z niestandardowym powitaniem.
  • Introduce(): zapisuje do konsoli komunikat domyślny.

Metoda Main tworzy dwa wystąpienia klasy Person i wywołuje metodę Introduce w każdym wystąpieniu. Wykonywana jest metoda Introduce zdefiniowana w klasie Person. Metoda Introduce bez parametrów w klasie PersonExtensions nie jest wywoływana, ponieważ klasa Person ma już metodę wystąpienia Introduce z tym samym podpisem. Kompilator jest zgodny z regułami dotyczącymi metod rozszerzenia powiązania w czasie kompilacji i nadaje priorytet metodzie wystąpienia zdefiniowanej w samym typie. Metoda Main wywołuje również metody rozszerzenia DisplayFullName, IsAdulti przeciążonej metody Introduce z niestandardowym powitaniem. Metody rozszerzenia są wykonywane zgodnie z oczekiwaniami.

Ogólne wytyczne

Metody rozszerzeń są ważną opcją tworzenia funkcji wielokrotnego użytku w całym ekosystemie platformy .NET. Jednak modyfikowanie kodu obiektu lub wyprowadzanie nowego typu zawsze, gdy jest to uzasadnione i możliwe, jest nadal uważane za preferowane. Metody rozszerzeń są doskonałym wyborem, gdy oryginalne źródło nie znajduje się pod kontrolą, gdy obiekt pochodny jest nieodpowiedni lub niemożliwy lub gdy funkcjonalność nie powinna być widoczna poza odpowiednim zakresem.

W przypadku używania metod rozszerzeń w celu rozszerzenia typu, którego kod źródłowy nie jest pod kontrolą, istnieje ryzyko, że zmiana w implementacji typu spowoduje przerwanie metody rozszerzenia. W przypadku implementowania metod rozszerzeń dla danego typu należy pamiętać o następujących kwestiach:

  • Metoda rozszerzenia nie jest wywoływana, jeśli ma ten sam podpis co metoda zdefiniowana w typie.
  • Metody rozszerzeń są wprowadzane do zakresu na poziomie przestrzeni nazw. Jeśli na przykład masz wiele klas statycznych, które zawierają metody rozszerzenia w jednej przestrzeni nazw o nazwie Extensions, wszystkie te klasy zostaną wprowadzone do zakresu przez dyrektywę using Extensions;.

W przypadku zaimplementowanej biblioteki klas nie należy używać metod rozszerzeń, aby uniknąć przyrostowego numeru wersji zestawu. Jeśli chcesz dodać znaczącą funkcjonalność do biblioteki, dla której jesteś właścicielem kodu źródłowego, postępuj zgodnie z wytycznymi platformy .NET dotyczącymi przechowywania wersji zestawu.