Uzantı Metotları (C# Programlama Kılavuzu)

Uzantı yöntemleri, yeni türetilmiş bir tür oluşturmadan, yeniden derlemeden ya da özgün türü değiştirmeden yöntemler "eklemenizi" sağlar. 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.

En yaygın uzantı yöntemleri, 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önergeyle bunları kapsama alın. Ardından, uygulayan IEnumerable<T> herhangi bir türün , OrderBy, ve Averagegibi GroupByörnek yöntemlerine sahip olduğu görünür. veya Arraygibi List<T> bir IEnumerable<T> türün örneğinden sonra "nokta" yazdığınızda IntelliSense deyimi tamamlama işleminde bu ek yöntemleri görebilirsiniz.

OrderBy Örneği

Aşağıdaki örnekte, bir tamsayı dizisinde standart sorgu işleci OrderBy yönteminin nasıl çağrılmadığı gösterilmektedir. Parantez içindeki ifade bir lambda ifadesidir. Birçok standart sorgu işleci lambda ifadelerini parametre olarak alır, ancak bu uzantı yöntemleri için bir gereksinim değildir. Daha fazla bilgi için bkz . Lambda İfadeleri.

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

Uzantı yöntemleri statik yöntemler olarak adlandırılı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. parametresi bu değiştiriciyi izler. Uzantı yöntemleri yalnızca ad alanını bir using yönergeyle kaynak kodunuz içine açıkça içeri aktardığınızda kapsam dahilindedir.

Aşağıdaki örnekte sınıfı için tanımlanan bir uzantı yöntemi gösterilmektedir System.String . İç içe olmayan, genel olmayan bir statik sınıf içinde tanımlanır:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this string str)
        {
            return str.Split(new char[] { ' ', '.', '?' },
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}

WordCount Uzantı yöntemi şu using yönergeyle kapsama getirilebilir:

using ExtensionMethods;

Ve bu sözdizimi kullanılarak bir uygulamadan çağrılabilir:

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

Örnek yöntemi söz dizimi ile kodunuzda uzantı yöntemini çağırırsınız. Derleyici tarafından oluşturulan ara dil (IL), kodunuzu statik yöntemdeki bir çağrıya çevirir. Kapsülleme ilkesi gerçekten ihlal edilmez. Uzantı yöntemleri, genişletdikleri türdeki özel değişkenlere erişemez.

MyExtensions Hem sınıfı hem WordCount de yöntemidir staticve diğer static tüm üyeler gibi bu sınıfa erişilebilir. yöntemi WordCount aşağıdaki gibi diğer static yöntemler gibi çağrılabilir:

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

Yukarıdaki C# kodu:

  • değerine "Hello Extension Methods"sahip yeni string bir adlandırılmış ad s bildirir ve atar.
  • Verilen bağımsız değişkeni sçağırırMyExtensions.WordCount.

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

Genel olarak, uzantı yöntemlerini kendi yöntemlerinizi uygulamaktan çok daha sık çağıracaksınız. Genişletme yöntemleri örnek yöntem sözdizimi tarafından çağrıldığından istemci kodundan kullanmak için herhangi bir özel bilgi gerekli değildir. Belirli bir tür için uzantı yöntemlerini etkinleştirmek için, yöntemlerin tanımlandığı ad alanına yönelik bir using yönerge eklemeniz gerekir. Örneğin, standart sorgu işleçlerini kullanmak için bu using yönergeyi kodunuza ekleyin:

using System.Linq;

(System.Core.dll bir başvuru da eklemeniz gerekebilir.) Standart sorgu işleçlerinin artık IntelliSense'te çoğu IEnumerable<T> tür için ek yöntemler olarak göründüğünü fark edeceksiniz.

Derleme Zamanında Uzantı Yöntemleri Bağlama

Bir sınıfı veya arabirimi genişletmek için genişletme yöntemini kullanabilir, ancak bunları geçersiz kılamazsınız. Arabirim veya sınıf yöntemiyle aynı ada ve imzaya sahip genişletme yöntemi asla çağrılmaz. Derleme sırasında genişletme yöntemleri, her zaman türün kendisinde tanımlı örnek yöntemlerden 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 örnek yöntemine bağlanır. Derleyici bir yöntem çağırmayla karşılaştığında, türün örnek yöntemleri önce bir eşleşme arar. Eşleşme bulunmazsa, türü için tanımlanan uzantı yöntemlerini arar ve bulduğu ilk uzantı yöntemine bağlanır.

Örnek

Aşağıdaki örnek, C# derleyicisinin bir yöntem çağrısını türde bir örnek yöntemine mi yoksa bir genişletme yöntemine mi bağlayacağını belirlemede izlediği kuralları gösterir. Statik sınıf Extensions , uygulayan IMyInterfaceherhangi bir tür için tanımlanan uzantı yöntemlerini içerir. , Bve C sınıfları Aarabirimini uygular.

MethodB Uzantı yöntemi hiçbir zaman çağrılmaz çünkü adı ve imzası sınıflar tarafından zaten uygulanan yöntemlerle tam olarak eşleşmektedir.

Derleyici eşleşen imzaya sahip bir örnek yöntemi bulamadıysa, varsa eşleşen bir uzantı yöntemine bağlanır.

// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    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()
 */

Ortak Kullanım Desenleri

Koleksiyon İşlevselliği

Geçmişte, belirli bir tür için arabirimi uygulayan System.Collections.Generic.IEnumerable<T> ve bu türdeki koleksiyonlar üzerinde işlem gerçekleştiren işlevler içeren "Koleksiyon Sınıfları" oluşturmak yaygındı. Bu tür bir koleksiyon nesnesi oluşturmada bir sorun olmasa da, üzerinde System.Collections.Generic.IEnumerable<T>bir uzantı kullanılarak aynı işlev elde edilebilir. Uzantılar, işlevselliğin bu tür üzerinde uygulanan System.Collections.Generic.IEnumerable<T> veya System.Collections.Generic.List<T> gibi herhangi bir System.Array 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.

Katmana Özgü İş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, nesneyi diğer katmanlarda gerekli olmayan veya istenen yöntemlerle aşağı yüklemeden her uygulama katmanına özgü işlevler eklemek için kullanılabilir.

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

Ö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şletebiliriz. Örneğin, uzantı yöntemlerini kullanmıyorsak, kodumuzda birden çok yerden çağrılabilen bir Engine SQL Server'da sorgu yürütme işini yapmak için bir veya Query sınıfı oluşturabiliriz. Ancak, sql server bağlantısına sahip olduğumuz her yerden bu sorguyu gerçekleştirmek için uzantı yöntemlerini kullanarak sınıfını genişletebiliriz System.Data.SqlClient.SqlConnection . Diğer örnekler sınıfa ortak işlevler System.String eklemek, nesnenin veri işleme özelliklerini System.IO.Stream genişletmek ve System.Exception belirli hata işleme işlevleri için nesneler 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 genişletilmesi, yöntemlere değer tarafından geçirildiği için türlerle struct 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. Değiştiriciyi ref ilk bağımsız değişkene ekleyerek uzantı ref yöntemi yapabilirsiniz. Anahtar ref sözcük, herhangi bir anlam farkı olmadan anahtar sözcüğün this önüne veya arkasına görünebilir. Değiştiricinin ref eklenmesi, ilk bağımsız değişkenin başvuruya göre geçirildiğini gösterir. Bu, genişletilmiş yapının durumunu değiştiren uzantı yöntemleri yazmanızı sağlar (özel üyelerin erişilebilir olmadığını unutmayın). Uzantı yönteminin ilk parametresi ref olarak yalnızca yapıyla kısıtlanmış değer türlerine veya genel türlere (daha fazla bilgi için kısıtlamaya bakınstruct) izin verilir. Aşağıdaki örnekte, sonucu yeniden atamaya veya anahtar sözcüğüyle ref bir ref işlevden geçirmeye gerek kalmadan yerleşik bir türü doğrudan değiştirmek için uzantı 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++;
}

