Metody rozszerzeń (Przewodnik programowania w języku C#)

Metody rozszerzenia umożliwiają „dodawanie” metod do istniejących typów bez konieczności tworzenia nowego typu pochodnego, ponownej kompilacji lub modyfikowania oryginalnego typu w inny sposób.Metody rozszerzenia stanowią specjalny rodzaj metod statycznych, ale są wywoływane tak, jakby były metodami wystąpień w typie rozszerzonym.W przypadku kodu klienta napisanego w języku C# i Visual Basic nie istnieje żadna widoczna różnica między wywołaniem metody rozszerzenia a wywołaniami metod, które faktycznie są zdefiniowane w typie.

Najczęściej stosowanymi metodami rozszerzenia są standardowe operatory zapytań LINQ, które dodają funkcje zapytań do istniejących typów IEnumerable i IEnumerable.Aby użyć standardowych operatorów zapytań, najpierw należy wprowadź je do zakresu za pomocą dyrektywy using System.Linq.Następnie dowolny typ, który implementuje interfejs IEnumerable ma metody wystąpienia, takie jak GroupBy``2, OrderBy``2, Average itd.Te dodatkowe metody można zobaczyć w uzupełnieniu instrukcji za pomocą technologii IntelliSense po wpisaniu „kropki” po wystąpieniu typu IEnumerable, takiego jak List lub Array.

W poniższym przykładzie pokazano, jak wywoływać metodę standardowego operatora zapytania OrderBy w tablicy liczb całkowitych.Wyrażenie w nawiasach to wyrażenie lambda.Wiele standardowych operatorów zapytań przyjmuje wyrażenia lambda jako parametry, ale nie jest to wymagane dla metod rozszerzenia.Aby uzyskać więcej informacji, zobacz Wyrażenia lambda (Przewodnik programowania w języku C#).

class ExtensionMethods2    
{

    static void Main()
    {            
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }           
    }        
}
//Output: 10 15 21 26 39 45

Metody rozszerzenia są zdefiniowane jako metody statyczne, ale są wywoływane przy użyciu składni metod wystąpienia.Ich pierwszy parametr określa typ, na jakim metoda wykonuje operacje, a ten parametr jest poprzedzony modyfikatorem this.Metody rozszerzenia są w zakresie wyłącznie wtedy, gdy przestrzeń nazw zostanie jawnie zaimportowana do kodu źródłowego za pomocą dyrektywy using.

W poniższym przykładzie pokazano metodę rozszerzenia zdefiniowaną dla klasy String.Należy zauważyć, że zdefiniowano ją wewnątrz niezagnieżdżonej nieogólnej klasy statycznej:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

Metoda rozszerzenia WordCount może być wprowadzana do zakresu za pomocą dyrektywy using:

using ExtensionMethods;

A za pomocą poniższej składni można ją wywołać z aplikacji:

string s = "Hello Extension Methods";
int i = s.WordCount();

W kodzie metoda rozszerzenia jest wywoływana za pomocą składni metody wystąpienia.Jednak język pośredni (IL) generowany przez kompilator dokonuje translacji kodu na wywołanie metody statycznej.W związku z tym zasada hermetyzacji tak naprawdę nie jest naruszana.W rzeczywistości metody rozszerzenia nie mają dostępu do zmiennych prywatnych w typie, który rozszerzają.

Aby uzyskać więcej informacji, zobacz Porady: implementowanie i wywołanie niestandardowej metody rozszerzenia (Przewodnik programowania w języku C#).

Ogólnie rzecz biorąc, liczba wywołań metod rozszerzenia zazwyczaj jest o wiele większa niż liczba implementacji własnych metod.Metody rozszerzenia są wywoływane przy użyciu składni metody wystąpienia, więc nie jest potrzebna specjalistyczna wiedza, aby móc używać ich z poziomu kodu klienta.Aby włączyć metody rozszerzenia dla określonego typu, wystarczy dodać dyrektywę using dla przestrzeni nazw, w której są zdefiniowane te metody.Na przykład aby użyć standardowych operatorów zapytań, należy dodać do kodu następującą dyrektywę using:

using System.Linq;

(Może być też koniecznie dodanie odwołania do biblioteki System.Core.dll). Łatwo zauważyć, że standardowe operatory zapytań pojawiają się w technologii IntelliSense jako dodatkowe metody dostępne dla większości typów IEnumerable.

[!UWAGA]

Chociaż standardowe operatory zapytań nie są wyświetlane w technologii IntelliSense dla typu String, są dla niego dostępne.

Metody rozszerzające w czasie kompilacji

Można stosować metody rozszerzenia, aby rozszerzyć klasę lub interfejs, ale nie w celu pominięcia go.Metoda rozszerzenia mająca taką samą nazwę i podpis jak interfejs lub metoda klasy nigdy nie zostanie wywołana.W czasie kompilacji metody rozszerzenia zawsze mają niższy priorytet niż zdefiniowane w typie metody wystąpienia.Innymi słowy, jeśli typ ma metodę o nazwie Process(int i) i użytkownik ma metodę rozszerzenia o takim samym podpisie, kompilator zawsze utworzy powiązanie z metodą wystąpienia.Gdy kompilator napotyka wywołanie metody, najpierw szuka dopasowania w metodach wystąpienia danego typu.Jeżeli nie znajdzie dopasowania, wyszuka metody rozszerzenia, które są zdefiniowane dla danego typu, i utworzy powiązanie z pierwszą metodą rozszerzenia, którą znajdzie.W poniższym przykładzie pokazano, w jaki sposób kompilator określa metodę rozszerzenia lub metodę wystąpienia, z którą ma utworzyć powiązanie.

Przykład

W poniższym przykładzie przedstawiono reguły, zgodnie z którymi kompilator języka C# określa, czy należy powiązać wywołanie metody z metodą wystąpienia typu, czy z metodą rozszerzenia.Klasa statyczna Extensions zawiera metody rozszerzenia zdefiniowane dla dowolnego typu, który implementuje interfejs IMyInterface.Klasy A, B i C implementują interfejs.

Metoda rozszerzenia MethodB nigdy nie jest wywoływana, ponieważ jej nazwa i podpis są dokładnie takie same, jak nazwa i podpis metod już zaimplementowanych przez te klasy.

Gdy kompilator nie może odnaleźć metody wystąpienia mającej pasujący podpis, tworzy powiązanie z pasującą metodą rozszerzenia, jeśli taka istnieje.

// Define an interface named IMyInterface. 
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method 
        // that matches the following signature. 
        void MethodB();
    }
}


