Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Począwszy od języka C# 14, deklaracje najwyższego static class poziomu mogą używać extension bloków do deklarowania składowych rozszerzeń. 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.
Dokumentacja języka C# zawiera ostatnio wydaną wersję języka C#. Zawiera również początkową dokumentację dla funkcjonalności w publicznych wersjach testowych nadchodzącego wydania języka.
Dokumentacja identyfikuje dowolną funkcję po raz pierwszy wprowadzoną w ostatnich trzech wersjach języka lub w bieżącej publicznej wersji zapoznawczej.
Wskazówka
Aby dowiedzieć się, kiedy funkcja została po raz pierwszy wprowadzona w języku C#, zapoznaj się z artykułem dotyczącym historii wersji języka C#.
Blok extension określa typ i odbiornik dla elementów rozszerzeń. Metody, właściwości, indeksatory i operatory można zadeklarować wewnątrz deklaracji extension . Poniższy przykład deklaruje pojedynczy blok rozszerzenia, który definiuje metodę rozszerzenia wystąpienia, właściwość wystąpienia i metodę operatora statycznego.
Uwaga / Notatka
Wszystkie przykłady w tym artykule obejmują komentarze XML dla elementów członkowskich i bloku rozszerzenia. Węzeł w extension bloku opisuje rozszerzony typ i parametr odbiornika. Kompilator języka C# kopiuje ten węzeł do wygenerowanego elementu członkowskiego dla wszystkich elementów członkowskich w bloku rozszerzenia. W tych przykładach pokazano preferowany styl generowania dokumentacji XML dla elementów członkowskich rozszerzenia.
/// <summary>
/// Contains extension members for numeric sequences.
/// </summary>
public static class NumericSequences
{
/// <summary>
/// Defines extensions for integer sequences.
/// </summary>
/// <param name="sequence">The sequence used as a receiver.</param>
extension(IEnumerable<int> sequence)
{
/// <summary>
/// Adds a scalar value to each element in the sequence.
/// </summary>
/// <param name="operand">The amount to add.</param>
/// <returns>
/// A new sequence where each value contains the updated value.
/// </returns>
public IEnumerable<int> AddValue(int operand)
{
foreach (var item in sequence)
{
yield return item + operand;
}
}
/// <summary>
/// Gets the median value of the sequence.
/// </summary>
/// <remarks>
/// This value is calculated when requested.
/// </remarks>
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]) / 2;
}
else
{
// Odd number of elements: return the middle element
return sortedList[middleIndex];
}
}
}
/// <summary>
/// Concatenates two sequences of integers into a single sequence.
/// </summary>
/// <remarks>The resulting sequence enumerates all elements from <paramref name="left"/> in order,
/// followed by all elements from <paramref name="right"/>. Enumeration is deferred and performed lazily as the
/// returned sequence is iterated.</remarks>
/// <param name="left">The first sequence of integers to concatenate. Cannot be null.</param>
/// <param name="right">The second sequence of integers to concatenate. Cannot be null.</param>
/// <returns>A sequence that contains the elements of the first sequence followed by the
/// elements of the second sequence.</returns>
public static IEnumerable<int> operator +(IEnumerable<int> left, IEnumerable<int> right)
=> left.Concat(right);
}
}
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.
Uzyskujesz dostęp do dowolnych elementów członkowskich rozszerzenia, tak jakby były członkami typu odbiornika:
IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);
var median = numbers.Median;
var combined = numbers + Enumerable.Range(100, 10);
Można zadeklarować dowolną liczbę elementów członkowskich w jednym bloku, o ile mają one 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:
/// <summary>
/// Provides static extensions for the <see cref="IEnumerable{Int32}"/> type.
/// </summary>
extension(IEnumerable<int>)
{
// Method:
/// <summary>
/// Generates a sequence of integers, starting from a specified value and incrementing by a given amount.
/// </summary>
/// <param name="low">The starting value of the sequence.</param>
/// <param name="count">The number of integers to generate. Must be non-negative.</param>
/// <param name="increment">The value by which to increment each subsequent integer in the sequence.</param>
/// <returns>
/// An enumerable collection of integers, beginning with the specified starting value and containing the
/// specified number of elements, each incremented by the given amount.
/// </returns>
public static IEnumerable<int> Generate(int low, int count, int increment)
{
for (int i = 0; i < count; i++)
yield return low + (i * increment);
}
// Property:
/// <summary>
/// Gets an empty sequence of integers representing the identity element for sequence operations.
/// </summary>
/// <remarks>
/// This property can be used as a neutral starting point when aggregating or composing
/// sequences of integers. The returned sequence is always empty and does not allocate any storage.
/// </remarks>
public static IEnumerable<int> Identity => Enumerable.Empty<int>();
}
Rozszerzenia statyczne są wywoływane tak, jakby były statycznymi elementami członkowskimi typu odbiorcy:
var newSequence = IEnumerable<int>.Generate(5, 10, 2);
var identity = IEnumerable<int>.Identity;
Operatory są wywoływane tak, jakby były operatorami zdefiniowanymi przez użytkownika w typie.
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ę AddValue można wywołać z dowolnej innej metody tak, jakby była członkiem 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 potrzebny jest parametr typu:
- Dodaj parametr typu do
extensiondeklaracji, gdy parametr typu jest używany w odbiorniku. - Dodaj parametr typu do deklaracji składowej, gdy typ różni się od dowolnego parametru typu określonego 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:
/// <summary>
/// Contains generic extension members for sequences.
/// </summary>
public static class GenericExtensions
{
/// <summary>
/// Defines extensions for generic sequences.
/// </summary>
/// <typeparam name="TReceiver">The type of elements in the receiver sequence.</typeparam>
/// <param name="source">The sequence used as a receiver.</param>
extension<TReceiver>(IEnumerable<TReceiver> source)
{
/// <summary>
/// Returns a sequence containing a specified number of elements from the source, starting at a given index.
/// </summary>
/// <param name="start">The zero-based index at which to begin retrieving elements. Must be greater than or equal to 0.</param>
/// <param name="count">The number of elements to return. Must be greater than or equal to 0.</param>
/// <returns>
/// An <see cref="IEnumerable{TReceiver}"/> that contains up to <paramref name="count"/> elements from the
/// source sequence, starting at the element at position <paramref name="start"/>. If <paramref name="start"/>
/// is greater than the number of elements in the source, an empty sequence is returned.
/// </returns>
public IEnumerable<TReceiver> Spread(int start, int count)
=> source.Skip(start).Take(count);
/// <summary>
/// Returns a sequence that contains the elements of the original sequence followed by the elements of a
/// specified sequence, each transformed by a converter function.
/// </summary>
/// <remarks>
/// Enumeration of the returned sequence will not start until the sequence is iterated.
/// The converter function is applied to each element of the appended sequence as it is enumerated.
/// </remarks>
/// <typeparam name="TArg">The type of the elements in the sequence to append.</typeparam>
/// <param name="second">The sequence whose elements are to be appended after being converted. Cannot be null.</param>
/// <param name="Converter">A function to convert each element of the appended sequence to the result type. Cannot be null.</param>
/// <returns>
/// An IEnumerable<TReceiver> that contains the elements of the original sequence followed by the converted
/// elements of the specified sequence.
/// </returns>
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);
}
}
/// <summary>
/// Returns a sequence that consists of the elements of the specified collection, transformed by the provided
/// converter, followed by the elements of the current sequence.
/// </summary>
/// <remarks>
/// Enumeration of the returned sequence will not start until the sequence is iterated.
/// Both the input collection and the converter function must not be null; otherwise, an exception will be
/// thrown at enumeration time.
/// </remarks>
/// <typeparam name="TArg">The type of the elements in the collection to prepend.</typeparam>
/// <param name="second">The collection whose elements are to be transformed and prepended to the current sequence. Cannot be null.</param>
/// <param name="converter">A function to convert each element of the prepended collection to the target type. Cannot be null.</param>
/// <returns>
/// An IEnumerable<TReceiver> that contains the converted elements of the specified collection followed by the
/// elements of the current sequence.
/// </returns>
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;
}
}
/// <summary>
/// Gets an empty sequence of the receiver's element type.
/// </summary>
/// <remarks>
/// This property can be used as a neutral starting point when aggregating or composing
/// sequences of the receiver's element type. The returned sequence is always empty and does not allocate any storage.
/// </remarks>
public static IEnumerable<TReceiver> Identity => Enumerable.Empty<TReceiver>();
}
}
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;
}
}
}
Indeksatory rozszerzeń
Począwszy od języka C# 15, można zadeklarować indeksatory w extension bloku. Indeksator rozszerzeń umożliwia indeksowanie do odbiornika tak, jakby indeksator został zadeklarowany w typie odbiorcy. Ponieważ indeksatory są zawsze elementami członkowskimi wystąpienia, blok rozszerzenia, który deklaruje indeksator, musi podać nazwany parametr odbiorcy. Indeksatory rozszerzeń obsługują te same funkcje co zwykłe indeksatory, w tym get metody dostępu i set akcesory, metody dostępu do wyrażeń i metody dostępu zwracane przez ref.
Poniższy przykład deklaruje indeksator IEnumerable<int> tylko do pobrania, który zwraca element na określonej pozycji:
/// <summary>
/// Contains an extension indexer for numeric sequences.
/// </summary>
public static class SequenceIndexer
{
/// <summary>
/// Defines an indexer for integer sequences.
/// </summary>
/// <param name="sequence">The sequence used as a receiver.</param>
extension(IEnumerable<int> sequence)
{
/// <summary>
/// Gets the element at the specified position in the sequence.
/// </summary>
/// <param name="index">The zero-based position of the element to retrieve.</param>
/// <value>The element at the specified position.</value>
public int this[int index] => sequence.ElementAt(index);
}
}
Indeksujesz do odbiornika tak, jakby indeksator był członkiem typu odbiornika:
int third = numbers[2];
Dostęp do indeksu i zakresu
Elementy członkowskie rozszerzenia mogą również dostarczać elementy członkowskie wymagane do niejawnegoIndex i Range dostępu. Właściwość Length lub Count i indeksator liczb całkowitych obsługują indeksowanie od końca.
Slice(int, int) Metoda obsługuje dostęp do zakresu. Te elementy członkowskie mogą być członkami wystąpienia, członkami rozszerzenia lub kombinacją obu tych elementów.
Na przykład Readings zawija tablicę bez deklarowania składowych indeksowania. Jego rozszerzenia udostępniają Countindeksator liczb całkowitych i Slice:
/// <summary>Stores sensor readings.</summary>
public sealed class Readings(int[] values)
{
/// <summary>Gets the stored readings.</summary>
public int[] Values { get; } = values;
}
/// <summary>Provides indexing for sensor readings.</summary>
public static class ReadingExtensions
{
/// <summary>Defines indexing members for readings.</summary>
/// <param name="readings">The readings to access.</param>
extension(Readings readings)
{
/// <summary>Gets the number of readings.</summary>
public int Count => readings.Values.Length;
/// <summary>Gets a reading by position.</summary>
/// <param name="index">The zero-based position.</param>
public int this[int index] => readings.Values[index];
/// <summary>Gets a range of readings.</summary>
/// <param name="start">The first position.</param>
/// <param name="length">The number of readings.</param>
public Readings Slice(int start, int length)
=> new(readings.Values.AsSpan(start, length).ToArray());
}
}
Te elementy członkowskie umożliwiają indeksowanie zwykłe, indeksowanie z końca, zakresów i wzorców listy:
Readings readings = new([10, 20, 30, 40]);
Console.WriteLine(readings[^1]); // output: 40
Console.WriteLine(readings[1..3][0]); // output: 20
Console.WriteLine(readings is [10, .., 40]); // output: True
Wyszukiwanie i ograniczenia
W przypadku wyszukiwania indeksatora kompilator próbuje stosować odpowiednie elementy członkowskie w następującej kolejności:
- Indeksator zadeklarowany w typie odbiorcy.
- Niejawny dostęp do indeksu lub zakresu przy użyciu elementów członkowskich wystąpienia.
- Indeksator rozszerzeń.
- Niejawny dostęp do indeksu lub zakresu przy użyciu elementów członkowskich rozszerzenia.
Wcześniejsza odpowiednia forma ma pierwszeństwo przed późniejszym. Na przykład indeksator liczby całkowitej wystąpienia z właściwością wystąpienia Length może obsłużyć receiver[^1] przed indeksatorem rozszerzenia, który bezpośrednio akceptuje Indexelement .
Indeksatory rozszerzeń i powiązane elementy członkowskie mają następujące ograniczenia:
- Indeksatory rozszerzeń nie są używane do uzyskiwania dostępu do ciągów ani elementów tablicy, nawet jeśli deklarujesz rozszerzenia dla tych typów.
- Drzewo wyrażeń nie może zawierać właściwości rozszerzenia ani dostępu indeksatora.
- Rozszerzenia
LengthiCountwłaściwości nie uczestniczą w optymalizacjach spreadu wyrażeń kolekcji.
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#.