Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Med tilläggsmedlemmar kan du "lägga till" metoder i befintliga typer utan att skapa en ny härledd typ, kompilera om eller på annat sätt ändra den ursprungliga typen.
Från och med C# 14 finns det två syntaxer som du använder för att definiera tilläggsmetoder. C# 14 lägger till extension block, där du definierar flera tilläggsmedlemmar för en typ eller en instans av en typ. Innan C# 14 lägger du till this modifieraren i den första parametern för en statisk metod för att indikera att metoden visas som medlem i en instans av parametertypen.
Tilläggsblock stöder flera medlemstyper: metoder, egenskaper och operatorer. Med tilläggsblock kan du definiera både instanstillägg och statiska tillägg. Instanstillägg utökar en instans av typen; statiska tillägg utökar själva typen. Formen av tilläggsmetoder som deklarerats med this-modifieraren stöder instanstilläggsmetoder.
Tilläggsmetoder är statiska metoder, men de anropas som om de vore instansmetoder för den utökade typen. För klientkod skriven i C#, F# och Visual Basic finns det ingen uppenbar skillnad mellan att anropa en tilläggsmetod och de metoder som definierats i en typ. Båda formerna av tilläggsmetoder kompileras till samma IL (mellanliggande språk). Användare av tilläggsmedlemmar behöver inte veta vilken syntax som användes för att definiera tilläggsmetoder.
De vanligaste tilläggsmedlemmarna är LINQ-standardfrågeoperatorer som lägger till frågefunktioner till befintliga System.Collections.IEnumerable och System.Collections.Generic.IEnumerable<T> typer. För att använda standardfrågeoperatorerna, inkludera dem först i omfånget med hjälp av ett using System.Linq-direktiv. Sedan verkar alla typer som implementerar IEnumerable<T> ha instansmetoder som GroupBy, OrderBy, Average, och så vidare. Du kan se dessa extra metoder i IntelliSense-funktionen för satskomplettering när du skriver "dot" efter en typinstans som IEnumerable<T>, List<T> eller Array.
Exempel på OrderBy
I följande exempel visas hur du anropar standardmetoden för frågeoperatorer OrderBy i en matris med heltal. Uttrycket i parenteser är ett lambda-uttryck. Många vanliga frågeoperatorer använder lambda-uttryck som parametrar. Mer information finns i Lambda-uttryck.
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
Tilläggsmetoder definieras som statiska metoder men anropas med hjälp av instansmetodsyntax. Deras första parameter anger vilken typ av metod som används. Parametern följer den här modifieraren. Tilläggsmetoder är endast tillgängliga när du uttryckligen importerar namnområdet till källkoden med direktivet using.
Deklarera tilläggsmedlemmar
Från och med C# 14 kan du deklarera tilläggsblock. Ett tilläggsblock är ett block i en icke-kapslad, icke-generisk, statisk klass som innehåller tilläggsmedlemmar för en typ eller en instans av den typen. I följande kodexempel definieras ett tilläggsblock för string typen. Tilläggsblocket innehåller en medlem: en metod som räknar orden i strängen:
namespace CustomExtensionMembers;
public static class MyExtensions
{
    extension(string str)
    {
        public int WordCount() =>
            str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
    }
}
Innan C# 14 deklarerar du en tilläggsmetod genom att lägga till this modifieraren i den första parametern:
namespace CustomExtensionMethods;
public static class MyExtensions
{
    public static int WordCount(this string str) =>
        str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Båda formerna av tillägg måste definieras i en icke-kapslad, icke-generisk statisk klass.
Och det kan anropas från ett program med hjälp av syntaxen för åtkomst till instansmedlemmar:
string s = "Hello Extension Methods";
int i = s.WordCount();
Tilläggsmedlemmar lägger till nya funktioner i en befintlig typ, men tilläggsmedlemmar bryter inte mot inkapslingsprincipen. Åtkomstdeklarationerna för alla medlemmar av den utökade typen gäller för tilläggsmedlemmar.
Både MyExtensions klassen och WordCount metoden är static, och de kan nås som alla andra static medlemmar. Metoden WordCount kan anropas som andra static metoder på följande sätt:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Den föregående C#-koden gäller för både tilläggsblocket och this syntaxen för tilläggsmedlemmar. Föregående kod:
- Deklarerar och tilldelar ett nytt stringnamnsmed värdet"Hello Extension Methods".
- Anropar det angivna argumentet MyExtensions.WordCountmeds.
Mer information finns i Implementera och anropa en anpassad tilläggsmetod.
I allmänhet kallar du förmodligen tilläggsmedlemmar mycket oftare än du implementerar dem. Eftersom tilläggsmedlemmar anropas som om de deklareras som medlemmar i den utökade klassen krävs ingen särskild kunskap för att använda dem från klientkoden. Om du vill aktivera tilläggsmedlemmar för en viss typ lägger du bara till ett using direktiv för namnområdet där metoderna definieras. Om du till exempel vill använda standardfrågeoperatorerna lägger du till det här using direktivet i koden:
using System.Linq;
Bindning av tilläggsmedlemmar vid kompileringstidpunkt
Du kan använda tilläggsmedlemmar för att utöka en klass eller ett gränssnitt, men inte för att åsidosätta beteende som definierats i en klass. En tilläggsmedlem med samma namn och signatur som ett gränssnitt eller klassmedlemmar anropas aldrig. Vid kompileringstiden har tilläggsmedlemmar alltid lägre prioritet än instansmedlemmar (eller statiska) som definierats i själva typen. Med andra ord, om en typ har en metod med namnet Process(int i), och du har en tilläggsmetod med samma signatur, binder kompilatorn alltid till medlemsmetoden. När kompilatorn stöter på ett medlemsanrop letar den först efter en matchning i typens medlemmar. Om ingen matchning hittas söker den efter eventuella tilläggsmedlemmar som har definierats för typen. Den binder till den första tilläggsmedlemmen som den hittar. I följande exempel visas de regler som C#-kompilatorn följer för att avgöra om en instansmedlem ska bindas till typen eller till en tilläggsmedlem. Den statiska klassen Extensions innehåller tilläggsmedlemmar som definierats för alla typer som implementerar 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)");
}
Motsvarande tillägg kan deklareras med hjälp av medlemssyntaxen för C# 14-tillägget:
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)");
    }
}
Klasser A, Boch C alla implementerar gränssnittet:
// 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)");
    }
}
Tilläggsmetoden MethodB anropas aldrig eftersom dess namn och signatur exakt matchar metoder som redan implementerats av klasserna. När kompilatorn inte kan hitta en instansmetod med en matchande signatur binder den till en matchande tilläggsmetod om det finns en sådan.
// 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()
 */
