Megosztás a következőn keresztül:


Bővítménytagok (C# programozási útmutató)

A bővítménytagok lehetővé teszik, hogy új származtatott típus létrehozása, újrafordítás vagy az eredeti típus egyéb módosítása nélkül "vegyen fel" metódusokat a meglévő típusokba.

A C# 14-től kezdődően két szintaxist használ a bővítménymetelyek definiálásához. A C# 14 bevezeti a extension blokkokat, ahol több kiterjesztési tagot határozhat meg egy típushoz vagy egy típuspéldányhoz. A C# 14 előtt adja hozzá a this módosítót egy statikus metódus első paraméteréhez, amely jelzi, hogy a metódus a paramétertípus egy példányának tagjaként jelenik meg.

A bővítményblokkok több tagtípust támogatnak: metódusokat, tulajdonságokat és operátorokat. A bővítményblokkokkal a példánybővítményeket és a statikus bővítményeket is definiálhatja. A példánykiterjesztések kiterjesztik a típus egy példányát; a statikus bővítmények kiterjesztik a típust. A this módosítóval deklarált kiterjesztési metódusok formája támogatja a példánykiterjesztési metódusokat.

A bővítménymetelyek statikus metódusok, de úgy hívják őket, mintha a kiterjesztett típushoz tartozó példánymetodusok lennének. A C#, F# és Visual Basic nyelven írt ügyfélkódok esetében nincs nyilvánvaló különbség a bővítménymetódus meghívása és a típusban definiált metódusok között. A kiterjesztési módszerek mindkét formája ugyanarra az IL-ra (köztes nyelvre) van lefordítva. A bővítménytagok felhasználóinak nem kell tudniuk, hogy melyik szintaxist használták a bővítménymetelyek definiálásához.

A bővítmények leggyakoribb tagjai a LINQ standard lekérdezési operátorok, amelyek lekérdezési funkciókat adnak hozzá a meglévő System.Collections.IEnumerable és System.Collections.Generic.IEnumerable<T> a típusokhoz. A szabványos lekérdezési operátorok használatához először hozza őket hatókörbe egy using System.Linq irányelvvel. Ezután úgy tűnik, hogy minden implementált IEnumerable<T> típus rendelkezik példánymetelyekkel, például GroupBy, OrderBy, Averagestb. Ezeket az extra metódusokat az IntelliSense utasításkiegészítésében láthatja, ha a "pont" szöveget egy olyan típusú példány IEnumerable<T> után írja be, mint például List<T> vagy Array.

OrderBy példa

Az alábbi példa bemutatja, hogyan hívhatja meg a standard lekérdezési operátor OrderBy metódust egész számok tömbjén. A zárójelben szereplő kifejezés egy lambda kifejezés. Számos szabványos lekérdezési operátor paraméterként használja a lambda kifejezéseket. További információ: Lambda Expressions.

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

A bővítménymetódusok statikus metódusokként vannak definiálva, de a példánymetódus szintaxisával vannak meghívva. Az első paraméter határozza meg, hogy a metódus melyik típuson működik. A paraméter ezt a módosítót követi. A bővítménymetelyek csak akkor tartoznak hatókörbe, ha a névteret explicit módon importálja a forráskódba egy using direktívával.

Bővítménytagok deklarálása

A C# 14-től kezdve deklarálhatja a bővítményblokkokat. A bővítményblokk egy nem beágyazott, nemgenerikus statikus osztály blokkja, amely egy adott típushoz vagy egy ilyen példányhoz tartozó bővítménytagokat tartalmaz. Az alábbi példakód egy bővítményblokkot határoz meg a string típushoz. A bővítményblokk egy tagot tartalmaz: egy metódust, amely megszámolja a sztringben szereplő szavakat:

namespace CustomExtensionMembers;

public static class MyExtensions
{
    extension(string str)
    {
        public int WordCount() =>
            str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

A bővítményblokkok bővítménytagjai használhatnak nyitott vagy zárt általános generikusokat. A típusparaméterek korlátozásokat tartalmazhatnak:

public static class MyGenericExtensions
{
    extension<T>(IEnumerable<T> source)
        where T : IEquatable<T>
    {
        public IEnumerable<T> ValuesEqualTo(T threshold)
            => source.Where(x => x.Equals(threshold));
    }
}

A C# 14 előtt deklaráljon egy bővítménymetódust úgy, hogy hozzáadja a this módosítót az első paraméterhez:

namespace CustomExtensionMethods;

public static class MyExtensions
{
    public static int WordCount(this string str) =>
        str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}

A bővítmények mindkét formáját nem beágyazott, nemgenerikus statikus osztályban kell definiálni.

Egy alkalmazásból is meghívható a példánytagok elérésére szolgáló szintaxis használatával:

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

Bár a bővítménytagok új képességeket adnak hozzá egy meglévő típushoz, a bővítménytagok nem sértik a beágyazás elvét. A kiterjesztett típus összes tagjára vonatkozó hozzáférési deklarációk a bővítménytagokra vonatkoznak.

MyExtensions Az osztály és a WordCount metódus is static, és az összes többi static taghoz hasonlóan elérhető. A WordCount metódus az alábbiak szerint hívható meg, más static metódusokhoz hasonlóan:

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

Az előző C# kód a bővítményblokkra és this a bővítménytagok szintaxisára is vonatkozik. Az előző kód:

  • Deklarál és hozzárendel egy új string nevet s , amelynek értéke : "Hello Extension Methods".
  • Argumentumként megadott MyExtensions.WordCounthívásoks.

További információ: Egyéni bővítménymetódus implementálása és meghívása.

Általában sokkal gyakrabban hívhatja meg a bővítménytagokat, mint a implementálásukat. Mivel a bővítménytagok úgy vannak meghívva, mintha a kiterjesztett osztály tagjaiként lennének deklarálva, nincs szükség speciális ismeretekre az ügyfélkódból való használatukhoz. Ha engedélyezni szeretné a bővítménytagokat egy adott típushoz, csak adjon hozzá egy using irányelvet ahhoz a névtérhez, amelyben a metódusok definiálva vannak. A szabványos lekérdezési operátorok használatához például adja hozzá ezt az using irányelvet a kódhoz:

using System.Linq;

Kötésbővítmény-tagok fordításkor

A bővítménytagok használatával kiterjeszthet egy osztályt vagy felületet, de nem bírálhatja felül az osztályban definiált viselkedést. A kiterjesztés tagok, amelyeknek a neve és aláírása megegyezik egy interfész vagy osztály tagjaival, soha nem kerülnek meghívásra. Fordításkor a bővítménytagok mindig alacsonyabb prioritással rendelkeznek, mint a típusban definiált példány (vagy statikus) tagok. Más szóval, ha egy típusnak van egy metódusa, Process(int i)és egy azonos aláírású bővítménymetódussal rendelkezik, a fordító mindig a tagmetódushoz kötődik. Amikor a fordító taghívással találkozik, először egyezést keres a típus tagjai között. Ha nem talál egyezést, megkeresi a típushoz definiált bővítménytagokat. A függvény a megtalált első bővítménytaghoz kapcsolódik. Az alábbi példa azokat a szabályokat mutatja be, amelyeket a C#-fordító követ annak meghatározásához, hogy a típus egy példánytaghoz vagy egy bővítménytaghoz szeretne-e kötést kötni. A statikus osztály Extensions minden implementálható típushoz definiált bővítménytagokat IMyInterfacetartalmaz:

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)");
}

Az egyenértékű bővítmények a C# 14 bővítménytag szintaxisával deklarálhatók:

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)");
    }
}

