Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Členové rozšíření umožňují přidat metody do existujících typů bez vytvoření nového odvozeného typu, rekompiace nebo jiné úpravy původního typu.
Počínaje jazykem C# 14 existují dvě syntaxe, které slouží k definování metod rozšíření. C# 14 přidává extension
kontejnery, kde definujete více členů rozšíření pro typ nebo instanci typu. Před jazykem C# 14 přidáte this
modifikátor do prvního parametru statické metody, který indikuje, že metoda se zobrazí jako člen instance typu parametru.
Rozšiřující metody jsou statické metody, ale volají se, jako by se jednalo o metody instance rozšířeného 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. Obě formy rozšiřujících metod se kompilují do stejného jazyka IL (Intermediate Language). Uživatelé metod rozšíření nemusí vědět, která syntaxe byla použita k definování metod rozšíření.
Nejběžnějšími členy 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, který implementuje IEnumerable<T>, má metody instance, jako GroupBy, OrderBy, Average a tak dále. Tyto další metody můžete zobrazit v doplňování příkazů IntelliSense, když zadáte tečku za instanci typu, například IEnumerable<T>, List<T> nebo Array.
Příklad OrderBy
Následující příklad ukazuje, jak volat standardní metodu operátoru OrderBy
dotazu na pole 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. Další informace najdete v tématu Výrazy lambda.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
Rozšiřující metody jsou definované jako statické metody, ale volají se pomocí syntaxe metody instance. První parametr určuje typ, se kterým metoda pracuje. Parametr se řídí tímto modifikátorem. Metody rozšíření jsou dostupné pouze v případech, kdy explicitně importujete obor názvů do zdrojového kódu pomocí direktivy using
.
Deklarujte členy rozšíření
Počínaje C# 14 můžete deklarovat bloky rozšíření. Blok rozšíření je blok v nenávrhové, negenerické statické třídě, která obsahuje členy rozšíření pro určitý typ nebo instanci tohoto typu. Následující příklad kódu definuje blok rozšíření pro string
typ. Blok rozšíření obsahuje jeden člen: metoda, která spočítá slova v řetězci:
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Před C# 14 deklarujete rozšiřující metodu přidáním this
modifikátoru do prvního parametru:
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Obě formy rozšíření musí být definovány uvnitř nezasazené, negenerické statické třídy.
A lze ji volat z aplikace pomocí syntaxe pro přístup ke členům instance:
string s = "Hello Extension Methods";
int i = s.WordCount();
Zatímco členové rozšíření přidávají do existujícího typu nové funkce, členové rozšíření neporušují zásadu zapouzdření. Deklarace přístupu pro všechny členy rozšířeného typu platí pro členy rozšíření.
Třída MyExtensions
i metoda WordCount
jsou static
a lze k nim přistupovat stejně jako ke všem ostatním static
členům. Metodu WordCount
lze vyvolat stejně jako jiné static
metody následujícím způsobem:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Předchozí kód v C# se vztahuje jak na rozšiřující blok, tak na syntaxi this
pro členy rozšíření. Předchozí kód:
- Deklaruje a přiřadí nový
string
pojmenovanýs
s hodnotou"Hello Extension Methods"
. - Volá
MyExtensions.WordCount
, daný arguments
.
Další informace naleznete v tématu Jak implementovat a volat vlastní rozšiřující metodu.
Obecně platí, že pravděpodobně používáte členy rozšiřujících modulů mnohem častěji, než je implementujete. Vzhledem k tomu, že se členové rozšíření volají, jako by byly deklarovány jako členové rozšířené třídy, není při jejich používání z klientského kódu potřeba žádných zvláštních znalostí. Chcete-li povolit členy rozšíření pro určitý typ, stačí přidat direktivu using
pro obor názvů, ve kterém jsou definovány metody. Pokud chcete například použít standardní operátory dotazu, přidejte do kódu tuto using
direktivu:
using System.Linq;
Vázání členů rozšíření v době kompilace
Členy rozšíření můžete použít k rozšíření třídy nebo rozhraní, ale ne k přepsání chování definovaného ve třídě. Člen rozšíření se stejným názvem a podpisem jako rozhraní nebo člen třídy se nikdy nevyvolá. V době kompilace mají členové rozšíření vždy nižší prioritu než členy instance (nebo statické) definované v samotném typu. Jinými slovy, pokud typ má pojmenovanou Process(int i)
metodu a máte rozšiřující metodu se stejným podpisem, kompilátor vždy vytvoří vazbu na metodu člena. Když kompilátor narazí na volání člena, nejprve vyhledá shodu ve členech typu. Pokud se nenajde žádná shoda, vyhledá všechny členy rozšíření definované pro daný typ. Vytvoří vazbu k prvnímu členu rozšíření, který najde. Následující příklad ukazuje pravidla, která kompilátor jazyka C# následuje při určování, zda se má svázat s členem instance typu, nebo s členem rozšíření. Statická třída Extensions
obsahuje členy rozšíření definované pro libovolný typ, který implementuje IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// 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)");
}
Ekvivalentní rozšíření lze deklarovat pomocí syntaxe člena rozšíření C# 14:
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(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 void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Třídy A
, B
, a C
všechny implementují rozhraní:
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
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)");
}
}
Rozšiřující MethodB
metoda se nikdy nevolá, protože jeho název a podpis přesně odpovídají metodám, které již byly implementovány třídami. Pokud kompilátor nemůže najít metodu instance s odpovídajícím podpisem, vytvoří vazbu s odpovídající rozšiřující metodou, pokud existuje.
// 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í
Funkčnost kolekce
V minulosti bylo běžné vytvořit "Třídu kolekcí", která implementovala System.Collections.Generic.IEnumerable<T> rozhraní pro daný typ a obsahovala funkce, které působily na kolekcích tohoto typu. I když není nic špatného při vytváření tohoto typu objektu kolekce, stejné funkce lze dosáhnout pomocí rozšíření na objektu System.Collections.Generic.IEnumerable<T>. Rozšíření mají výhodu v tom, že umožňují volání funkcí z jakékoli kolekce, například z kolekce System.Array nebo System.Collections.Generic.List<T>, které implementují System.Collections.Generic.IEnumerable<T> na daném typu. Příklad použití pole Int32 najdete dříve v tomto článku.
Funkcionalita 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é je možné použít ke komunikaci přes hranice 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í specifických pro každou aplikační vrstvu.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Pomocí nové syntaxe bloku rozšíření můžete deklarovat ekvivalentní FullName
vlastnost v jazyce C# 14 a novější:
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{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ůžete často rozšířit existující typ, například typ .NET nebo CLR. Pokud například nepoužíváte rozšiřující metody, můžete vytvořit Engine
nebo Query
třídu, která provede provádění dotazu na SQL Serveru, který může být volána z několika míst v našem kódu. Místo toho můžete třídu rozšířit System.Data.SqlClient.SqlConnection pomocí rozšiřujících metod k provedení dotazu odkudkoliv, kde máte připojení k SQL Serveru. Dalšími příklady mohou být přidání běžných funkcí do System.String třídy, rozšíření možností zpracování dat objektu System.IO.Stream a System.Exception objektů pro konkrétní funkce zpracování chyb. Tyto typy případů použití jsou omezeny pouze vaší představivostí a dobrým smyslem.
Rozšíření předdefinovaných typů může být obtížné u struct
typů, protože jsou předány hodnotami metodám. To znamená, že všechny změny struktury se provádějí v kopii struktury. Tyto změny se po ukončení metody rozšíření nezobrazí. Modifikátor můžete přidat ref
do prvního argumentu, aby se jedná o rozšiřující metodu ref
. Klíčové ref
slovo se může zobrazit před klíčovým slovem nebo za ho this
bez jakýchkoli sémantických rozdílů. Přidání modifikátoru ref
označuje, že první argument je předán odkazem. Tato technika umožňuje psát rozšiřující metody, které mění stav rozšířené struktury (všimněte si, že soukromé členy nejsou přístupné). Jako první parametr metody rozšíření nebo jako příjemce rozšiřujícího struct
bloku jsou povoleny pouze typy hodnot nebo obecné typy omezené na strukturu (Další informace o těchto pravidlech najdete v tématu ). Následující příklad ukazuje, jak pomocí ref
rozšiřující metody přímo upravit předdefinovaný typ, aniž by bylo nutné znovu přiřadit výsledek nebo předat funkci pomocí klíčového ref
slova:
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
Ekvivalentní bloky rozšíření se zobrazují v následujícím kódu:
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Různé rozšiřující bloky jsou vyžadovány k rozlišení režimů parametrů podle hodnoty a podle odkazu pro příjemce.
Rozdíl, který má použití ref
na příjemce v následujícím příkladu, můžete vidět:
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
Stejnou techniku můžete použít přidáním ref
členů rozšíření do uživatelsky definovaných typů struktur:
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
Předchozí ukázku je možné vytvořit také pomocí bloků rozšíření v jazyce C# 14:
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
K těmto metodám rozšíření se dostanete následujícím způsobem:
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Obecné pokyny
Lepší je 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í jsou zásadní možností pro vytváření opakovaně použitelných funkcí v ekosystému .NET. Preferovaní členové rozšíření se používají, když původní zdroj není pod vaší kontrolou, když je odvozený objekt nevhodný nebo nemožný, nebo když má funkce omezenou rozsah.
Další informace o odvozených typech naleznete v tématu Dědičnost.
Pokud implementujete rozšiřující metody pro daný typ, mějte na paměti následující body:
- Metoda rozšíření není volána, pokud má stejný podpis jako metoda definovaná v typu.
- Metody rozšíření jsou přeneseny do oboru názvů. Pokud máte například více statických tříd, které obsahují rozšiřující metody v jednom oboru názvů s názvem
Extensions
, všechny z nich jsou přeneseny do oboru direktivyusing Extensions;
.
Pro knihovnu tříd, kterou jste implementovali, byste neměli používat rozšiřující metody, abyste se vyhnuli zvýšení počtu verzí sestavení. 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 naleznete v tématu Verze sestavení.
Viz také
- Ukázky paralelního programování (mnoho příkladů demonstruje rozšiřující metody)
- Výrazy lambda
- Přehled standardních operátorů dotazů
- Pravidla převodu pro parametry instance a jejich dopad
- Interoperabilita metod rozšíření mezi jazyky
- Rozšiřující metody a karryované delegáty
- Vazba metody rozšíření a hlášení chyb