Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
I membri dell'estensione consentono di aggiungere metodi a tipi esistenti senza creare un nuovo tipo derivato, ricompilare o modificare in altro modo il tipo originale.
A partire da C# 14, sono disponibili due sintassi usate per definire i metodi di estensione. C# 14 aggiunge extension
contenitori, in cui si definiscono più membri di estensione per un tipo o un'istanza di un tipo. Prima di C# 14, si aggiunge il this
modificatore al primo parametro di un metodo statico per indicare che il metodo viene visualizzato come membro di un'istanza del tipo di parametro.
I metodi di estensione sono metodi statici, ma vengono chiamati come se fossero metodi di istanza nel tipo esteso. Per il codice client scritto in C#, F# e Visual Basic, non esiste alcuna differenza evidente tra la chiamata di un metodo di estensione e i metodi definiti in un tipo. Entrambe le forme dei metodi di estensione vengono compilate nello stesso linguaggio intermedio. I consumatori dei membri di estensione non debbono sapere quale sintassi sia stata usata per definire i metodi di estensione.
I membri di estensione più comuni sono gli operatori di query standard LINQ che aggiungono funzionalità di query ai tipi esistenti System.Collections.IEnumerable e System.Collections.Generic.IEnumerable<T>. Per usare gli operatori di query standard, prima di tutto inserirli nell'ambito con una direttiva using System.Linq
. Qualsiasi tipo implementato IEnumerable<T> sembra quindi avere metodi di istanza come GroupBy, OrderBy, Averagee così via. È possibile visualizzare questi metodi aggiuntivi nel completamento delle istruzioni di IntelliSense quando si digita il punto dopo un'istanza di un tipo IEnumerable<T>, ad esempio List<T> o Array.
Esempio di OrderBy
Nell'esempio seguente viene illustrato come chiamare il metodo dell'operatore OrderBy
di query standard su una matrice di numeri interi. L'espressione tra parentesi è un'espressione lambda. Molti operatori di query standard accettano espressioni lambda come parametri. Per altre informazioni, vedere Espressioni lambda.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
I metodi di estensione sono definiti come metodi statici, ma vengono chiamati usando la sintassi del metodo di istanza. Il primo parametro specifica il tipo su cui opera il metodo. Il parametro segue questo modificatore. I metodi di estensione sono nell'ambito solo quando si importa esplicitamente lo spazio dei nomi nel codice sorgente con una direttiva using
.
Dichiarare i membri dell'estensione
A partire da C# 14, è possibile dichiarare blocchi di estensione. Un blocco di estensione è un blocco in una classe statica non annidata e non generica che contiene membri di estensione per un tipo o un'istanza di tale tipo. Nell'esempio di codice seguente viene definito un blocco di estensione per il string
tipo . Il blocco di estensione contiene un membro: un metodo che conta le parole nella stringa:
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Prima di C# 14, si dichiara un metodo di estensione aggiungendo il this
modificatore al primo parametro:
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Entrambe le forme di estensioni devono essere definite all'interno di una classe statica non annidata e non generica.
E può essere chiamato da un'applicazione usando la sintassi per accedere ai membri dell'istanza:
string s = "Hello Extension Methods";
int i = s.WordCount();
Mentre i membri dell'estensione aggiungono nuove funzionalità a un tipo esistente, i membri dell'estensione non violano il principio di incapsulamento. Le dichiarazioni di accesso per tutti i membri del tipo esteso si applicano ai membri dell'estensione.
Sia la MyExtensions
classe che il WordCount
metodo sono static
e possono essere accessibili come tutti gli altri static
membri. Il WordCount
metodo può essere richiamato come altri static
metodi come segue:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Il codice C# precedente si applica sia al blocco di estensione sia alla sintassi this
per i membri dell'estensione. Il codice precedente:
- Dichiara e assegna un nuovo
string
chiamatos
con il valore"Hello Extension Methods"
. - Chiama
MyExtensions.WordCount
l'argomentos
specificato.
Per altre informazioni, vedere Come implementare e chiamare un metodo di estensione personalizzato.
In generale, è probabile che i membri dell'estensione vengano chiamati molto più spesso di quanto vengano implementati. Poiché i membri dell'estensione vengono chiamati come se siano dichiarati come membri della classe estesa, non è necessaria alcuna conoscenza speciale per usarli dal codice client. Per abilitare i membri di estensione per un particolare tipo, è sufficiente aggiungere una using
direttiva per lo spazio dei nomi in cui sono definiti i metodi. Ad esempio, per usare gli operatori di query standard, aggiungere questa using
direttiva al codice:
using System.Linq;
Collegamento dei membri dell'estensione in fase di compilazione
È possibile usare i membri dell'estensione per estendere una classe o un'interfaccia, ma non per eseguire l'override del comportamento definito in una classe. Gli membri di estensione con lo stesso nome e la stessa signature di un'interfaccia o di un membro di classe non vengono mai chiamati. In fase di compilazione, i membri dell'estensione hanno sempre una priorità inferiore rispetto ai membri dell'istanza (o statici) definiti nel tipo stesso. In altre parole, se un tipo ha un metodo denominato Process(int i)
e si dispone di un metodo di estensione con la stessa firma, il compilatore viene sempre associato al metodo membro. Quando il compilatore rileva una chiamata a un membro, cerca prima di tutto una corrispondenza nei membri del tipo. Se non viene trovata alcuna corrispondenza, cerca tutti i membri di estensione definiti per il tipo. Viene associato al primo membro di estensione trovato. Nell'esempio seguente vengono illustrate le regole che il compilatore C# segue per determinare se eseguire l'associazione a un membro dell'istanza nel tipo o a un membro di estensione. La classe Extensions
statica contiene membri di estensione definiti per qualsiasi tipo che implementa IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public static void MethodA(this IMyInterface myInterface, string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface) =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
Le estensioni equivalenti possono essere dichiarate usando la sintassi del membro dell'estensione C# 14:
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Le classi A
, B
e C
tutte implementano l'interfaccia :
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
Il MethodB
metodo di estensione non viene mai chiamato perché il nome e la firma corrispondono esattamente ai metodi già implementati dalle classi. Quando il compilatore non riesce a trovare un metodo di istanza con una firma corrispondente, viene associato a un metodo di estensione corrispondente, se presente.
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
// For a, b, and c, call the following methods:
// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.
// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB(); // A.MethodB()
// B has methods that match the signatures of the following
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Modelli di utilizzo comuni
Funzionalità della raccolta
In passato, era comune creare "Classi di raccolta" che implementano l'interfaccia System.Collections.Generic.IEnumerable<T> per un determinato tipo e che contengono funzionalità che agiscono su raccolte di quel tipo. Anche se non c'è nulla di sbagliato con la creazione di questo tipo di oggetto raccolta, è possibile ottenere la stessa funzionalità usando un'estensione in System.Collections.Generic.IEnumerable<T>. Le estensioni hanno il vantaggio di consentire la chiamata della funzionalità da qualsiasi tipo di raccolta, come un oggetto System.Array o System.Collections.Generic.List<T>, che implementa System.Collections.Generic.IEnumerable<T> su quel tipo. Un esempio di questo uso di una matrice di Int32 è disponibile in precedenza in questo articolo.
Funzionalità specifica del livello
Quando si usa un'architettura di cipolla o un'altra progettazione di applicazioni a più livelli, è comune avere un set di entità di dominio o oggetti di trasferimento dati che possono essere usati per comunicare attraverso i limiti dell'applicazione. Questi oggetti in genere non contengono funzionalità o solo funzionalità minime che si applicano a tutti i livelli dell'applicazione. I metodi di estensione possono essere usati per aggiungere funzionalità specifiche di ogni livello applicazione.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
È possibile dichiarare una proprietà equivalente FullName
in C# 14 e versioni successive usando la nuova sintassi del blocco di estensione:
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{value.FirstName} {value.LastName}";
}
}
Estensione dei tipi predefiniti
Invece di creare nuovi oggetti quando è necessario creare funzionalità riutilizzabili, è spesso possibile estendere un tipo esistente, ad esempio un tipo .NET o CLR. Ad esempio, se non si usano metodi di estensione, è possibile creare una Engine
classe o Query
per eseguire il lavoro di esecuzione di una query in un'istanza di SQL Server che potrebbe essere chiamata da più posizioni nel codice. Tuttavia, è possibile estendere la System.Data.SqlClient.SqlConnection classe usando metodi di estensione per eseguire tale query da qualsiasi punto in cui si dispone di una connessione a SQL Server. Altri esempi possono essere l'aggiunta di funzionalità comuni alla System.String classe , l'estensione delle funzionalità di elaborazione dati dell'oggetto System.IO.Stream e System.Exception gli oggetti per funzionalità di gestione degli errori specifiche. Questi tipi di casi d'uso sono limitati solo dalla vostra immaginazione e buon senso.
L'estensione dei tipi predefiniti può essere difficile con i tipi struct
perché vengono passati per valore ai metodi. Ciò significa che tutte le modifiche apportate allo struct vengono apportate a una copia dello struct. Queste modifiche non sono visibili al termine del metodo di estensione. È possibile aggiungere il ref
modificatore al primo argomento rendendolo un ref
metodo di estensione. La ref
parola chiave può essere visualizzata prima o dopo la this
parola chiave senza alcuna differenza semantica. L'aggiunta del modificatore ref
indica che il primo argomento viene passato tramite riferimento. Questa tecnica consente di scrivere metodi di estensione che modificano lo stato dello struct esteso (si noti che i membri privati non sono accessibili). Solo i tipi valore o i tipi generici vincolati allo struct (Per altre informazioni su queste regole, vedere struct
Vincolo per altre informazioni) sono consentiti come primo parametro di un ref
metodo di estensione o come ricevitore di un blocco di estensione. L'esempio seguente illustra come usare un ref
metodo di estensione per modificare direttamente un tipo predefinito senza dover riassegnare il risultato o passarlo attraverso una funzione con la ref
parola chiave :
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
I blocchi di estensione equivalenti sono illustrati nel codice seguente:
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Sono necessari blocchi di estensione diversi per distinguere le modalità di parametro per valore e per riferimento per il ricevitore.
È possibile vedere la differenza che l'applicazione di ref
al ricevitore ha nell'esempio seguente:
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
È possibile applicare la stessa tecnica aggiungendo ref
membri di estensione ai tipi di struct definiti dall'utente:
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
L'esempio precedente può essere creato anche usando i blocchi di estensione in C# 14:
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
È possibile accedere a questi metodi di estensione come indicato di seguito:
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Linee guida generali
È preferibile aggiungere funzionalità modificando il codice di un oggetto o derivando un nuovo tipo ogni volta che è ragionevole e possibile farlo. I metodi di estensione sono un'opzione fondamentale per la creazione di funzionalità riutilizzabili in tutto l'ecosistema .NET. I membri dell'estensione sono preferibili quando l'origine originale non è sotto il controllo, quando un oggetto derivato non è appropriato o impossibile o quando la funzionalità ha un ambito limitato.
Per altre informazioni sui tipi derivati, vedere Ereditarietà.
Se si implementano metodi di estensione per un determinato tipo, tenere presente i punti seguenti:
- Un metodo di estensione non viene chiamato se ha la stessa firma di un metodo definito nel tipo.
- I metodi di estensione vengono inseriti nell'ambito a livello di spazio dei nomi. Ad esempio, se sono presenti più classi statiche che contengono metodi di estensione in un singolo spazio dei nomi denominato
Extensions
, tutte vengono inserite nell'ambito dallausing Extensions;
direttiva .
Per una libreria di classi implementata, non è consigliabile usare metodi di estensione per evitare di incrementare il numero di versione di un assembly. Per aggiungere funzionalità significative a una libreria per cui si è proprietari del codice sorgente, seguire le linee guida .NET per il controllo delle versioni degli assembly. Per altre informazioni, vedere Controllo delle versioni degli assembly.
Vedere anche
- Esempi di programmazione parallela (molti esempi illustrano i metodi di estensione)
- Espressioni lambda
- Panoramica degli operatori di query standard
- Regole di conversione per i parametri dell'istanza e il relativo impatto
- Interoperabilità dei metodi di estensione tra linguaggi
- Metodi di estensione e delegati curried
- Metodo di estensione Binding e segnalazione errori