Osztályok A, B, és C mind implementálják a felületet:

// 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)");
    }
}

A MethodB bővítménymetódus soha nem lesz meghívva, mert neve és aláírása pontosan egyezik az osztályok által már implementált metódusokkal. Ha a fordító nem talál egy egyező aláírással rendelkező példánymetódust, akkor egy megfelelő bővítménymetódushoz kapcsolódik, ha létezik ilyen.

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

Gyakori használati minták

Gyűjteményfunkciók

A múltban gyakori volt a "Gyűjteményosztályok" létrehozása, amely egy adott típus felületét implementálta System.Collections.Generic.IEnumerable<T> , és olyan funkciókat tartalmazott, amelyek az ilyen típusú gyűjteményeken jártak el. Bár nincs semmi baj az ilyen típusú gyűjteményobjektumok létrehozásával, ugyanez a funkció a bővítmény használatával érhető el a System.Collections.Generic.IEnumerable<T>. A bővítmények előnye, hogy lehetővé teszik a funkciók meghívását bármely olyan gyűjteményből, mint egy System.Array vagy System.Collections.Generic.List<T>, amelyen az System.Collections.Generic.IEnumerable<T> implementálva van. Erre az Int32 tömböt használó példa a cikk korábbi részében található.

Layer-Specific funkciók

Hagymaarchitektúra vagy más rétegzett alkalmazásterv használata esetén gyakran előfordul, hogy tartományi entitások vagy adatátviteli objektumok készlete van, amelyek az alkalmazáshatárokon keresztüli kommunikációra használhatók. Ezek az objektumok általában nem, vagy csak minimális funkciókat tartalmaznak, amelyek az alkalmazás minden rétegére vonatkoznak. A bővítménymetelyek az egyes alkalmazásrétegekre jellemző funkciók hozzáadására használhatók.

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}";
}