Vanliga användningsmönster
Samlingsfunktionalitet
Tidigare var det vanligt att skapa "Samlingsklasser" som implementerade System.Collections.Generic.IEnumerable<T> gränssnittet för en viss typ och innehöll funktioner som agerade på samlingar av den typen. Även om det inte är något fel med att skapa den här typen av samlingsobjekt kan samma funktioner uppnås med hjälp av ett tillägg på System.Collections.Generic.IEnumerable<T>. Tillägg har fördelen att möjliggöra att funktionaliteten kan anropas från alla samlingar, såsom en System.Array eller System.Collections.Generic.List<T> som implementerar System.Collections.Generic.IEnumerable<T> för den typen. Ett exempel på detta med hjälp av en matris med Int32 finns tidigare i den här artikeln.
Layer-Specific funktioner
När du använder en lökarkitektur eller annan programdesign i flera lager är det vanligt att ha en uppsättning domänentiteter eller dataöverföringsobjekt som kan användas för att kommunicera över programgränser. Dessa objekt innehåller vanligtvis inga funktioner, eller bara minimala funktioner som gäller för alla skikt i programmet. Tilläggsmetoder kan användas för att lägga till funktioner som är specifika för varje programskikt.
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}";
}
Du kan deklarera en motsvarande FullName egenskap i C# 14 och senare med hjälp av den nya syntaxen för tilläggsblock:
static class DomainEntityExtensions
{
    extension(DomainEntity value)
    {
        string FullName => $"{value.FirstName} {value.LastName}";
    }
}
Utöka fördefinierade typer
I stället för att skapa nya objekt när återanvändbara funktioner behöver skapas kan du ofta utöka en befintlig typ, till exempel en .NET- eller CLR-typ. Om du till exempel inte använder tilläggsmetoder kan du skapa en Engine eller Query -klass för att utföra arbetet med att köra en fråga på en SQL Server som kan anropas från flera platser i koden. Du kan dock utöka System.Data.SqlClient.SqlConnection klassen med hjälp av tilläggsmetoder för att utföra frågan var du än har en anslutning till en SQL Server. Andra exempel kan vara att lägga till vanliga funktioner i System.String klassen, utöka databehandlingsfunktionerna för System.IO.Stream objektet och System.Exception objekt för specifika funktioner för felhantering. Dessa typer av användningsfall begränsas endast av din fantasi och ditt goda förnuft.
Det kan vara svårt att utöka fördefinierade typer med struct typer eftersom de skickas som värdetyper till metoder. Det innebär att alla ändringar i struct görs på en kopia av struct. Ändringarna visas inte när tilläggsmetoden avslutas. Du kan lägga till ref modifieraren i det första argumentet, vilket gör det till en ref tilläggsmetod. Nyckelordet ref kan visas före eller efter nyckelordet this utan några semantiska skillnader. 
              ref Att lägga till modifieraren anger att det första argumentet skickas med referens. Med den här tekniken kan du skriva tilläggsmetoder som ändrar tillståndet för den struktur som utvidgas (observera att privata medlemmar inte är tillgängliga). Endast värdetyper eller generiska typer som är begränsade till struct (Mer information om dessa regler finns i artikeln om villkoretstruct) tillåts som den första parametern för en ref tilläggsmetod eller som mottagare av ett tilläggsblock. I följande exempel visas hur du använder en ref tilläggsmetod för att direkt ändra en inbyggd typ utan att du behöver tilldela om resultatet eller skicka det genom en funktion med nyckelordet ref :
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++;
}
Motsvarande tilläggsblock visas i följande kod:
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++;
    }
}
Olika tilläggsblock krävs för att särskilja bivärdes- och by-ref-parameterlägen för mottagaren.
Du kan se vilken skillnad det gör när du applicerar ref på mottagaren i följande exempel:
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
Du kan använda samma teknik genom att lägga till ref tilläggsmedlemmar i användardefinierade structtyper:
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
    }
}
Föregående exempel kan också skapas med hjälp av tilläggsblock i 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
        }
    }
}
Du kan komma åt dessa tilläggsmetoder på följande sätt:
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
Allmänna riktlinjer
Det är bättre att lägga till funktioner genom att ändra ett objekts kod eller härleda en ny typ när det är rimligt och möjligt att göra det. Tilläggsmetoder är ett viktigt alternativ för att skapa återanvändbara funktioner i hela .NET-ekosystemet. Tilläggsmedlemmar är att föredra när den ursprungliga källan inte står under din kontroll, när ett härlett objekt är olämpligt eller omöjligt eller när funktionen har begränsat omfång.
Mer information om härledda typer finns i Arv.
Kom ihåg följande om du implementerar tilläggsmetoder för en viss typ:
- En tilläggsmetod anropas inte om den har samma signatur som en metod som definierats i typen.
- Tilläggsmetoder tas med i omfånget på namnområdesnivå. Om du till exempel har flera statiska klasser som innehåller tilläggsmetoder i ett enda namnområde med namnet Extensionskommer alla att omfattas avusing Extensions;direktivet.
För ett klassbibliotek som du implementerade bör du inte använda tilläggsmetoder för att undvika att öka versionsnumret för en sammansättning. Om du vill lägga till betydande funktionalitet i ett bibliotek vars källkod du äger, bör du följa .NET-riktlinjerna för versionshantering av assembly. Mer information finns i Versionshantering för sammansättning.