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. Rozšiřující metody jsou statické metody, ale jsou volány, jako by šlo o metody instance rozšířeného typu. pro klientský kód napsaný v jazyce C#, F # a Visual Basic neexistuje žádný zřejmý rozdíl mezi voláním metody rozšíření a metodami definovanými v typu.

Nejběžnější metody rozšíření jsou operátory dotazů LINQ Standard, které přidávají funkce dotazů existujícím System.Collections.IEnumerable System.Collections.Generic.IEnumerable<T> typům a. Chcete-li použít standardní operátory dotazu, nejprve je přeneste do rozsahu s using System.Linq direktivou. Pak jakýkoli typ, který implementuje, IEnumerable<T> má metody instance, jako například GroupBy , OrderBy , Average a tak dále. Tyto další metody lze zobrazit v dokončování příkazů technologie IntelliSense, pokud zadáte "tečka" po instanci IEnumerable<T> typu, například List<T> nebo Array .

Příklad OrderBy

Následující příklad ukazuje, jak zavolat standardní metodu operátoru dotazu OrderBy na pole celých čísel. Výraz v závorkách je výraz lambda. Mnoho standardních operátorů dotazů přijímá výrazy lambda jako parametry, ale to není vyžadováno pro metody rozšíření. Další informace naleznete v tématu lambda výrazy.

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, na který typ metoda pracuje. Tomuto parametru předchází Tento modifikátor. Metody rozšíření jsou v oboru pouze v případě, že explicitně importujete obor názvů do zdrojového kódu s using direktivou.

Následující příklad ukazuje metodu rozšíření definovanou pro System.String třídu. Je definovaný 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;
        }
    }
}

WordCountMetoda rozšíření může být přenesena do rozsahu pomocí 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();

Metodu rozšíření ve vašem kódu vyvoláte pomocí syntaxe metody instance. Převodní jazyk (IL) generovaný kompilátorem překládá váš kód do volání statické metody. Princip zapouzdření není opravdu vyrušován. Metody rozšíření nemůžou přistupovat k privátním proměnným v typu, který rozšiřují.

MyExtensionsTřída i WordCount Metoda jsou static a mohou být k dispozici jako všichni ostatní static Členové. WordCountMetodu lze vyvolat podobným static způsobem jako jiné metody:

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

Předchozí kód jazyka C#:

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

Další informace naleznete 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ž implementace vlastního. 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čí přidat using direktivu pro obor názvů, ve kterém jsou metody definovány. Například pro použití standardních operátorů dotazu přidejte tuto using direktivu do kódu:

using System.Linq;

(Je také možné, že budete muset přidat odkaz na System.Core.dll.) Všimněte si, že standardní operátory dotazu 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 typ obsahuje metodu s názvem a máte Process(int i) metodu rozšíření se stejnou signaturou, kompilátor bude vždy svázán 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 metody rozšíření definované pro jakýkoli typ, který implementuje IMyInterface . Třídy A , B a C implementují rozhraní.

MethodBMetoda rozšíření se nikdy nevolá, protože její název a signatura se přesně shodují s metodami, které už jsou implementované třídami.

Pokud kompilátor nemůže najít metodu instance s vyhovující signaturou, bude vytvořena vazba s vyhovující metodou rozšíření, pokud taková 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é vzorce použití

Funkce kolekce

V minulosti bylo běžné vytvořit "třídy kolekcí", které implementovaly System.Collections.Generic.IEnumerable<T> rozhraní pro daný typ a obsahovaly funkce, které se v kolekcích daného typu jednaly. Přestože při vytváření tohoto typu objektu kolekce není nic špatné, je možné dosáhnout stejné funkčnosti pomocí rozšíření na System.Collections.Generic.IEnumerable<T> . Rozšíření mají výhodu povolit volání funkce z jakékoli kolekce, jako je například System.Array nebo System.Collections.Generic.List<T> , která implementuje System.Collections.Generic.IEnumerable<T> Tento typ. Příklad tohoto použití pole typu Int32 najdete výše v tomto článku.

Funkce Layer-Specific

Při použití průsvitek nebo jiného návrhu vrstvené aplikace je běžné mít sadu entit domény nebo Přenos dat objekty, které lze použít ke komunikaci napříč hranicemi aplikací. Tyto objekty obecně neobsahují žádné funkce nebo pouze minimální funkce, které se vztahují na všechny vrstvy aplikace. Metody rozšíření lze použít k přidání funkcionality, která je specifická pro každou vrstvu aplikace bez načítání objektu mimo jiné, než je potřeba nebo žádoucí 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ů, pokud je třeba vytvořit opakovaně použitelná funkčnost, můžeme často rozlišit existující typ, například typ .NET nebo CLR. pokud například nepoužíváme rozšiřující metody, můžeme vytvořit Engine Query třídu nebo k provedení dotazu na SQL Server, která může být volána z více míst v našem kódu. Místo toho však můžeme rozšířit System.Data.SqlClient.SqlConnection třídu pomocí rozšiřujících metod, aby bylo možné tento dotaz provést odkudkoli odkudkoli, máme připojení k SQL Server. Další příklady mohou být přidány do třídy společné funkce System.String , rozšiřuje možnosti zpracování dat System.IO.File objektů a a System.IO.Stream System.Exception objekty pro konkrétní funkce zpracování chyb. Tyto typy případů použití jsou omezené jenom představivost a dobrým smyslem.

Rozšíření předdefinovaných typů může být obtížné s struct typy, protože jsou předány podle hodnoty metodám. To znamená, že všechny změny struktury jsou provedeny na kopii struktury. Tyto změny nejsou po ukončení metody rozšíření viditelné. Počínaje jazykem C# 7,2 můžete přidat ref Modifikátor k prvnímu argumentu metody rozšíření. Přidání ref modifikátoru znamená, že první argument je předán odkazem. To umožňuje psát rozšiřující metody, 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 pokaždé, když je to přijatelné a možné, je nutné, aby se metody rozšíření staly zásadní volbou pro vytváření opakovaně použitelných funkcí v rámci ekosystému .NET. Pro případy, kdy původní zdroj není pod vaším ovládacím prvkem, když je odvozený objekt nevhodný nebo nedostupný nebo pokud by se funkce nemusely vystavit nad rámec příslušného rozsahu, jsou rozšiřující metody vynikající volbou.

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

Při použití rozšiřující metody k rozšíření typu, jehož zdrojový kód neovládáte, spustíte riziko, že změna implementace typu způsobí, že vaše metoda rozšíření bude mít za následek přerušení.

Pokud implementujete metody rozšíření pro daný typ, mějte na paměti 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ů. Například pokud máte více statických tříd, které obsahují metody rozšíření v jednom oboru názvů s názvem Extensions , budou všechny převedeny do rozsahu podle using Extensions; direktivy.

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, pro kterou vlastníte zdrojový kód, postupujte podle pokynů rozhraní .NET pro správu verzí sestavení. Další informace naleznete v tématu Správa verzí sestavení.

Viz také