Az új bővítményblokk szintaxisával deklarálhat egy egyenértékű FullName tulajdonságot a C# 14-ben és újabb verzióiban:

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

Előre definiált típusok kiterjesztése

Ahelyett, hogy új objektumokat hoz létre újrahasználható funkciók létrehozásakor, gyakran kibővíthet egy meglévő típust, például .NET- vagy CLR-típust. Ha például nem használ kiterjesztési metódusokat, létrehozhat egy Engine vagy Query osztályt egy SQL Server-lekérdezés végrehajtásához, amelyet a kód több helyről is meghívhat. Ehelyett azonban bővítheti az System.Data.SqlClient.SqlConnection osztályt bővítménymetelyekkel, hogy a lekérdezést bárhonnan végrehajtsa, ahol van kapcsolata egy SQL Serverrel. Más példák lehetnek az osztály általános funkcióinak hozzáadására, az System.String objektum adatfeldolgozási képességeinek kiterjesztésére, valamint System.IO.Stream az System.Exception objektumok adott hibakezelési funkciókhoz való hozzáadására. Az ilyen típusú használati eseteket csak a képzelet és a jó érzék korlátozza.

Az előre definiált típusok kiterjesztése nehézkes lehet a típusok esetében struct , mert az érték adja át őket a metódusok számára. Ez azt jelenti, hogy a szerkezet minden módosítása a szerkezet egy példányán történik. Ezek a módosítások nem láthatók, miután a kiterjesztési metódus kilép. A módosító hozzáadható az ref első argumentumhoz, így bővítménymetódusként ref is használható. A ref kulcsszó szemantikai különbségek nélkül jelenhet meg a this kulcsszó előtt vagy után. A ref módosító hozzáadása azt jelzi, hogy az első argumentumot hivatkozással adja át a függvény. Ez a technika lehetővé teszi olyan bővítménymetelyek írását, amelyek megváltoztatják a kiterjesztett szerkezet állapotát (vegye figyelembe, hogy a magántagok nem érhetők el). A bővítménymetódus első paramétereként struct vagy egy bővítményblokk fogadójaként csak az strukturáláshoz korlátozott értéktípusok vagy általános típusok engedélyezettek (ezekről a szabályokról további információt a szóló cikkben talál). Az alábbi példa bemutatja, hogyan lehet bővítménymetódussal ref közvetlenül módosítani egy beépített típust anélkül, hogy újra kellene hozzárendelni az eredményt, vagy át kellene adni egy függvényen a ref kulcsszóval:

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++;
}

Az ezzel egyenértékű bővítményblokkok a következő kódban jelennek meg:

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++;
    }
}

Különböző bővítményblokkokra van szükség a fogadó érték szerinti és by-ref paraméter módjának megkülönböztetéséhez.

A következő példában látható, milyen különbséget okoz a ref alkalmazása a fogadóra.

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

Ugyanezt a technikát alkalmazhatja úgy is, hogy bővítménytagokat ad hozzá ref a felhasználó által definiált struktúratípusokhoz:

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
    }
}

Az előző minta a C# 14 bővítményblokkokkal is létrehozható:

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
        }
    }
}

Ezeket a bővítmény metódusokat az alábbi módon érheti el:

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

Általános irányelvek

Célszerűbb funkciókat hozzáadni egy objektum kódjának módosításával vagy egy új típus származtatásával, amikor ésszerű és lehetséges. A bővítménymetelyek kulcsfontosságúak az újrahasználható funkciók létrehozásához a .NET-ökoszisztémában. A bővítménytagok akkor előnyösek, ha az eredeti forrás nincs az Ön felügyelete alatt, ha egy származtatott objektum nem megfelelő vagy lehetetlen, vagy ha a funkció hatóköre korlátozott.

A származtatott típusokról további információt az Öröklés című témakörben talál.

Ha egy adott típushoz bővítménymetó módszereket implementál, jegyezze meg a következő pontokat:

  • A bővítménymetódus nem hívható meg, ha ugyanazzal az aláírással rendelkezik, mint a típusban definiált metódus.
  • A bővítménymetelyek a névtér szintjén kerülnek hatókörbe. Ha például több olyan statikus osztálya van, amely egyetlen névtérben Extensionstartalmaz bővítménymetszeteket, az irányelv az összeset hatókörbe sorolja using Extensions; .

Az ön által implementált osztálykönyvtár esetében ne használjon kiterjesztési metódusokat annak érdekében, hogy elkerülje a szerelvény verziószámának növelését. Ha jelentős funkciókat szeretne hozzáadni egy olyan könyvtárhoz, amelynek Ön a forráskód tulajdonosa, kövesse a .NET szerelvény-verziózásra vonatkozó irányelveket. További információ: Szerelvény verziószámozása.

Lásd még