Condividi tramite


Dichiarazione di estensione (Riferimento C#)

A partire da C# 14, le dichiarazioni di livello superiore e non generiche static class possono usare extension contenitori per dichiarare i membri dell'estensione. I membri di estensione sono metodi o proprietà e possono apparire come membri di istanza o statici. Le versioni precedenti di C# abilitano i metodi di estensione aggiungendo this come modificatore al primo parametro di un metodo statico dichiarato in una classe statica di primo livello e non generica.

Il extension blocco specifica il tipo e il ricevitore per i membri dell'estensione. È possibile dichiarare metodi e proprietà all'interno della extension dichiarazione. Nell'esempio seguente viene dichiarato un singolo blocco di estensione che definisce un metodo di estensione dell'istanza e una proprietà dell'istanza.

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

Definisce extension il ricevitore: sequence, che è un oggetto IEnumerable<int>. Il tipo di ricevitore può essere non generico, generico aperto o un tipo generico chiuso. Il nome sequence è nell'ambito di visibilità in ogni membro di istanza che è dichiarato in tale estensione. Il metodo di estensione e la proprietà accedono entrambi a sequence.

È possibile accedere a qualsiasi membro dell'estensione come se fossero membri del tipo di ricevitore:

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

var median = numbers.Median;

È possibile dichiarare qualsiasi numero di membri in un singolo contenitore, purché condividono lo stesso ricevitore. È anche possibile dichiarare il numero di blocchi di estensione in una singola classe. Estensioni diverse non devono dichiarare lo stesso tipo o nome del ricevitore. Il parametro di estensione non deve includere il nome del parametro se gli unici membri sono statici:

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

Le estensioni statiche possono essere chiamate come se fossero membri statici del tipo di ricevitore:

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

Importante

Un'estensione non introduce un ambito per le dichiarazioni dei membri. Tutti i membri dichiarati in una singola classe, anche se in più estensioni, devono avere firme univoche. La firma generata include il tipo di ricevitore nel nome per i membri statici e il parametro del ricevitore per i membri di istanza estensibile.

L'esempio seguente mostra un metodo di estensione usando il this modificatore :

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

Il Add metodo può essere chiamato da qualsiasi altro metodo come se fosse un membro dell'interfaccia IEnumerable<int> :

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

Entrambe le forme di metodi di estensione generano lo stesso linguaggio intermedio (IL). I chiamanti non possono fare una distinzione tra di loro. In effetti, è possibile convertire i metodi di estensione esistenti nella nuova sintassi del membro senza causare interruzioni. I formati sono sia binari che compatibili con l'origine.

Blocchi di estensione generici

Se si specificano i parametri di tipo per un membro di estensione dichiarato in un blocco di estensione, dipende dalla posizione in cui è necessario il parametro di tipo:

  • Aggiungere il parametro di tipo alla extension dichiarazione quando il parametro di tipo viene usato nel ricevitore.
  • Si aggiunge il parametro di tipo alla dichiarazione del membro quando il tipo è distinto da qualsiasi parametro di tipo specificato nel ricevitore.
  • Non è possibile specificare lo stesso parametro di tipo in entrambe le posizioni.

L'esempio seguente mostra un blocco di estensione per IEnumerable<T> il quale due membri dell'estensione richiedono un secondo parametro di tipo:

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

I membri Append e Prepend specificano il parametro di tipo aggiuntivo per la conversione. Nessuno dei membri ripete il parametro di tipo per il ricevitore.

Le dichiarazioni di metodi di estensione equivalenti illustrano come vengono codificati questi parametri di tipo:

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

Vedere anche

Specificazione del linguaggio C#

Per altre informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.