// Define extension methods for IMyInterface. 
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any  
    // class that implements IMyInterface. 
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each  
        // of the three classes A, B, and C implements a method named MethodB 
        // that has a matching signature. 
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}


// Define three classes that implement IMyInterface, and then use them to test 
// the extension methods. 
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods: 
            //      -- MethodA with an int argument 
            //      -- MethodA with a string argument 
            //      -- MethodB with no argument. 

            // A contains no MethodA, so each call to MethodA resolves to  
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string) 

            // A has a method that matches the signature of the following call 
            // to MethodB.
            a.MethodB();            // A.MethodB() 

            // B has methods that match the signatures of the following 
            // method calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB() 

            // B has no matching method for the following call, but  
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string) 

            // C contains an instance method that matches each of the following 
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

Ogólne wskazówki

Ogólnie zalecane jest, aby implementować metody rozszerzenia oszczędnie i tylko wtedy, gdy jest to konieczne.Jeśli to możliwe, kod klienta, który musi rozszerzyć istniejący typ, powinien to zrobić przez utworzenie nowego typu, który będzie typem pochodnym istniejącego typu.Aby uzyskać więcej informacji, zobacz Dziedziczenie (Przewodnik programowania w języku C#).

W przypadku korzystania z metody rozszerzenia w celu rozszerzania typu, którego kodu źródłowego nie można zmienić, istnieje ryzyko, że zmiana w implementacji typu spowoduje przerwanie działania metody rozszerzenia.

W przypadku implementowania metod rozszerzenia dla danego typu należy pamiętać o następujących kwestiach:

  • Metoda rozszerzenia nigdy nie zostanie wywołana, jeśli ma taki sam podpis, jak metoda zdefiniowana w typie.

  • Metody rozszerzenia są włączane do zakresu na poziomie przestrzeni nazw.Na przykład, jeśli masz wiele klas statycznych, które zawierają metody rozszerzenia w pojedynczej przestrzeni nazw o nazwie Extensions, wszystkie te metody zostaną włączone do zakresu przy użyciu dyrektywy using Extensions;.

Dla zaimplementowanej biblioteki klas nie należy używać metod rozszerzenia, aby uniknąć zwiększenia numeru wersji zestawu.W przypadku dodawania znaczącej funkcjonalności do biblioteki, której kod źródłowy jest własnością użytkownika, należy przestrzegać standardowych wytycznych programu .NET Framework dotyczących wersji zestawów.Aby uzyskać więcej informacji, zobacz Przechowywanie wersji zestawu.

Zobacz też

Informacje

Wyrażenia lambda (Przewodnik programowania w języku C#)

Koncepcje

Przewodnik programowania w języku C#

Standardowe operatory zapytań — Omówienie

Inne zasoby

Przykłady programowania równoległego (zawierają wiele przykładów metod rozszerzenia)

Reguły konwersji dla parametrów wystąpienia i ich wpływ

Współdziałanie metod rozszerzenia w różnych językach

Metody rozszerzenia i przenoszone delagaty

Tworzenie powiązań i raportowanie błędów metod rozszerzenia