Aracılığıyla paylaş


LINQ'i genişletme

Tüm LINQ tabanlı yöntemler iki benzer desenden birini izler. Numaralandırılabilir bir sıra alır. Farklı bir dizi veya tek bir değer döndürür. Şeklin tutarlılığı, benzer şekilde yöntemler yazarak LINQ'i genişletmenizi sağlar. Aslında LINQ ilk kez kullanıma sunulduğundan beri .NET kitaplıkları birçok .NET sürümünde yeni yöntemler kazanmıştır. Bu makalede, aynı deseni izleyen kendi yöntemlerinizi yazarak LINQ'yi genişletme örnekleri görürsünüz.

LINQ sorguları için özel yöntemler ekleme

Arabirime uzantı yöntemleri ekleyerek LINQ sorguları için kullandığınız yöntem IEnumerable<T> kümesini genişletirsiniz. Örneğin, standart ortalama veya maksimum işlemlere ek olarak, bir değer dizisinden tek bir değeri hesaplamak için özel bir toplama yöntemi oluşturursunuz. Ayrıca, bir değer dizisi için özel filtre veya belirli bir veri dönüşümü olarak çalışan ve yeni bir sıra döndüren bir yöntem de oluşturursunuz. Bu tür yöntemlere örnek olarak Distinct, Skipve Reverseverilebilir.

Arabirimi IEnumerable<T> genişlettiğinizde, kendi yöntemlerinizi herhangi bir numaralandırılabilir koleksiyona uygulayabilirsiniz. Daha fazla bilgi için bkz . Uzantı Yöntemleri.

Toplama yöntemi, bir değer kümesinden tek bir değer hesaplar. LINQ Average, Min, ve Max gibi çeşitli toplama yöntemleri sağlar. Arabirime bir uzantı yöntemi ekleyerek, IEnumerable<T> arabirimine kendi toplama yönteminizi oluşturabilirsiniz.

C# 14'te başlayarak, birden çok uzantı üyesi içerecek bir uzantı bloğu bildirebilirsiniz. Anahtar sözcüğünü extension ve ardından parantez içinde alıcı parametresini içeren bir uzantı bloğu bildirirsiniz. Aşağıdaki kod örneği, uzantı bloğunda adlı Median bir uzantı yönteminin nasıl oluşturulacağını gösterir. yöntemi, türünde doublebir sayı dizisi için ortanca değeri hesaplar.

extension(IEnumerable<double>? source)
{
    public double Median()
    {
        if (source is null || !source.Any())
        {
            throw new InvalidOperationException("Cannot compute median for a null or empty set.");
        }

        var sortedList =
            source.OrderBy(number => number).ToList();

        int itemIndex = sortedList.Count / 2;

        if (sortedList.Count % 2 == 0)
        {
            // Even number of items.
            return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
        }
        else
        {
            // Odd number of items.
            return sortedList[itemIndex];
        }
    }
}

Uzantı yöntemini bildirmek için değiştiriciyi thisstatik yönteme de ekleyebilirsiniz. Aşağıdaki kod eşdeğer Median uzantı yöntemini gösterir:

public static class EnumerableExtension
{
    public static double Median(this IEnumerable<double>? source)
    {
        if (source is null || !source.Any())
        {
            throw new InvalidOperationException("Cannot compute median for a null or empty set.");
        }

        var sortedList =
            source.OrderBy(number => number).ToList();

        int itemIndex = sortedList.Count / 2;

        if (sortedList.Count % 2 == 0)
        {
            // Even number of items.
            return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
        }
        else
        {
            // Odd number of items.
            return sortedList[itemIndex];
        }
    }
}

Herhangi bir numaralandırılabilir koleksiyon için her iki uzantı yöntemini de arabirimden IEnumerable<T> diğer toplama yöntemlerini çağırdığınız gibi çağırırsınız.

Aşağıdaki kod örneği, Median türünde bir dizi için double yönteminin nasıl kullanılacağını gösterir.

double[] numbers = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query = numbers.Median();

Console.WriteLine($"double: Median = {query}");
// This code produces the following output:
//     double: Median = 4.85

Toplama yönteminizi, çeşitli türlerdeki dizileri kabul etmek için aşırı yükleyebilirsiniz. Standart yaklaşım, her tür için bir aşırı yükleme oluşturmaktır. Bir diğer yaklaşım da genel bir tür alan ve temsilci kullanarak bunu belirli bir türe dönüştüren bir aşırı yükleme oluşturmaktır. Ayrıca her iki yaklaşımı da birleştirebilirsiniz.

Desteklemek istediğiniz her tür için belirli bir aşırı yükleme oluşturabilirsiniz. Aşağıdaki kod örneği, Median türü için int yönteminin aşırı yüklemesini gösterir.

// int overload
public static double Median(this IEnumerable<int> source) =>
    (from number in source select (double)number).Median();

Artık aşağıdaki kodda gösterildiği gibi, hem Median hem de integer türleri için double aşırı yüklemelerini çağırabilirsiniz.

double[] numbers1 = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query1 = numbers1.Median();

Console.WriteLine($"double: Median = {query1}");

int[] numbers2 = [1, 2, 3, 4, 5];
var query2 = numbers2.Median();

Console.WriteLine($"int: Median = {query2}");
// This code produces the following output:
//     double: Median = 4.85
//     int: Median = 3

