Aracılığıyla paylaş


Uzantı üyeleri (C# Programlama Kılavuzu)

Uzantı üyeleri, yeni türetilmiş bir tür oluşturmadan, yeniden derlemeden veya özgün türü başka bir şekilde değiştirmeden mevcut türlere yöntemleri "eklemenize" olanak tanır.

C# 14'le başlayarak, uzantı yöntemlerini tanımlamak için kullandığınız iki söz dizimi vardır. C# 14, bir tür veya bir tür örneği için birden çok uzantı üyesi tanımladığınız kapsayıcılar ekler extension . C# 14'ün öncesinde, yöntemin this parametre türünün bir örneğinin üyesi olarak göründüğünü belirtmek için değiştiriciyi statik yöntemin ilk parametresine eklersiniz.

Uzantı yöntemleri statik yöntemlerdir, ancak genişletilmiş türdeki örnek yöntemleriymiş gibi çağrılır. C#, F# ve Visual Basic ile yazılmış istemci kodu için, uzantı yöntemini çağırma ile bir tür içinde tanımlanan yöntemler arasında belirgin bir fark yoktur. Her iki uzantı yöntemi biçimi de aynı IL(Ara Dil) için derlenir. Uzantı üyelerinin tüketicilerinin uzantı yöntemlerini tanımlamak için hangi söz diziminin kullanıldığını bilmesi gerekmez.

En yaygın uzantı üyeleri, var olan System.Collections.IEnumerable ve System.Collections.Generic.IEnumerable<T> türlerine sorgu işlevselliği ekleyen LINQ standart sorgu işleçleridir. Standart sorgu işleçlerini kullanmak için önce bir using System.Linq yönergesi ile bunları kapsama alın. Ardından IEnumerable<T> uygulayan herhangi bir türün GroupBy, OrderBy, Averagevb. örnek yöntemlerine sahip olduğu görülür. IntelliSense ifade tamamlama işlemi sırasında, IEnumerable<T> veya List<T> gibi bir Array türünün örneğinden sonra "nokta" yazdığınızda bu ek yöntemleri görebilirsiniz.

OrderBy örneği

Aşağıdaki örnek, bir tamsayı dizisinde standart sorgu işleci OrderBy yönteminin nasıl çağrılacağını göstermektedir. Parantez içindeki ifade bir lambda ifadesidir. Birçok standart sorgu işleci lambda ifadelerini parametre olarak alır. Daha fazla bilgi için bkz. Lambda İfadeleri.

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

Uzantı yöntemleri statik yöntemler olarak tanımlanır, ancak örnek yöntemi söz dizimi kullanılarak çağrılır. İlk parametreleri yöntemin hangi tür üzerinde çalıştığını belirtir. Parametre bu değiştiriciyi izler. Uzantı yöntemleri yalnızca ad alanını bir using yönergesi ile kaynak kodunuz içine açıkça içeri aktardığınızda kapsam dahilindedir.

Uzantı üyelerini bildirme

C# 14'le başlayarak uzantı bloklarını bildirebilirsiniz. İç içe olmayan, jenerik olmayan, statik bir sınıfta, bir türün veya bu türün bir örneğinin uzantı üyelerini içeren bir blok olan uzantı bloğu. Aşağıdaki kod örneği, türü için string bir uzantı bloğu tanımlar. Uzantı bloğu bir üye içerir: dizedeki sözcükleri sayan bir yöntem:

namespace CustomExtensionMembers;

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

C# 14'e geçmeden önce, değiştiriciyi this ilk parametreye ekleyerek bir uzantı yöntemi bildirirsiniz:

namespace CustomExtensionMethods;

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

Her iki uzantı biçimi de iç içe olmayan,generik olmayan bir statik sınıf içinde tanımlanmalıdır.

Bir uygulamadan, örnek üye erişim söz dizimi kullanılarak çağrılabilir.

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

Uzantı üyeleri mevcut bir türe yeni özellikler eklerken, uzantı üyeleri kapsülleme ilkesini ihlal etmemektedir. Genişletilmiş türün tüm üyeleri için erişim bildirimleri uzantı üyeleri için geçerlidir.

Hem MyExtensions sınıfı hem de WordCount yöntemi static'dir ve diğer tüm static üyeleri gibi erişilebilir. WordCount yöntemi, aşağıdaki gibi diğer static yöntemleri gibi çağrılabilir:

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

Yukarıdaki C# kodu, uzantı üyeleri için hem uzantı bloğu this hem de söz dizimi için geçerlidir. Önceki kod:

  • Yeni bir string'ı, s adıyla ve "Hello Extension Methods"değerine sahip olarak bildirir ve atar.
  • Çağrı MyExtensions.WordCount, verilen bağımsız değişken s'i çağırır.

Daha fazla bilgi için bkz. Özel uzantı yöntemini uygulama ve çağırma.

Genel olarak, uzantı üyelerini uygulamaktan çok daha sık çağırırsınız. Uzantı üyeleri genişletilmiş sınıfın üyesi olarak bildiriliyormuş gibi çağrıldığından, bunları istemci kodundan kullanmak için özel bir bilgi gerekmez. Belirli bir tür için uzantı üyelerini etkinleştirmek için, yöntemlerin tanımlandığı ad alanı için bir using yönerge eklemeniz gerekir. Örneğin, standart sorgu işleçlerini kullanmak için bu using yönergesini kodunuza ekleyin:

using System.Linq;

Derleme zamanında uzantı üyelerini bağlama

Uzantı üyelerini bir sınıfı veya arabirimi genişletmek için kullanabilirsiniz, ancak bir sınıfta tanımlanan davranışı geçersiz kılmak için kullanamazsınız. Arabirim veya sınıf üyeleriyle aynı ada ve imzaya sahip bir uzantı üyesi hiçbir zaman çağrılmaz. Derleme zamanında uzantı üyeleri her zaman türün kendisinde tanımlanan örnek (veya statik) üyelerden daha düşük önceliğe sahiptir. Başka bir deyişle, bir türün adlı Process(int i)bir yöntemi varsa ve aynı imzaya sahip bir uzantı yönteminiz varsa, derleyici her zaman üye yöntemine bağlanır. Derleyici bir üye çağrısıyla karşılaştığında, önce türün üyelerinde bir eşleşme arar. Eşleşme bulunamadığında, tür için tanımlı herhangi bir uzantı üyesini arar. Bulduğu ilk uzantı üyesine bağlanır. Aşağıdaki örnek, C# derleyicisinin türdeki bir örnek üyesine mi yoksa uzantı üyesine mi bağlanacağını belirlerken izlediği kuralları gösterir. Statik sınıf Extensions, IMyInterface uygulayan herhangi bir tür için tanımlanan uzantı üyelerini içerir.

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

Eşdeğer uzantılar, C# 14 uzantı üyesi söz dizimi kullanılarak bildirilebilir:

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

A, B ve C sınıflarının tümü arabirimi uygular:

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

adı ve imzası sınıflar tarafından zaten uygulanan yöntemlerle tam olarak eşleştiğinden MethodB uzantısı yöntemi hiçbir zaman çağrılmaz. Derleyici eşleşen imzaya sahip bir örnek yöntemi bulamadıysa, varsa eşleşen bir uzantı yöntemine bağlanır.

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

Yaygın kullanım kalıpları

Koleksiyon İşlevselliği

Geçmişte, belirli bir tür için System.Collections.Generic.IEnumerable<T> arabirimini uygulayan ve bu türdeki koleksiyonlar üzerinde hareket eden işlevler içeren "Koleksiyon Sınıfları" oluşturmak yaygındı. Bu tür bir koleksiyon nesnesi oluşturmada bir sorun olmasa da, System.Collections.Generic.IEnumerable<T>üzerinde bir uzantı kullanılarak aynı işlev elde edilebilir. Uzantılar, işlevselliğin bu türdeki System.Array uygulayan bir System.Collections.Generic.List<T> veya System.Collections.Generic.IEnumerable<T> gibi herhangi bir koleksiyondan çağrılmasına izin verme avantajına sahiptir. Int32 Dizisi kullanılarak bunun bir örneği, bu makalenin önceki bölümlerinde bulunabilir.

Layer-Specific İşlevsellik

Bir Soğan Mimarisi veya diğer katmanlı uygulama tasarımı kullanılırken, uygulama sınırları arasında iletişim kurmak için kullanılabilecek bir etki alanı varlıkları veya Veri Aktarım Nesneleri kümesine sahip olmak yaygın bir durumdur. Bu nesneler genellikle hiçbir işlev veya uygulamanın tüm katmanları için geçerli olan en düşük işlevsellik içerir. Uzantı yöntemleri, her uygulama katmanına özgü işlevler eklemek için kullanılabilir.

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

Yeni uzantı bloğu söz dizimini kullanarak C# 14 ve sonraki sürümlerde eşdeğer FullName bir özellik bildirebilirsiniz:

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

Önceden Tanımlanmış Türleri Genişletme

Yeniden kullanılabilir işlevselliğin oluşturulması gerektiğinde yeni nesneler oluşturmak yerine, genellikle .NET veya CLR türü gibi mevcut bir türü genişletebilirsiniz. Örneğin, uzantı yöntemlerini kullanmıyorsanız, kodumuzda birden çok yerden çağrılabilecek bir SQL Server'da sorgu yürütme işini yapmak için bir veya Engine sınıfı oluşturabilirsinizQuery. Bununla birlikte, sql server ile bağlantınız olan her yerden bu sorguyu gerçekleştirmek için uzantı yöntemlerini kullanarak sınıfını genişletebilirsiniz System.Data.SqlClient.SqlConnection . Diğer örnekler, System.String sınıfına ortak işlevler eklemek, System.IO.Stream nesnesinin veri işleme özelliklerini genişletmek ve belirli hata işleme işlevselliği için nesneleri System.Exception olabilir. Bu tür kullanım örnekleri yalnızca hayal gücünüz ve sağduyunuzla sınırlıdır.

Önceden tanımlanmış türlerin, struct türleriyle genişletilmesi, bunlar yöntemlere değer olarak geçirildiği için zor olabilir. Bu, yapıda yapılan tüm değişikliklerin yapının bir kopyasında yapıldığı anlamına gelir. Uzantı yöntemi çıktıktan sonra bu değişiklikler görünmez. İlk bağımsız değişkene ref değiştiricisini ekleyerek onu ref genişletme metodu haline getirebilirsiniz. ref anahtar sözcüğü, herhangi bir anlam farkı olmadan this anahtar sözcüğünden önce veya sonra görünebilir. ref değiştiricisinin eklenmesi, ilk parametrenin başvuruyla geçirildiğini gösterir. Bu teknik, genişletilmiş yapının durumunu değiştiren uzantı yöntemleri yazmanızı sağlar (özel üyelerin erişilebilir olmadığını unutmayın). Yalnızca yapıyla kısıtlanmış değer türlerine veya genel türlere (Bu kurallar hakkında daha fazla bilgi için bkz. kısıtlamaya bakınstruct) uzantı ref yönteminin ilk parametresi veya uzantı bloğunun alıcısı olarak izin verilir. Aşağıdaki örnekte, sonucu yeniden atamaya gerek kalmadan yerleşik bir türü doğrudan değiştirmek veya ref anahtar sözcüğüyle bir işlevden geçirmek için ref uzantısı yönteminin nasıl kullanılacağı gösterilmektedir:

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

Eşdeğer uzantı blokları aşağıdaki kodda gösterilir:

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

Alıcı için değere ve başvuruya göre parametre modlarını ayırt etmek için farklı uzantı blokları gerekir.

Alıcıya uygulanan ref farkı aşağıdaki örnekte görebilirsiniz:

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

Kullanıcı tanımlı yapı türlerine uzantı üyeleri ekleyerek ref aynı tekniği uygulayabilirsiniz:

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

Yukarıdaki örnek, C# 14'teki uzantı blokları kullanılarak da oluşturulabilir:

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

Bu uzantı yöntemlerine aşağıdaki gibi erişebilirsiniz:

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

Genel Yönergeler

Bir nesnenin kodunu değiştirerek veya makul ve mümkün olduğunda yeni bir tür türeterek işlevsellik eklemek tercih edilir. Uzantı yöntemleri, .NET ekosistemi genelinde yeniden kullanılabilir işlevsellik oluşturmak için kritik bir seçenektir. Uzantı üyeleri, özgün kaynak denetiminiz altında olmadığında, türetilmiş bir nesne uygun olmadığında veya imkansız olduğunda ya da işlevselliğin sınırlı kapsamı olduğunda tercih edilir.

Türetilmiş türler hakkında daha fazla bilgi için bkz Devralma.

Belirli bir tür için uzantı yöntemleri uygularsanız aşağıdaki noktaları unutmayın:

  • Uzantı yöntemi, türünde tanımlanan bir yöntemle aynı imzaya sahipse çağrılmaz.
  • Uzantı yöntemleri, ad alanı düzeyinde kapsama getirilir. Örneğin, adlı Extensionstek bir ad alanında uzantı yöntemleri içeren birden çok statik sınıfınız varsa, bunların tümü yönergesi using Extensions; tarafından kapsama alınır.

Uyguladığınız bir sınıf kitaplığı için, derlemenin sürüm numarasını artırmaktan kaçınmak için uzantı yöntemlerini kullanmamalısınız. Kaynak kodun sahibi olduğunuz bir kitaplığa önemli işlevler eklemek istiyorsanız, derleme sürümü oluşturma için .NET yönergelerini izleyin. Daha fazla bilgi için bkz. Derleme Sürümleme .

Ayrıca bkz.