Sdílet prostřednictvím


Metody rozšíření (Průvodce programováním v C#)

Metody rozšíření umožňují „přidávat“ metody ke stávajícím typům bez vytváření nového odvozeného typu, rekompilace nebo jiné změny původního typu.Metody rozšíření jsou zvláštním druhem statické metody, jsou však volány tak, jako kdyby byly metodami instance rozšířeného typu.Pro klientský kód napsaný v jazyce C# a Visual Basic neexistuje žádný zjevný rozdíl mezi voláním metody rozšíření a metodami, které jsou ve skutečnosti definovány v rámci typu.

Nejběžnějšími metodami rozšíření jsou operátory standardního dotazu LINQ, které přidávají funkce dotazu ke stávajícím typům IEnumerable a IEnumerable.Chcete-li použít operátory standardního dotazu, nejdříve je převeďte do rozsahu pomocí direktivy using System.Linq.Poté bude libovolný typ s implementací IEnumerable mít metody instance, jako jsou GroupBy``2, OrderBy``2, Average atd.Tyto další metody můžete zobrazit v doplňování příkazů IntelliSense po zadání „tečky“ za instanci typu IEnumerable, jako je List nebo Array.

Následující příklad ukazuje způsob volání metody OrderBy operátoru standardního dotazu v poli celých čísel.Výraz v závorkách je výraz lambda.Velký počet operátorů standardního dotazu používá výrazy lambda jako parametry. To však metody rozšíření nepožadují.Další informace naleznete v tématu Výrazy Lambda (Průvodce programováním v 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 rozšíření jsou definovány jako statické metody, ale jsou volány pomocí syntaxe metody instance.První parametr určuje, s jakým typem metoda pracuje, a parametru předchází tento modifikátor.Metody rozšíření se nachází v rozsahu pouze tehdy, pokud explicitně importujete obor názvů do zdrojového kódu s direktivou using.

Následující příklad znázorňuje metodu rozšíření definovanou pro třídu String.Tato třída je definována uvnitř nevnořené, neobecné statické třídy:

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

Metoda rozšíření WordCount může být přenesena do rozsahu pomocí této direktivy using:

using ExtensionMethods;

A může být volána z aplikace pomocí následující syntaxe:

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

V kódu je možné vyvolat metodu rozšíření pomocí syntaxe metody instance.Jazyk IL (Intermediate Language) generovaný kompilátorem však přeloží váš kód do volání statické metody.Princip zapouzdření tedy není ve skutečnosti porušen.Metody rozšíření ve skutečnosti nemají přístup k proměnným v typu, který rozšiřují.

Další informace naleznete v tématu Postupy: Implementace a volání vlastní metody rozšíření (Průvodce programováním v C#).

Metody rozšíření budete pravděpodobně mnohem častěji volat, než implementovat své vlastní.Vzhledem k tomu, že metody rozšíření jsou volány pomocí syntaxe metody instance, není vyžadována žádná zvláštní znalost, abyste je mohli použít v klientském kódu.Chcete-li povolit metody rozšíření pro konkrétní typ, stačí pouze přidat pro obor názvů, ve kterém jsou metody definovány, direktivu using.Chcete-li například použít operátory standardního dotazu, přidejte tuto direktivu using do kódu:

using System.Linq;

(Budete také pravděpodobně muset přidat odkaz na knihovnu System.Core.dll.) Zjistíte, že operátory standardního dotazu se nyní zobrazí v prostředí IntelliSense jako dodatečné metody, které jsou dostupné pro většinu typů IEnumerable.

[!POZNÁMKA]

Ačkoli operátory standardního dotazu pro String se v prostředí IntelliSense nezobrazují, jsou stále k dispozici.

Vytváření vazeb na metody rozšíření v době kompilace

Metody rozšíření můžete použít k rozšíření třídy nebo rozhraní, nikoli však k jejich přepsání.Metoda rozšíření se stejným názvem a signaturou, jako má rozhraní nebo metoda třídy, nebude nikdy volána.V době kompilace mají metody rozšíření vždy nižší prioritu než metody instance definované v samotném typu.Jinými slovy to znamená, že pokud má typ metodu s názvem Process(int i) a vy použijete metodu rozšíření se stejnou signaturou, kompilátor vždy vytvoří vazbu na metodu instance.Pokud kompilátor narazí na vyvolání metody, nejprve vyhledá shodu v metodách instance tohoto typu.Pokud není nalezena žádná shoda, budou vyhledány jakékoli metody rozšíření, které jsou definovány pro daný typ, a budou připojeny k první vyhledané metodě rozšíření.Následující příklad znázorňuje, jakým způsobem kompilátor určuje, se kterou metodou rozšíření nebo metodou instance má vytvořit vazbu.

Příklad

Následující příklad znázorňuje pravidla, které u kompilátoru jazyka C# určují, zda vytvořit vazbu volání metody s metodou instance v rámci typu, nebo s metodou rozšíření.Statická třída Extensions obsahuje metody rozšíření definované pro každý typ, který implementuje IMyInterface.Rozhraní implementují všechny třídy A, B a C.

Metoda rozšíření MethodB se nikdy nevolá, protože její název a signatura se přesně shodují s metodami, které třídy již implementovaly.

Pokud kompilátor nemůže najít metodu instance s odpovídající signaturou, vytvoří vazbu s odpovídající metodou rozšíření, je-li k dispozici.

// 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()
 */

Obecné pokyny

Většinou doporučujeme implementovat metody rozšíření opatrně a pouze tehdy, je-li to třeba.Kdykoli to je možné, měl by klientský kód, který musí rozšířit stávající typ, provést toto rozšíření vytvořením nového typu odvozeného ze stávajícího typu.Další informace naleznete v tématu Dědičnost (Průvodce programováním v C#).

Pokud použijete metodu rozšíření k rozšíření typu, jehož zdrojový kód nelze změnit, podstupujete riziko, že změna implementace typu způsobí poškození metody rozšíření.

Pokud implementujete metody rozšíření pro daný typ, zapamatujte si následující body:

  • Metoda rozšíření nebude nikdy volána, pokud má stejnou signaturu jako metoda definovaná v typu.

  • Dále jsou metody rozšíření přeneseny do rozsahu na úrovni oboru názvů.Pokud máte například větší počet statických tříd, které obsahují metody rozšíření v jediném oboru názvů s názvem Extensions, budou všechny přeneseny do rozsahu pomocí direktivy using Extensions;.

Chcete-li zamezit zvýšení čísla verze sestavení, neměli byste pro implementovanou knihovnu metody rozšíření používat.Pokud chcete přidat významné funkce do knihovny, jejíž zdrojový kód vlastníte, měli byste postupovat podle standardních pokynů pro rozhraní .NET Framework pro správu verzí sestavení.Další informace naleznete v tématu Správa verzí sestavení.

Viz také

Referenční dokumentace

Výrazy Lambda (Průvodce programováním v C#)

Koncepty

Průvodce programováním v C#

Přehled standardních operátorů dotazu

Další zdroje

Ukázky paralelního programování (včetně mnoha příkladů metod rozšíření)

Pravidla převodu pro parametry instance a jejich dopad

Interoperabilita metod rozšíření mezi jazyky

Metody rozšíření a curryfikované delegáty

Vazba metod rozšíření a zasílání zpráv o chybách