Partager via


Déclaration d’extension (référence C#)

À compter de C# 14, les déclarations non générées static class de niveau supérieur peuvent utiliser extension des blocs pour déclarer des membres d’extension. Les membres d’extension sont des méthodes ou des propriétés et peuvent apparaître comme des membres d’instance ou statiques. Les versions antérieures de C# activent les méthodes d’extension en ajoutant this en tant que modificateur au premier paramètre d’une méthode statique déclarée dans une classe statique de niveau supérieur et non générique.

Le extension bloc spécifie le type et le récepteur pour les membres d’extension. Vous pouvez déclarer des méthodes, des propriétés ou des opérateurs à l’intérieur de la extension déclaration. L’exemple suivant déclare un bloc d’extension unique qui définit une méthode d’extension d’instance, une propriété d’instance et une méthode d’opérateur statique.

Note

Tous les exemples de cet article incluent des commentaires XML pour les membres et le bloc d’extension. Le nœud sur le extension bloc décrit le type étendu et le paramètre récepteur. Le compilateur C# copie ce nœud vers le membre généré pour tous les membres du bloc d’extension. Ces exemples illustrent le style préféré pour générer la documentation XML pour les membres d’extension.

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

Le extension définit le récepteur : sequence, qui est un IEnumerable<int>. Le type de récepteur peut être non générique, générique ouvert ou un type générique fermé. Le nom sequence est accessible dans toutes les instances déclarées dans cette extension. La méthode d'extension et la propriété accèdent toutes deux à sequence.

Vous accédez à tous les membres de l’extension comme s’ils étaient membres du type de récepteur :

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

var median = numbers.Median;

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

Vous pouvez déclarer n’importe quel nombre de membres dans un seul bloc, tant qu’ils partagent le même récepteur. Vous pouvez également déclarer autant de blocs d’extension dans une seule classe. Différentes extensions n’ont pas besoin de déclarer le même type ou le même nom de récepteur. Le paramètre d’extension n’a pas besoin d’inclure le nom du paramètre si les seuls membres sont statiques :

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

Vous appelez des extensions statiques comme si elles sont des membres statiques du type de récepteur :

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

Vous appelez des opérateurs comme s’ils sont des opérateurs définis par l’utilisateur sur le type.

Importante

Une extension n'introduit pas d'étendue pour les déclarations de membres. Tous les membres déclarés dans une classe unique, même dans plusieurs extensions, doivent avoir des signatures uniques. La signature générée inclut le type de récepteur dans son nom pour les membres statiques et le paramètre de récepteur pour les membres de l’instance d’extension.

L’exemple suivant montre une méthode d’extension à l’aide du this modificateur :

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

Vous pouvez appeler la Add méthode à partir de n’importe quelle autre méthode comme si elle était membre de l’interface IEnumerable<int> :

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

Les deux formes de méthodes d’extension génèrent le même langage intermédiaire (IL). Les appelants ne peuvent pas faire de distinction entre eux. En réalité, vous pouvez convertir les méthodes d'extension existantes vers la nouvelle syntaxe de membre sans changement cassant. Les formats sont à la fois binaires et compatibles avec la source.

Blocs d’extension génériques

Lorsque vous spécifiez les paramètres de type d’un membre d’extension déclaré dans un bloc d’extension dépend de l’emplacement où vous avez besoin du paramètre de type :

  • Ajoutez le paramètre de type à la extension déclaration lorsque le paramètre de type est utilisé dans le récepteur.
  • Ajoutez le paramètre de type à la déclaration de membre lorsque le type est distinct de n’importe quel paramètre de type spécifié sur le récepteur.
  • Vous ne pouvez pas spécifier le même paramètre de type dans les deux emplacements.

L’exemple suivant montre un bloc d’extension pour IEnumerable<T> lequel deux des membres de l’extension nécessitent un deuxième paramètre de type :

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

Les membres Append et Prepend spécifient le paramètre de type supplémentaire pour la conversion. Aucun des membres ne répète le paramètre de type pour le récepteur.

Les déclarations de méthode d’extension équivalentes montrent comment ces paramètres de type sont encodés :

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

Voir aussi

Spécification du langage C#

Pour plus d'informations, voir la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.