public static class IntProgram
{
    public static void Test()
    {
        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
    }
}

Bu sonraki örnekte kullanıcı tanımlı yapı türleri için uzantı yöntemleri gösterilmektedir ref :

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

public static class AccountProgram
{
    public static void Test()
    {
        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 yine de tercih edilir olsa da, uzantı yöntemleri .NET ekosistemi genelinde yeniden kullanılabilir işlevsellik oluşturmak için önemli bir seçenek haline gelmiştir. Özgün kaynağın sizin denetiminizde olmadığı, türetilmiş bir nesnenin uygunsuz veya imkansız olduğu veya işlevselliğin geçerli kapsamın dışında gösterilmemesi gereken durumlar için Uzantı yöntemleri mükemmel bir seçimdir.

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

Kaynak kodunun denetiminde olmadığınız bir türü genişletmek için bir uzantı yöntemi kullanırken, tür uygulamasındaki bir değişikliğin uzantı yönteminizin bozulmasına neden olması riskini çalıştırırsınız.

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ı seviyesinde kapsama alınır. Ö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.

Uygulanan bir sınıf kitaplığı için derleme sürüm numarasının artıyor olmasını önlemek için uzantı yöntemleri kullanmamanız gerekir. 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ümü Oluşturma.

Ayrıca bkz.