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 statické metody, ale volají se, jako by se jednalo o metody instance v rozšířeném typu. Pro klientský kód napsaný v jazyce C#, F# a Visual Basic neexistuje žádný zjevný rozdíl mezi voláním metody rozšíření a metodami definovanými v typu.

Nejběžnějšími metodami rozšíření jsou standardní operátory dotazů LINQ, které přidávají funkce dotazu do existujících System.Collections.IEnumerable a System.Collections.Generic.IEnumerable<T> typů. Pokud chcete použít standardní operátory dotazů, nejprve je přineste do oboru direktivou using System.Linq . Pak se zdá, že jakýkoli typ implementuje IEnumerable<T> instance metody, jako je GroupBy, OrderBy, Averagea tak dále. Tyto další metody můžete zobrazit v dokončování příkazů IntelliSense, když zadáte tečku za instanci IEnumerable<T> typu, například List<T> nebo Array.

Příklad OrderBy

Následující příklad ukazuje, jak volat standardní metodu operátoru OrderBy dotazu na matici celých čísel. Výraz v závorkách je výraz lambda. Mnoho standardních operátorů dotazů bere výrazy lambda jako parametry, ale není to požadavek na metody rozšíření. Další informace najdete v tématu Výrazy lambda.

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. Jejich první parametr určuje typ, na kterém metoda pracuje. Tento modifikátor předchází parametru. Metody rozšíření jsou v oboru pouze v případě, že explicitně importujete obor názvů do zdrojového kódu pomocí direktivy using .

Následující příklad ukazuje metodu rozšíření definovanou System.String pro třídu. Definuje se uvnitř nedefinované, ne generické statické třídy:

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

Metodu WordCount rozšíření lze rozšířit do působnosti této using direktivy:

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 vyvoláte metodu rozšíření pomocí syntaxe metody instance. Zprostředkující jazyk (IL) vygenerovaný kompilátorem přeloží váš kód do volání statické metody. Princip zapouzdření není ve skutečnosti porušen. Metody rozšíření nemají přístup k privátním proměnným v typu, který rozšiřují.

Třída MyExtensions i metoda jsou statica lze k ní přistupovat stejně jako ke všem ostatním static členůmWordCount. Metodu WordCount lze vyvolat podobně jako jiné static metody následujícím způsobem:

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

Předchozí kód jazyka C#:

  • Deklaruje a přiřadí nový string pojmenovaný s s hodnotou ."Hello Extension Methods"
  • Volání MyExtensions.WordCount daného argumentu s

Další informace najdete v tématu Implementace a volání vlastní metody rozšíření.

Obecně platí, že budete pravděpodobně volat metody rozšíření mnohem častěji než implementovat 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 určitý typ, stačí přidat direktivu using oboru názvů, ve kterém jsou metody definovány. Pokud chcete například použít standardní operátory dotazů, přidejte tuto using direktivu do kódu:

using System.Linq;

(Možná budete muset přidat odkaz na System.Core.dll.) Všimněte si, že standardní operátory dotazů se teď zobrazují v IntelliSense jako další metody dostupné pro většinu IEnumerable<T> typů.

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, pokud má typ metodu s názvem Process(int i)a máte metodu rozšíření se stejným podpisem, kompilátor bude vždy svázat s metodou 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 rozšiřující metody definované pro libovolný typ, který implementuje IMyInterface. Třídy A, Ba C všechny implementují rozhraní.

MethodB Metoda rozšíření se nikdy nevolá, protože její název a podpis přesně odpovídají metodám, které už jsou implementované třídami.

Pokud kompilátor nemůže najít metodu instance s odpovídajícím podpisem, vytvoří vazbu na odpovídající metodu rozšíření, pokud existuje.

// 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(IMyInterface, int)
            a.MethodA("hello");     // Extension.MethodA(IMyInterface, 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(IMyInterface, 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()
 */

Běžné vzory použití

