扩展声明(C# 参考)

从 C# 14 开始,顶级非泛型 static class 声明可以使用 extension 块来声明 扩展成员。 扩展成员是方法或属性,可以显示为实例或静态成员。 早期版本的 C# 通过向顶级非泛型静态类中声明的静态方法的第一个参数添加修饰符来启用this

extension 块指定扩展成员的类型和接收器。 可以在声明内 extension 声明方法、属性或运算符。 以下示例声明一个扩展块,该块定义实例扩展方法、实例属性和静态运算符方法。

注释

本文中的所有示例都包含成员和扩展块的 XML 注释。 块上的 extension 节点描述扩展类型和接收方参数。 C# 编译器将此节点复制到扩展块中的所有成员生成的成员。 这些示例演示了为扩展成员生成 XML 文档的首选样式。

/// <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]);
                }
                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);
    }
}

extension 定义了接收方:sequence,它是一个 IEnumerable<int>。 接收方类型可以是非泛型、开放泛型或封闭的泛型类型。 该名称 sequence 位于该扩展中声明的每个实例成员的范围内。 扩展方法和属性都访问 sequence

访问任何扩展成员,就像它们是接收方类型的成员一样:

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

var median = numbers.Median;

var combined = numbers + Enumerable.Range(100, 10);

只要成员共享相同的接收方,就可以在单个块中声明任意数量的成员。 还可以在单个类中声明任意数量的扩展块。 不同的扩展不需要声明相同的接收方类型或名称。 如果唯一的成员是静态的,则扩展参数不需要包含参数名称:

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

调用静态扩展,就像它们是接收方类型的静态成员一样:

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

调用运算符,就像它们是类型上的用户定义的运算符一样。

重要

扩展不会引入成员声明 的范围 。 在单个类中声明的所有成员(即使在多个扩展中)必须具有唯一签名。 生成的签名在其名称中包含静态成员的接收方类型,以及扩展实例成员的接收方参数。

以下示例演示了使用修饰符的 this 扩展方法:

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

可以从任何其他方法调用 Add 该方法,就像它是接口的成员 IEnumerable<int> 一样:

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

这两种扩展方法都生成相同的中间语言(IL)。 呼叫者不能区分它们。 事实上,你可以将现有扩展方法转换为新的成员语法,而不会引发破坏性变更。 格式既支持二进制兼容,也支持源代码兼容。

泛型扩展块

在扩展块中为声明的扩展成员指定类型参数的位置取决于需要类型参数的位置:

  • 在接收方中使用类型参数时,将类型参数添加到 extension 声明中。
  • 当类型与接收器上指定的任何类型参数不同时,将类型参数添加到成员声明中。
  • 不能在这两个位置指定相同的类型参数。

以下示例显示了 IEnumerable<T> 的一个扩展块,其中两个扩展成员需要第二个类型参数:

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

成员 AppendPrepend 指定了用于转换的额外类型参数。 所有成员都不重复接收方的类型参数。

等效的扩展方法声明演示如何对这些类型参数进行编码:

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

另请参阅

C# 语言规范

有关详细信息,请参阅 C# 语言规范。 语言规范是 C# 语法和用法的明确来源。