Freigeben über


Erweiterungsdeklaration (C#-Referenz)

Ab C# 14 können nichtgenerische static class-Deklarationen auf oberster Ebene extension-Container verwenden, um Erweiterungsmember zu deklarieren. Erweiterungsmember sind Methoden oder Eigenschaften und können als Instanz- oder statische Mitglieder erscheinen. Frühere Versionen von C# ermöglichen Erweiterungsmethoden , indem sie dem ersten Parameter einer statischen Methode, die in einer nichtgenerischen statischen Klasse auf oberster Ebene deklariert ist, als Modifizierer hinzufügen this .

Der extension Block spezifiziert den Typ und den Empfänger für Erweiterungsmitglieder. Sie können Methoden und Eigenschaften innerhalb der extension Deklaration deklarieren. Im folgenden Beispiel wird ein einzelner Erweiterungsblock deklariert, der eine Instanzerweiterungsmethode und eine Instanzeigenschaft definiert.

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

Der extension definiert den Empfänger: sequence, welches ein IEnumerable<int> ist. Der Empfängertyp kann nichtgenerisch, ein offenes Generika oder ein geschlossener generischer Typ sein. Der Name sequence ist im Gültigkeitsbereich jedes Instanzmembers, der in dieser Erweiterung deklariert ist. Die Erweiterungsmethode und die Eigenschaft greifen beide auf sequence zu.

Auf alle Erweiterungsmitglieder kann zugegriffen werden, als wären sie Mitglieder des Empfängertyps:

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

var median = numbers.Median;

Sie können eine beliebige Anzahl von Mitgliedern in einem einzelnen Container deklarieren, solange sie denselben Empfänger gemeinsam nutzen. Sie können auch so viele Erweiterungsblöcke in einer einzelnen Klasse deklarieren. Unterschiedliche Erweiterungen müssen nicht denselben Typ oder denselben Empfängernamen deklarieren. Der Erweiterungsparameter muss den Parameternamen nicht enthalten, wenn die einzigen Member statisch sind:

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

Statische Erweiterungen können aufgerufen werden, als wären sie statische Member des Empfängertyps:

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

Von Bedeutung

Eine Erweiterung führt keinen Gültigkeitsbereich für Memberdeklarationen ein. Alle in einer einzigen Klasse deklarierten Member müssen eindeutige Signaturen aufweisen, selbst wenn sie über mehrere Erweiterungen verteilt sind. Die generierte Signatur enthält den Empfängertyp in ihrem Namen für statische Member und den Empfängerparameter für Erweiterungsinstanzmember.

Das folgende Beispiel zeigt eine Erweiterungsmethode mit dem this Modifizierer:

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

Die Add Methode kann von jeder anderen Methode aufgerufen werden, als ob es sich um ein Element der IEnumerable<int> Schnittstelle handelte:

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

Beide Formen von Erweiterungsmethoden generieren dieselbe Zwischensprache (IL). Anrufer können keinen Unterschied zwischen ihnen machen. Tatsächlich können Sie vorhandene Erweiterungsmethoden in die neue Membersyntax umwandeln, ohne einen Bruch zu verursachen. Die Formate sind sowohl binär als auch quellkompatibel.

Generische Erweiterungsblöcke

Wo Sie die Typparameter für ein erweiterungsmitglied angeben, das in einem Erweiterungsblock deklariert ist, hängt davon ab, wo dieser Typparameter erforderlich ist:

  • Sie fügen der Deklaration den Typparameter extension hinzu, wenn der Typparameter im Empfänger verwendet wird.
  • Sie fügen der Memberdeklaration den Typparameter hinzu, wenn sich der Typ von jedem Typparameter unterscheidet, der für den Empfänger angegeben ist.
  • Sie können in beiden Speicherorten nicht denselben Typparameter angeben.

Das folgende Beispiel zeigt einen Erweiterungsblock, für IEnumerable<T> den zwei der Erweiterungsmember einen zweiten Typparameter erfordern:

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

Die Member Append und Prepend geben den zusätzlichen Typparameter für die Konvertierung an. Keines der Mitglieder wiederholt den Typparameter für den Empfänger.

Die entsprechenden Erweiterungsmethodedeklarationen veranschaulichen, wie diese Typparameter codiert werden:

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

Siehe auch

C#-Sprachspezifikation

Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die endgültige Quelle für C#-Syntax und -Verwendung.