Funkce kolekce

V minulosti bylo běžné vytvořit "Třídy kolekce", které implementovaly System.Collections.Generic.IEnumerable<T> rozhraní pro daný typ a obsažené funkce, které fungovaly na kolekcích tohoto typu. I když vytvoření tohoto typu objektu kolekce není nic špatného, stejné funkce lze dosáhnout pomocí rozšíření na objektu System.Collections.Generic.IEnumerable<T>. Rozšíření mají výhodu, která umožňuje volat funkce z jakékoli kolekce, jako je například kolekce System.Array nebo System.Collections.Generic.List<T> která implementuje System.Collections.Generic.IEnumerable<T> tento typ. Příklad použití pole Int32 najdete dříve v tomto článku.

funkce Layer-Specific

Při použití architektury onionu nebo jiného návrhu vrstvené aplikace je běžné mít sadu entit domény nebo objektů přenosu dat, které lze použít ke komunikaci mezi hranicemi aplikace. Tyto objekty obecně neobsahují žádné funkce nebo pouze minimální funkce, které platí pro všechny vrstvy aplikace. Metody rozšíření lze použít k přidání funkcí, které jsou specifické pro každou aplikační vrstvu bez načtení objektu dolů s metodami, které nejsou potřeba nebo požadované v jiných vrstvách.

public class DomainEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

static class DomainEntityExtensions
{
    static string FullName(this DomainEntity value)
        => $"{value.FirstName} {value.LastName}";
}

Rozšíření předdefinovaných typů

Místo vytváření nových objektů, když je potřeba vytvořit opakovaně použitelné funkce, můžeme často rozšířit existující typ, například typ .NET nebo CLR. Pokud například nepoužíváme metody rozšíření, můžeme vytvořit Engine nebo Query třídu, abychom mohli provádět provádění dotazu na SQL Server, které se můžou volat z více míst v našem kódu. Třídu však můžeme rozšířit System.Data.SqlClient.SqlConnection pomocí metod rozšíření k provedení dotazu odkudkoliv, kde máme připojení k SQL Server. Dalšími příklady mohou být přidání běžných funkcí do System.String třídy, rozšíření možností System.IO.File zpracování dat a System.IO.Stream objektů a System.Exception objektů pro konkrétní funkce zpracování chyb. Tyto typy případů použití jsou omezené pouze vaší fantazií a dobrým smyslem.

Rozšíření předdefinovaných typů může být u typů obtížné struct , protože jsou předány hodnotou metodám. To znamená, že všechny změny struktury jsou provedeny v kopii struktury. Tyto změny se po ukončení metody rozšíření nezobrazují. Modifikátor můžete přidat ref do prvního argumentu metody rozšíření. Přidání modifikátoru ref znamená, že první argument je předán odkazem. To umožňuje psát metody rozšíření, které mění stav rozšířené struktury.

Obecné pokyny

I když je stále vhodnější přidat funkce úpravou kódu objektu nebo odvozením nového typu, kdykoli je to rozumné a možné to udělat, metody rozšíření se staly zásadní možností pro vytváření opakovaně použitelných funkcí v ekosystému .NET. Pro tyto příležitosti, kdy původní zdroj není pod kontrolou, pokud je odvozený objekt nevhodný nebo nemožné nebo kdy by funkce neměla být vystavena nad rámec příslušného oboru, jsou metody rozšíření skvělou volbou.

Další informace o odvozených typech najdete v tématu Dědičnost.

Při použití metody rozšíření k rozšíření typu, jehož zdrojový kód nemáte pod kontrolou, spustíte riziko, že změna implementace typu způsobí přerušení metody rozšíření.

Pokud implementujete metody rozšíření pro daný typ, nezapomeňte 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íce statických tříd, které obsahují metody rozšíření v jednom oboru názvů s názvem Extensions, všechny budou převedeny do oboru direktivou using Extensions; .

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

Viz také