Ayrıca , genel bir nesne dizisini kabul eden bir aşırı yükleme de oluşturabilirsiniz. Bu aşırı yükleme bir temsilciyi parametre olarak alır ve bunu kullanarak genel türdeki nesnelerin sırasını belirli bir türe dönüştürür.

Aşağıdaki kod, Median temsilcisini parametre olarak alan Func<T,TResult> yönteminin aşırı yüklemesini gösterir. Bu temsilci T genel türünde bir nesne alır ve türünde double bir nesne döndürür.

// generic overload
public static double Median<T>(
    this IEnumerable<T> numbers, Func<T, double> selector) =>
    (from num in numbers select selector(num)).Median();

Artık herhangi bir türdeki Median nesne dizisi için yöntemini çağırabilirsiniz. Türün kendi yöntem aşırı yüklemesi yoksa, bir temsilci parametresi geçirmeniz gerekir. C# dilinde, bu amaçla bir lambda ifadesi kullanabilirsiniz. Ayrıca, yalnızca Visual Basic'te, yöntem çağrısı yerine Aggregate veya Group By yan tümcesini kullanırsanız, bu yan tümcesinin kapsamındaki herhangi bir değeri veya ifadeyi geçirebilirsiniz.

Aşağıdaki örnek kod, bir tamsayı dizisi ve bir dize dizisi için Median yönteminin nasıl çağrılacağını gösterir. Dizeler için, dizideki dizelerin uzunlukları için ortanca değer hesaplanır. Örnek, her durum için temsilci parametresinin Func<T,TResult> yöntemine Median nasıl geçirileceğini göstermektedir.

int[] numbers3 = [1, 2, 3, 4, 5];

/*
    You can use the num => num lambda expression as a parameter for the Median method
    so that the compiler will implicitly convert its value to double.
    If there is no implicit conversion, the compiler will display an error message.
*/
var query3 = numbers3.Median(num => num);

Console.WriteLine($"int: Median = {query3}");

string[] numbers4 = ["one", "two", "three", "four", "five"];

// With the generic overload, you can also use numeric properties of objects.
var query4 = numbers4.Median(str => str.Length);

Console.WriteLine($"string: Median = {query4}");
// This code produces the following output:
//     int: Median = 3
//     string: Median = 4

Arabirimi, IEnumerable<T>bir değer dizisi döndüren özel bir sorgu yöntemiyle genişletebilirsiniz. Bu durumda, yöntem türünde IEnumerable<T> bir koleksiyon döndürmelidir. Bu tür yöntemler, bir değer dizisine filtre veya veri dönüşümleri uygulamak için kullanılabilir.

Aşağıdaki örnekte, ilk öğeden başlayarak koleksiyondaki diğer tüm öğeleri döndüren adlı AlternateElements bir uzantı yönteminin nasıl oluşturulacağı gösterilmektedir.

// Extension method for the IEnumerable<T> interface.
// The method returns every other element of a sequence.
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T> source)
{
    int index = 0;
    foreach (T element in source)
    {
        if (index % 2 == 0)
        {
            yield return element;
        }

        index++;
    }
}

Aşağıdaki kodda gösterildiği gibi, arabirimden IEnumerable<T> diğer yöntemleri çağırdığınız gibi, numaralandırılabilir herhangi bir koleksiyon için bu uzantı yöntemini çağırabilirsiniz:

string[] strings = ["a", "b", "c", "d", "e"];

var query5 = strings.AlternateElements();

foreach (var element in query5)
{
    Console.WriteLine(element);
}
// This code produces the following output:
//     a
//     c
//     e

Bu makalede gösterilen her örnek farklı bir alıcıya sahiptir. Bu, her yöntemin benzersiz alıcıyı belirten farklı bir uzantı bloğunda bildirilmesi gerektiği anlamına gelir. Aşağıdaki kod örneği, her biri bu makalede tanımlanan yöntemlerden birini içeren üç farklı uzantı bloğuna sahip tek bir statik sınıfı gösterir:

public static class EnumerableExtension
{
    extension(IEnumerable<double>? source)
    {
        public double Median()
        {
            if (source is null || !source.Any())
            {
                throw new InvalidOperationException("Cannot compute median for a null or empty set.");
            }

            var sortedList =
                source.OrderBy(number => number).ToList();

            int itemIndex = sortedList.Count / 2;

            if (sortedList.Count % 2 == 0)
            {
                // Even number of items.
                return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
            }
            else
            {
                // Odd number of items.
                return sortedList[itemIndex];
            }
        }
    }

    extension(IEnumerable<int> source)
    {
        public double Median() =>
            (from number in source select (double)number).Median();
    }

    extension<T>(IEnumerable<T> source)
    {
        public double Median(Func<T, double> selector) =>
            (from num in source select selector(num)).Median();

        public IEnumerable<T> AlternateElements()
        {
            int index = 0;
            foreach (T element in source)
            {
                if (index % 2 == 0)
                {
                    yield return element;
                }

                index++;
            }
        }
    }
}

Son uzantı bloğu genel bir uzantı bloğu bildirir. Alıcının tür parametresi doğrudan extension üzerinde bildirilir.

Yukarıdaki örnek, her uzantı bloğunda bir uzantı üyesi bildirir. Çoğu durumda, aynı alıcı için birden çok uzantı üyesi oluşturursunuz. Böyle durumlarda, bu üyeler için uzantıları tek bir uzantı bloğunda bildirmeniz gerekir.