Udostępnij za pośrednictwem


Deklaracja rozszerzenia (Przewodnik C#)

Począwszy od języka C# 14, najwyższego zakresu deklaracje niegeneryczne static class mogą używać extension kontenerów do deklarowania członków rozszerzenia. Członkowie rozszerzenia są metodami lub właściwościami i mogą być członkami instancji lub członkami statycznymi. Wcześniejsze wersje języka C# umożliwiają stosowanie metod rozszerzeń przez dodanie this jako modyfikatora do pierwszego parametru metody statycznej zadeklarowanej w klasie statycznej najwyższego poziomu.

Blok extension określa typ i odbiornik dla elementów rozszerzeń. Metody i właściwości można zadeklarować wewnątrz deklaracji extension . Poniższy przykład deklaruje pojedynczy blok rozszerzenia, który definiuje metodę rozszerzającą dla instancji oraz umożliwia dodanie właściwości instancji.

public static class NumericSequences
{
    extension(IEnumerable<int> sequence)
    {
        public IEnumerable<int> AddValue(int operand)
        {
            foreach (var item in sequence)
            {
                yield return item + operand;
            }
        }

        public int Median
        {
            get
            {

                var sortedList = sequence.OrderBy(n => n).ToList();
                int count = sortedList.Count;
                int middleIndex = count / 2;

                if (count % 2 == 0)
                {
                    // Even number of elements: average the two middle elements
                    return (sortedList[middleIndex - 1] + sortedList[middleIndex]);
                }
                else
                {
                    // Odd number of elements: return the middle element
                    return sortedList[middleIndex];
                }
            }
        }

        public int this[int index] => sequence.Skip(index).First();
    }
}

Element extension definiuje odbiornik: sequence, czyli IEnumerable<int>. Typ odbiornika może być niesparametryzowany, otwarty generyczny lub zamknięty generyczny. Nazwa sequence jest w zakresie we wszystkich elementach członkowskich instancji, które są zadeklarowane w tym rozszerzeniu. Zarówno metoda rozszerzenia, jak i właściwość, mają dostęp do sequence.

Do każdego z członków rozszerzenia można uzyskać dostęp, jak gdyby to były członkowie typu odbiornika.

IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);

var median = numbers.Median;

Można zadeklarować dowolną liczbę elementów w jednym kontenerze, o ile dzielą ten sam odbiornik. Można również zadeklarować dowolną liczbę bloków rozszerzeń w jednej klasie. Różne rozszerzenia nie muszą deklarować tego samego typu ani nazwy odbiorcy. Parametr rozszerzenia nie musi zawierać nazwy parametru, jeśli jedynymi elementami członkowskimi są statyczne:

extension(IEnumerable<int>)
{
    // Method:
    public static IEnumerable<int> Generate(int low, int count, int increment)
    {
        for (int i = 0; i < count; i++)
            yield return low + (i * increment);
    }

    // Property:
    public static IEnumerable<int> Identity => Enumerable.Empty<int>();
}

Rozszerzenia statyczne mogą być wywoływane tak, jak gdyby były statycznymi członkami typu odbiorcy.

var newSequence = IEnumerable<int>.Generate(5, 10, 2);
var identity = IEnumerable<int>.Identity;

Ważne

Rozszerzenie nie wprowadza zakresu dla deklaracji członków. Wszyscy członkowie zadeklarowani w jednej klasie, nawet jeśli są w różnych rozszerzeniach, muszą mieć unikatowe sygnatury. Wygenerowany podpis zawiera typ odbiorcy w nazwie statycznych członków oraz parametr odbiornika dla członków instancji rozszerzenia.

W poniższym przykładzie przedstawiono metodę rozszerzenia przy użyciu this modyfikatora:

public static class NumericSequenceExtensionMethods
{
    public static IEnumerable<int> AddValue(this IEnumerable<int> sequence, int operand)
    {
        foreach (var item in sequence)
            yield return item + operand;
    }
}

Metodę Add można wywołać z dowolnej innej metody, tak jakby była elementem członkowskim interfejsu IEnumerable<int> :

IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);

Obie formy metod rozszerzeń generują ten sam język pośredni (IL). Osoby wywołujące nie mogą dokonać rozróżnienia między nimi. W rzeczywistości można przekonwertować istniejące metody rozszerzenia na nową składnię składową bez zmiany powodującej niezgodność. Formaty są zarówno binarne, jak i źródłowe.

Ogólne bloki rozszerzeń

W przypadku określenia parametrów typu dla elementu członkowskiego rozszerzenia zadeklarowanego w bloku rozszerzenia zależy od tego, gdzie jest wymagany ten parametr typu:

  • Należy dodać parametr typu do deklaracji extension, gdy parametr typu jest używany w odbiorniku.
  • Należy dodać parametr typu do deklaracji członka, gdy typ różni się od dowolnego parametru typu podanego w odbiorniku.
  • Nie można określić tego samego parametru typu w obu lokalizacjach.

W poniższym przykładzie pokazano blok rozszerzenia, dla IEnumerable<T> którego dwa elementy członkowskie rozszerzenia wymagają drugiego parametru typu:

public static class GenericExtensions
{
    extension<TReceiver>(IEnumerable<TReceiver> source)
    {
        public IEnumerable<TReceiver> Spread(int start, int count)
            => source.Skip(start).Take(count);

        public IEnumerable<TReceiver> Append<TArg>(IEnumerable<TArg> second, Func<TArg, TReceiver> Converter)
        {
            foreach(TReceiver item in source)
            {
                yield return item;
            }
            foreach (TArg item in second)
            {
                yield return Converter(item);
            }
        }

        public IEnumerable<TReceiver> Prepend<TArg>(IEnumerable<TArg> second, Func<TArg, TReceiver> Converter)
        {
            foreach (TArg item in second)
            {
                yield return Converter(item);
            }
            foreach (TReceiver item in source)
            {
                yield return item;
            }
        }
    }
}

Składowe Append i Prepend określają dodatkowy parametr typu dla konwersji. Żaden z elementów członkowskich nie powtarza parametru typu dla odbiornika.

Równoważne deklaracje metody rozszerzenia pokazują, jak te parametry typu są kodowane:

public static class GenericExtensions
{
    public static IEnumerable<T> Spread<T>(this IEnumerable<T> source, int start, int count)
        => source.Skip(start).Take(count);

    public static IEnumerable<T1> Append<T1, T2>(this IEnumerable<T1> source, IEnumerable<T2> second, Func<T2, T1> Converter)
    {
        foreach (T1 item in source)
        {
            yield return item;
        }
        foreach (T2 item in second)
        {
            yield return Converter(item);
        }
    }

    public static IEnumerable<T1> Prepend<T1, T2>(this IEnumerable<T1> source, IEnumerable<T2> second, Func<T2, T1> Converter)
    {
        foreach (T2 item in second)
        {
            yield return Converter(item);
        }
        foreach (T1 item in source)
        {
            yield return item;
        }
    }
}

Zobacz także

Specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz Specyfikacja języka C#. Specyfikacja języka jest ostatecznym źródłem informacji o składni i użyciu języka C#.