Il 0% ha trovato utile questo documento (0 voti)
310 visualizzazioni2.030 pagine

Libro C#

Caricato da

sestidavide.mail
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
310 visualizzazioni2.030 pagine

Libro C#

Caricato da

sestidavide.mail
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 2030

Contents

Documentazione di C#
Introduzione
Introduzione al linguaggio C# e .NET
Panoramica di C#
Introduzione
Tipi
Blocchi predefiniti del programma
Aree principali del linguaggio
Esercitazioni
Panoramica
Introduzione alla programmazione con C#
Scegliere la prima lezione
Hello world
Numeri in C#
Rami e cicli
Raccolte di elenchi
Lavorare nell'ambiente locale
Configurare l'ambiente
Numeri in C#
Rami e cicli
Raccolte di elenchi
Introduzione alle classi
Programmazione orientata a oggetti
Esplorare i tipi di record
Esplorare le istruzioni di primo livello
Esplorare i modelli negli oggetti
Esplorare l'interpolazione di stringhe - interattiva
Esplorare l'interpolazione di stringhe - nell'ambiente
Scenari avanzati per l'interpolazione di stringhe
Aggiornare le interfacce in modo sicuro con i metodi di interfaccia predefiniti
Creare la funzionalità mixin con i metodi di interfaccia predefiniti
Esplorare indici e intervalli
Usare tipi riferimento nullable
Aggiornare un'app per i tipi riferimento nullable
Generare e usare flussi asincroni
Creare algoritmi basati sui dati con criteri di ricerca
Applicazione console
REST Client (Client REST)
Ereditarietà in C# e .NET
Usare LINQ
Usare attributi
Novità di C#
C# 9.0
C# 8.0
C# 7.0-7.3
Modifiche importanti nel compilatore
Cronologia delle versioni di C#
Relazioni tra linguaggio e framework
Considerazioni su versione e aggiornamento
Concetti relativi a C#
Sistema di tipi C#
Tipi riferimento nullable
Scegliere una strategia per l'abilitazione dei tipi riferimento nullable
Spazi dei nomi
Tipi di base
Classi
Decostruzione di tuple e altri tipi
Interfacce
Metodi
Proprietà
Indicizzatori
Variabili discard
Generics
Iterators
Delegati ed eventi
Introduzione ai delegati
System.Delegate e parola chiave delegate
Delegati fortemente tipizzati
Modelli comuni per i delegati
Introduzione agli eventi
Schemi di eventi .NET standard
Schema di eventi .NET aggiornato
Distinzione di delegati ed eventi
LINQ (Language-Integrated Query)
Panoramica di LINQ
Nozioni fondamentali sulle espressioni di query
LINQ in C#
Scrivere query LINQ in C#
Eseguire query in una raccolta di oggetti
Ottenere una query da un metodo
Archiviare i risultati di una query in memoria
Raggruppare i risultati di una query
Creare un gruppo annidato
Eseguire una sottoquery su un'operazione di raggruppamento
Raggruppare i risultati per chiavi contigue
Specificare dinamicamente i filtri dei predicati in fase di esecuzione
Eseguire inner join
Eseguire join raggruppati
Eseguire left outer join
Ordinare i risultati di una clausola join
Eseguire un join usando chiavi composte
Eseguire operazioni di join personalizzate
Gestire i valori Null nelle espressioni di query
Gestire le eccezioni nelle espressioni di query
Programmazione asincrona
Criteri di ricerca
Scrivere codice efficiente e sicuro
Alberi delle espressioni
Introduzione agli alberi delle espressioni
Nozioni di base sugli alberi delle espressioni
Tipi di framework che supportano alberi delle espressioni
Esecuzione di espressioni
Interpretazione di espressioni
Creazione di espressioni
Conversione di espressioni
Riepilogo
Interoperabilità nativa
Documentazione del codice
Controllo delle versioni
Articoli sulle procedure C#
Indice degli articoli
Dividere le stringhe in sottostringhe
Concatenare le stringhe
Stringhe di ricerca
Modificare il contenuto delle stringhe
Confrontare le stringhe
Eseguire il cast sicuro con i criteri di ricerca e gli operatori is/as
.NET Compiler Platform SDK (API Roslyn)
Panoramica di .NET Compiler Platform SDK (API Roslyn)
Informazioni sul modello delle API del compilatore
Utilizzare la sintassi
Utilizzare la semantica
Utilizzare un'area di lavoro
Esplorare il codice con il visualizzatore di sintassi
Guide introduttive
Analisi della sintassi
Analisi semantica
Trasformazione della sintassi
Esercitazioni
Compilare il primo analizzatore e la prima correzione del codice
Guida per programmatori C#
Panoramica
Contenuto di un programma C#
Guida al contenuto di un programma C#
Hello World -- Il primo programma
Struttura generale di un programma C#
Nomi di identificatore
Convenzioni di codifica C#
Main() e argomenti della riga di comando
Panoramica
Argomenti della riga di comando
Come visualizzare gli argomenti della riga di comando
Valori restituiti da Main()
Concetti di programmazione
Panoramica
Programmazione asincrona
Panoramica
Modello di programmazione asincrona attività
Tipi restituiti asincroni
Annullare le attività
Annullare un elenco di attività
Annullare attività dopo un periodo di tempo
Elaborare le attività asincrone quando vengono completate
Accesso ai file asincroni
Attributi
Panoramica
Creazione di attributi personalizzati
Accesso agli attributi tramite reflection
Come creare un'unione C/C++ tramite attributi
Raccolte
Covarianza e controvarianza
Panoramica
Varianza nelle interfacce generiche
Creare interfacce generiche variant
Usare la varianza nelle interfacce per le raccolte generiche
Varianza nei delegati
Usare la varianza nei delegati
Usare la varianza per i delegati generici Func e Action
Alberi delle espressioni
Panoramica
Come eseguire alberi delle espressioni
Come modificare strutture ad albero dell'espressione
Come usare alberi delle espressioni per la compilazione di query dinamiche
Debug degli alberi delle espressioni in Visual Studio
Sintassi di DebugView
Iterators
LINQ (Language-Integrated Query)
Panoramica
Nozioni di base su LINQ in C#
Introduzione alle query LINQ
LINQ e tipi generici
Operazioni di query LINQ di base
Trasformazioni di dati con LINQ
Relazioni tra i tipi nelle operazioni di query LINQ
Sintassi di query e sintassi di metodi in LINQ
Funzionalità di C# che supportano LINQ
Procedura dettagliata: Scrittura di query in C# (LINQ)
Cenni preliminari sugli operatori di query standard
Panoramica
Sintassi di espressione della query per operatori di query standard
Classificazione di operatori di query standard in base alla modalità di esecuzione
Ordinamento dei dati
Operazioni sui set
Filtraggio dei dati
Operazioni del quantificatore
Operazioni di proiezione
Partizionamento dei dati
Operazioni di join
Raggruppamento dei dati
Operazioni di generazione
Operazioni di uguaglianza
Operazioni con gli elementi
Conversione del tipo di dati
Operazioni di concatenazione
Operazioni di aggregazione
LINQ to Objects
Panoramica
LINQ e stringhe
Articoli sulle procedure
Come contare le occorrenze di una parola in una stringa (LINQ)
Come eseguire una query per trovare frasi che contengono un set specificato
di parole (LINQ)
Come eseguire una query per trovare caratteri in una stringa (LINQ)
Come combinare query LINQ con espressioni regolari
Come trovare la differenza dei set tra due elenchi (LINQ)
Come ordinare o filtrare i dati di testo in base a qualsiasi parola o campo
(LINQ)
Come riordinare i campi di un file delimitato (LINQ)
Come combinare e confrontare raccolte di stringhe (LINQ)
Come popolare raccolte di oggetti da più origini (LINQ)
Come suddividere un file in molti file usando i gruppi (LINQ)
Come unire contenuto da file dissimili (LINQ)
Come calcolare i valori di colonna in un file di testo CSV (LINQ)
LINQ e Reflection
Come eseguire una query sui metadati di un assembly tramite reflection (LINQ)
Directory di file e LINQ
Panoramica
Come eseguire una query per trovare i file con un attributo o un nome
specifico
Come raggruppare i file in base all'estensione (LINQ)
Come eseguire una query per trovare il numero totale di byte in un set di
cartelle (LINQ)
Come confrontare il contenuto di due cartelle (LINQ)
Come eseguire una query per trovare il file o i file più grandi in un albero di
directory (LINQ)
Come eseguire una query per trovare i file duplicati in un albero di directory
(LINQ)
Come eseguire una query sul contenuto dei file in una cartella (LINQ)
Come eseguire una query su un ArrayList con LINQ
Come aggiungere metodi personalizzati per le query LINQ
LINQ to ADO.NET (pagina portale)
Abilitazione di un'origine dati per l'esecuzione di query LINQ
Supporto degli strumenti e IDE di Visual Studio per LINQ
Reflection
Serializzazione (C#)
Panoramica
Come scrivere dati oggetto in un file XML
Come leggere dati oggetto in un file XML
Procedura dettagliata: Persistenza di un oggetto in Visual Studio
Istruzioni, espressioni e operatori
Panoramica
Istruzioni
Membri con corpo di espressione
Funzioni anonime
Panoramica
Come usare espressioni lambda in una query
Uguaglianza e confronti di uguaglianza
Confronti di uguaglianza
Come definire l'uguaglianza di valori per un tipo
Come testare l'uguaglianza dei riferimenti (identità)
Tipi
Usare e definire i tipi
Cast e conversioni di tipi (C#)
Boxing e unboxing
Come convertire una matrice di byte in un Integer
Come convertire una stringa in un numero
Come eseguire la conversione tra stringhe esadecimali e tipi numerici
Utilizzo del tipo dinamico
Procedura dettagliata: Creazione e uso di oggetti dinamici (C# e Visual Basic)
Classi e struct
Panoramica
Classi
Oggetti
Ereditarietà
Polimorfismo
Panoramica
Controllo delle versioni con le parole chiave Override e New
Sapere quando utilizzare le parole chiave Override e New
Come eseguire l'override del metodo ToString
Membri
Panoramica dei membri
Classi e membri delle classi astratte e sealed
Classi statiche e membri di classi statiche
Modificatori di accesso
Campi
Costanti
Come definire proprietà astratte
Come definire le costanti in C#
Proprietà
Panoramica delle proprietà
Utilizzo delle proprietà
Proprietà dell'interfaccia
Limitazione dell'accessibilità delle funzioni di accesso
Come dichiarare e usare le proprietà di lettura/scrittura
Proprietà implementate automaticamente
Come implementare una classe leggera con proprietà implementate
automaticamente
Metodi
Panoramica dei membri
Funzioni locali
Valori restituiti e variabili locali ref
Parametri
Passaggio di parametri
Passaggio di parametri di tipi di valore
Passaggio di parametri di tipo di riferimento
Come differenziare tra il passaggio a un metodo di uno struct e di un riferimento
a una classe
Variabili locali tipizzate in modo implicito
Come usare variabili e matrici locali tipizzate in modo implicito in un'espressione di
query
Metodi di estensione
Come implementare e chiamare un metodo di estensione personalizzato
Come creare un nuovo metodo per un'enumerazione
Argomenti denominati e facoltativi
Come usare argomenti denominati e facoltativi nella programmazione di Office
Costruttori
Panoramica dei costruttori
Utilizzo di costruttori
Costruttori di istanza
Costruttori privati
Costruttori statici
Come scrivere un costruttore di copia
Finalizzatori
Inizializzatori di oggetto e di raccolta
Come inizializzare oggetti usando un inizializzatore di oggetto
Come Inizializzare un dizionario con un inizializzatore di raccolta
Tipi annidati
Classi e metodi parziali
Tipi anonimi
Come restituire sottoinsiemi di proprietà degli elementi in una query
Interfacce
Panoramica
Implementazione esplicita dell'interfaccia
Come implementare in modo esplicito i membri di interfaccia
Come implementare in modo esplicito i membri di due interfacce
Delegati
Panoramica
Utilizzo di delegati
Delegati con metodi denominati Metodi anonimi
Come combinare delegati (delegati multicast) (Guida per programmatori C#)
Come dichiarare un delegato, crearne un'istanza e usarlo
Matrici
Panoramica
Matrici unidimensionali
Matrici multidimensionali
Matrici irregolari
Uso di foreach con matrici
Passaggio di matrici come argomenti
Matrici tipizzate in modo implicito
Stringhe
Programmazione con stringhe
Come determinare se una stringa rappresenta un valore numerico
Indicizzatori
Panoramica
Utilizzo degli indicizzatori
Indicizzatori nelle interfacce
Confronto tra proprietà e indicizzatori
Events
Panoramica
Come eseguire e annullare la sottoscrizione a eventi
Come pubblicare eventi conformi alle linee guida di .NET
Come generare eventi di classe base nelle classi derivate
Come implementare eventi di interfaccia
Come implementare funzioni di accesso a eventi personalizzati
Generics
Panoramica
Parametri di tipo generico
Vincoli sui parametri di tipo
Classi generiche
Interfacce generiche
Metodi generici
Generics e matrici
Delegati generici
Differenze tra modelli C++ e generics C#
Generics in fase di esecuzione
Generics e reflection
Generics e attributi
Spazi dei nomi
Panoramica
Using Namespaces
Come usare lo spazio dei nomi My
Codice unsafe e puntatori
Panoramica e restrizioni
Buffer a dimensione fissa
Tipi puntatore
Panoramica
Conversioni puntatore
Come usare i puntatori per copiare una matrice di byte
Commenti relativi alla documentazione XML
Panoramica
Tag consigliati per i commenti relativi alla documentazione
Elaborazione del file XML
Delimitatori per i tag della documentazione
Come usare le funzionalità relative alla documentazione XML
Informazioni di riferimento sui tag per la documentazione
<c>
<code>
Attributo cref
<example>
<exception>
<include>
<inheritdoc>
<list>
<para>
<param>
<paramref>
<permission>
<remarks>
<returns>
<see>
<seealso>
<summary>
<typeparam>
<typeparamref>
<value>
Eccezioni e gestione delle eccezioni
Panoramica
Utilizzo di eccezioni
Gestione delle eccezioni
Creazione e generazione di eccezioni
Eccezioni generate dal compilatore
Come gestire un'eccezione usando try-catch
Come eseguire codice di pulitura mediante finally
Come intercettare un'eccezione non CLS
File system e Registro di sistema
Panoramica
Come scorrere un albero di directory
Come ottenere informazioni relative a file, cartelle e unità
Come creare un file o una cartella
Come copiare, eliminare e spostare file e cartelle
Come fornire una finestra di dialogo dello stato di avanzamento per operazioni su
file
Come scrivere in un file di testo
Come leggere da un file di testo
Come leggere un file di testo una riga alla volta
Come creare una chiave nel Registro di sistema
Interoperabilità
Interoperabilità .NET
Cenni preliminari sull'interoperabilità
Come accedere agli oggetti di interoperabilità di Office usando le funzionalità di C#
Come usare proprietà indicizzate nella programmazione dell'interoperabilità COM
Come usare platform invoke per riprodurre un file WAV
Procedura dettagliata: Programmazione di Office (C# e Visual Basic)
Esempio di classe COM
Riferimenti per il linguaggio
Panoramica
Configurare la versione del linguaggio
Tipi
Tipi valore
Panoramica
Tipi numerici integrali
Tipi numerici a virgola mobile
Conversioni numeriche predefinite
bool
char
Tipi di enumerazione
Tipi di struttura
Tipi tupla
Tipi valore nullable
Tipi riferimento
Funzionalità dei tipi riferimento
Tipi di riferimento predefiniti
classe
interfaccia
Tipi riferimento nullable
void
var
Tipi incorporati
Tipi non gestiti
Valori predefiniti
Parole chiave
Panoramica
Modificatori
Modificatori di accesso
Riferimento rapido
Livelli di accessibilità
Dominio di accessibilità
Restrizioni relative all'uso dei livelli di accessibilità
internal
private
protected
public
protected internal
protetto privato
abstract
async
const
event
extern
in (modificatore generico)
new (modificatore membro)
out (modificatore generico)
override
readonly
sealed
static
unsafe
virtuale
volatile
Parole chiave per le istruzioni
Categorie di istruzioni
Istruzioni di selezione
if-else
switch
Istruzioni di iterazione
do
for
foreach, in
while
Istruzioni di salto
break
continue
goto
return
Istruzioni di gestione delle eccezioni
throw
try-catch
try-finally
try-catch-finally
Checked e Unchecked
Panoramica
checked
unchecked
Istruzione fixed
Istruzione lock
Parametri di metodo
Passaggio di parametri
params
in (modificatore di parametro)
ref
out (modificatore di parametro)
Parole chiave per gli spazi dei nomi
namespace
using
Contesti per l'uso
Direttiva using
Direttiva using static
Istruzione using
extern alias
Parole chiave di test del tipo
is
Parole chiave del vincolo di tipo generico
Vincolo new
where
Parole chiave di accesso
base
this
Parole chiave letterali
Null
true e false
default
Parole chiave contestuali
Riferimento rapido
add
get
partial (Tipo)
partial (metodo)
remove
set
when (condizione di filtro)
value
yield
Parole chiave per le query
Riferimento rapido
Clausola from
Clausola where
Clausola select
Clausola group
into
Clausola orderby
Clausola join
Clausola let
ascending
descending
on
equals
by
in
Operatori ed espressioni
Panoramica
Operatori aritmetici
Operatori logici booleani
Operatori di spostamento e bit per bit
Operatori di uguaglianza
Operatori di confronto
Espressioni e operatori di accesso ai membri
Operatori per i test dei tipi ed espressione cast
Operatori di conversione definiti dall'utente
Operatori correlati al puntatore
Operatori di assegnazione
Espressioni lambda
+ e operatori +=
- e -= (operatori)
Operatore ?:
! (operatore null-forgiving)
?? e operatori ??=
Operatore =>
:: (operatore)
await (operatore)
espressioni con valore predefinito
Operatore delegate
Espressione nameof
Operatore new
Operatore sizeof
Espressione stackalloc
Espressione switch
Operatori true e false
Espressione with
Overload degli operatori
Caratteri speciali
Panoramica
$ -- interpolazione di stringhe
@ -- identificatore verbatim
Attributi letti dal compilatore
Attributi globali
Generale
Informazioni sul chiamante
Analisi statica nullable
Direttive per il preprocessore
Panoramica
#if
#else
#elif
#endif
#define
#undef
#warning
#error
#line
#nullable
#region
#endregion
#pragma
#pragma warning
#pragma checksum
Opzioni del compilatore
Panoramica
Compilazione dalla riga di comando con csc.exe
Come impostare le variabili di ambiente per la riga di comando di Visual Studio
Opzioni del compilatore C# elencate per categoria
Opzioni del compilatore C# in ordine alfabetico
@
-addmodule
-appconfig
-baseaddress
-bugreport
-checked
-codepage
-debug
-define
-delaysign
-deterministic
-doc
-errorreport
-filealign
-fullpaths
-help, -?
-highentropyva
-keycontainer
-keyfile
-langversion
-lib
-link
-linkresource
-main
-moduleassemblyname
-noconfig
-nologo
-nostdlib
-nowarn
-nowin32manifest
-nullable
-optimize
-out
-pathmap
-pdb
-platform
-preferreduilang
-publicsign
-recurse
-reference
-refout
-refonly
-resource
-subsystemversion
-target
-target:appcontainerexe
-target:exe
-target:library
-target:module
-target:winexe
-target:winmdobj
-unsafe
-utf8output
-warn
-warnaserror
-win32icon
-win32manifest
-win32res
Errori del compilatore
Bozza di specifica C# 6.0
Proposte per C# 7.0 - 9.0
Procedure dettagliate
Introduzione al linguaggio C# e .NET
02/11/2020 • 8 minutes to read • Edit Online

C# è un linguaggio orientato a oggetti elegante e indipendente dai tipi. C# consente agli sviluppatori di
compilare molti tipi di applicazioni sicure e affidabili eseguite nell'ecosistema .NET.

Linguaggio C#
La sintassi di C# è altamente espressiva, ma è anche semplice e facile da imparare. La sintassi di parentesi graffe
di C# sarà immediatamente riconoscibile per chiunque abbia familiarità con C, C++, Java o JavaScript. Gli
sviluppatori che conoscono uno di questi linguaggi sono in genere in grado di lavorare in modo produttivo in
C# in breve tempo. C# offre funzionalità avanzate, ad esempio tipi nullable, delegati, espressioni lambda, criteri
di ricerca e accesso diretto a memoria sicura. C# supporta i tipi e i metodi generici, che garantiscono una
maggiore indipendenza dai tipi e prestazioni. C# fornisce gli iteratori, che consentono ai responsabili
dell'implementazione di classi Collection di definire comportamenti personalizzati per il codice client. Le
espressioni Language-Integrated query (LINQ) rendono la query fortemente tipizzata un costrutto di linguaggio
di prima classe.
Essendo un linguaggio orientato a oggetti, C# supporta i concetti di incapsulamento, ereditarietà e
polimorfismo. Una classe può ereditare direttamente da un'unica classe padre, ma può implementare un
numero qualsiasi di interfacce. Per evitare una ridefinizione accidentale, i metodi che eseguono l'override di
metodi virtuali in una classe padre richiedono la parola chiave override . In C# uno struct è analogo a una
classe Lightweight; si tratta di un tipo allocato nello stack che può implementare interfacce, ma non supporta
l'ereditarietà. C# fornisce anche record, che sono tipi di classe il cui scopo è archiviare principalmente i valori dei
dati.
C# semplifica lo sviluppo di componenti software tramite diversi costrutti di linguaggio innovativi, tra cui:
Firme del metodo incapsulate, denominate delegati, che consentono le notifiche degli eventi indipendenti dai
tipi.
Proprietà, che fungono da funzioni di accesso per le variabili membro private.
Attributi, che forniscono metadati dichiarativi sui tipi in fase di esecuzione.
Commenti inline relativi alla documentazione XML.
Language-Integrated query (LINQ), che fornisce funzionalità di query incorporate in diversi tipi di origini dati.
Criteri di ricerca che consentono al flusso di controllo di esaminare i tipi di dati e i valori.
Si interagisce con i componenti nativi tramite un processo denominato "Interop". Interop permette ai
programmi C# di eseguire sostanzialmente qualsiasi operazione consentita a un'applicazione C++ nativa. C#
supporta anche i puntatori e il concetto di codice "non sicuro" per i casi in cui l'accesso diretto alla memoria è
fondamentale.
Il processo di compilazione di C# è più semplice rispetto a quelli di C e C++ e più flessibile rispetto a quello di
Java. Non sono previsti file di intestazione separati e non è necessario che i metodi e i tipi vengano dichiarati in
un ordine specifico. In un file di origine C# è possibile definire un numero qualsiasi di classi, struct, interfacce ed
eventi.
Di seguito sono riportate alcune risorse aggiuntive su C#:
Per un'introduzione generale al linguaggio, vedere la Panoramica di C#.
Per informazioni dettagliate su aspetti specifici del linguaggio C#, vedere Riferimenti per C#.
Per ulteriori informazioni su LINQ, vedere LINQ (Language-Integrated Query).
Architettura della piattaforma .NET
I programmi C# vengono eseguiti in .NET, un sistema di esecuzione virtuale denominato Common Language
Runtime (CLR) e un set unificato di librerie di classi. CLR rappresenta l'implementazione commerciale di
Microsoft dell'infrastruttura CLI (Common Language Infrastructure), uno standard internazionale. L'interfaccia
della riga di comando costituisce la base per la creazione di ambienti di esecuzione e sviluppo in cui linguaggi e
librerie interagiscono in modo uniforme.
Il codice sorgente scritto in C# viene compilato in un linguaggio intermedio (il) conforme alla specifica
dell'interfaccia della riga di comando. Il codice IL e le risorse, ad esempio bitmap e stringhe, vengono archiviati
in un assembly, in genere con estensione. dll. Un assembly contiene un manifesto che fornisce informazioni sui
tipi, la versione e le impostazioni cultura dell'assembly.
Quando viene eseguito il programma C#, l'assembly viene caricato in CLR. CLR esegue la compilazione JIT ( just-
in-Time) per convertire il codice IL in istruzioni computer native. CLR fornisce altri servizi correlati alla Garbage
Collection automatica, alla gestione delle eccezioni e alla gestione delle risorse. Il codice eseguito dal CLR viene
talvolta definito "codice gestito", a differenza del "codice non gestito", che viene compilato nel linguaggio
macchina nativo destinato a un sistema specifico.
L'interoperabilità del linguaggio è una funzionalità chiave di .NET. Il codice IL prodotto dal compilatore C# è
conforme a CTS (Common Type Specification). IL codice IL generato da C# può interagire con il codice generato
dalle versioni .NET di F #, Visual Basic, C++ o da più di 20 linguaggi conformi a CTS. Un singolo assembly può
contenere più moduli scritti in linguaggi .NET diversi e i tipi possono farvi riferimento come se fossero scritti
nella stessa lingua.
Oltre ai servizi di runtime, .NET include anche librerie estese. Queste librerie supportano molti carichi di lavoro
diversi. Sono organizzati in spazi dei nomi che offrono un'ampia gamma di funzionalità utili, tra cui l'input e
l'output di file alla manipolazione di stringhe per l'analisi XML, a Framework applicazione Web per Windows
Forms controlli. La tipica applicazione C# usa ampiamente la libreria di classi .NET per gestire le faccende
comuni "plumbing".
Per ulteriori informazioni su .NET, vedere Panoramica di .NET.
Panoramica del linguaggio C#
02/11/2020 • 22 minutes to read • Edit Online

C# (pronunciato "See Sharp") è un linguaggio di programmazione moderno, orientato a oggetti e indipendente


dai tipi. C# ha le sue radici nella famiglia di linguaggi C e risulterà immediatamente familiare ai programmatori
di C, C++, Java e JavaScript. In questa presentazione viene fornita una panoramica dei componenti principali del
linguaggio in C# 8 e versioni precedenti. Per esplorare il linguaggio tramite esempi interattivi, provare a
eseguire le esercitazioni di introduzione a C# .
C# è un linguaggio di programmazione orientato a oggetti e orientato ai componenti . C# fornisce costrutti di
linguaggio per supportare direttamente questi concetti, rendendo C# un linguaggio naturale in cui creare e
utilizzare i componenti software. Dal momento che ha originato, C# ha aggiunto funzionalità per supportare
nuovi carichi di lavoro e procedure di progettazione software emergenti.
Diverse funzionalità C# facilitano la costruzione di applicazioni solide e durevoli. Garbage Collection recupera
automaticamente la memoria occupata dagli oggetti inutilizzati non raggiungibili. La gestione delle eccezioni
fornisce un approccio strutturato ed estendibile per il rilevamento e il ripristino degli errori. Le espressioni
lambda supportano le tecniche di programmazione funzionale. La sintassi di quer y consente di creare un
modello comune per lavorare con i dati di qualsiasi origine. Il supporto delle lingue per le operazioni
asincrone fornisce la sintassi per la creazione di sistemi distribuiti. Criteri di ricerca fornisce la sintassi per
separare facilmente i dati dagli algoritmi nei sistemi distribuiti moderni. C# dispone di un sistema di tipi
unificato . Tutti i tipi C#, inclusi i tipi di primitiva quali int e double , ereditano da un unico tipo object radice.
Tutti i tipi condividono un set di operazioni comuni. I valori di qualsiasi tipo possono essere archiviati, trasportati
e gestiti in modo coerente. C# supporta inoltre i tipi di riferimento definiti dall'utente e i tipi di valore. C#
consente l'allocazione dinamica di oggetti e l'archiviazione inline di strutture semplici.
C# enfatizza il controllo delle versioni per garantire che i programmi e le librerie possano evolversi nel
tempo in modo compatibile. Gli aspetti della progettazione di C# che sono stati influenzati direttamente dalle
considerazioni sul controllo delle versioni includono i virtual override modificatori e separati, le regole per la
risoluzione dell'overload del metodo e il supporto per le dichiarazioni esplicite dei membri di interfaccia.

Hello world
Il programma "Hello World" viene tradizionalmente usato per presentare un linguaggio di programmazione. Di
seguito è riportato il programma Hello, World in C#:

using System;

class Hello
{
static void Main()
{
Console.WriteLine("Hello, World");
}
}

Il programma "Hello World" inizia con una direttiva using che fa riferimento allo spazio dei nomi System . Gli
spazi dei nomi consentono di organizzare i programmi e le librerie C# in modo gerarchico. Gli spazi dei nomi
contengono tipi e altri spazi dei nomi. Lo stazio dei nomi System , ad esempio, contiene diversi tipi, come la
classe Console a cui viene fatto riferimento nel programma, e altri spazi dei nomi, come IO e Collections .
Una direttiva using che fa riferimento a un determinato spazio dei nomi consente l'uso non qualificato dei tipi
che sono membri di tale spazio dei nomi. Grazie alla direttiva using , il programma può usare
Console.WriteLine come sintassi abbreviata per System.Console.WriteLine .

La classe Hello dichiarata dal programma "Hello World" ha un solo membro, ovvero il metodo denominato
Main . Il Main metodo viene dichiarato con il static modificatore. Mentre i metodi di istanza possono fare
riferimento a una particolare istanza dell'oggetto contenitore usando la parola chiave this , i metodi statici
operano senza riferimento a un determinato oggetto. Per convenzione, un metodo statico denominato Main
funge da punto di ingresso di un programma C#.
L'output del programma viene prodotto dal metodo WriteLine della classe Console nello spazio dei nomi
System . Questa classe viene fornita da librerie di classi standard a cui, per impostazione predefinita, fa
automaticamente riferimento il compilatore.

Tipi e variabili
In C# esistono due generi di tipi: tipi valore e tipi riferimento. Le variabili dei tipi valore contengono direttamente
i propri dati, mentre le variabili dei tipi riferimento archiviano i riferimenti ai propri dati, noti come oggetti. Con i
tipi di riferimento, è possibile che due variabili facciano riferimento allo stesso oggetto ed è possibile che le
operazioni su una variabile influiscano sull'oggetto a cui fa riferimento l'altra variabile. Con i tipi valore, ogni
variabile ha una propria copia dei dati e non è possibile che le operazioni su uno influiscano sull'altro (ad
eccezione ref delle out variabili di parametro e).
Un identificatore è un nome di variabile. Un identificatore è una sequenza di caratteri Unicode senza spazi
vuoti. Un identificatore può essere una parola riservata C#, se è preceduta da @ . Questo può essere utile
quando si interagisce con altri linguaggi.
I tipi di valore di C# sono ulteriormente divisi in tipi semplici, tipi enum, tipi struct, tipi di valore Nullable e tipi di
valore di tupla. I tipi di riferimento di C# sono ulteriormente divisi in tipi di classe, tipi di interfaccia, tipi di
matricie tipi delegati.
La struttura seguente fornisce una panoramica del sistema di tipi di C#.
Tipi valore
Tipi semplici
Integrale con segno: sbyte , short , int , long
Integrale senza segno: byte , ushort , uint , ulong
Caratteri Unicode: char , che rappresenta un'unità di codice UTF-16
Virgola mobile binario IEEE: float , double
Virgola mobile decimale a precisione elevata: decimal
Booleano: bool , che rappresenta valori booleani, valori che sono true o false
Tipi enum
Tipi definiti dall'utente del modulo enum E {...} . Un tipo enum è un tipo distinto con costanti
denominate. Ogni tipo enum ha un tipo sottostante, che deve essere uno degli otto tipi
integrali. Il set di valori di un tipo enum coincide con il set di valori del tipo sottostante.
Tipi struct
Tipi definiti dall'utente nel formato struct S {...}
Tipi valore nullable
Estensioni di tutti gli altri tipi valore con un valore null
Tipi di valore di tupla
Tipi definiti dall'utente nel formato (T1, T2, ...)
Tipi riferimento
Tipi di classe
Classe di base principale di tutti gli altri tipi: object
Stringhe Unicode: string , che rappresenta una sequenza di unità di codice UTF-16
Tipi definiti dall'utente nel formato class C {...}
Tipi di interfaccia
Tipi definiti dall'utente nel formato interface I {...}
Tipi di matrice
Unidimensionale, multidimensionale e irregolare. Ad esempio: int[] , int[,] e int[][]
Tipi delegato
Tipi definiti dall'utente nel formato delegate int D(...)
I programmi C# usano le dichiarazioni di tipo per creare nuovi tipi. Una dichiarazione di tipo consente di
specificare il nome e i membri del nuovo tipo. Sei categorie di tipi C# sono definibili dall'utente: tipi di classe, tipi
di struct, tipi di interfaccia, tipi enum, tipi delegati e tipi di valore di tupla.
Un tipo class definisce una struttura dati contenente membri dati (campi) e membri funzione (metodi,
proprietà e altro). I tipi classe supportano l'ereditarietà singola e il polimorfismo, meccanismi in base ai quali
le classi derivate possono estendere e specializzare le classi di base.
Un tipo struct è simile a un tipo classe in quanto rappresenta una struttura con membri dati e membri
funzione. Tuttavia, a differenza delle classi, gli struct sono tipi di valore e in genere non richiedono
l'allocazione dell'heap. I tipi struct non supportano l'ereditarietà specificata dall'utente e tutti i tipi struct
ereditano implicitamente dal tipo object .
Un interface tipo definisce un contratto come un set denominato di membri pubblici. Un oggetto class o
struct che implementa un oggetto interface deve fornire implementazioni dei membri dell'interfaccia. Un
tipo interface può ereditare da più interfacce di base e un tipo class o struct può implementare più
interfacce.
Un tipo delegate rappresenta i riferimenti ai metodi, con un elenco di parametri e un tipo restituito
particolari. I delegati consentono di trattare i metodi come entità che è possibile assegnare a variabili e
passare come parametri. I delegati sono analoghi ai tipi funzione forniti dai linguaggi funzionali. Sono anche
simili al concetto di puntatori a funzione disponibili in altri linguaggi. A differenza dei puntatori a funzione, i
delegati sono orientati agli oggetti e indipendenti dai tipi.
I class tipi,, struct interface e delegate supportano tutti i generics, in base ai quali possono essere
parametrizzati con altri tipi.
C# supporta matrici unidimensionali e multidimensionali di qualsiasi tipo. A differenza dei tipi elencati in
precedenza, i tipi di matrice non devono essere dichiarati prima di poter essere usati. Al contrario, i tipi matrice
vengono costruiti facendo seguire a un nome di tipo delle parentesi quadre. Ad esempio, int[] è una matrice
unidimensionale di int , int[,] è una matrice bidimensionale di int e int[][] è una matrice
unidimensionale di matrici unidimensionali o una matrice "frastagliata" di int .
I tipi nullable non richiedono una definizione separata. Per ogni tipo non Nullable T , esiste un tipo nullable
corrispondente T? , che può avere un valore aggiuntivo, null . Ad esempio, int? è un tipo che può ospitare
qualsiasi intero a 32 bit o il valore null e string? è un tipo che può conservare qualsiasi string valore o
null .

Il sistema di tipi di C# è unificato in modo che un valore di qualsiasi tipo possa essere considerato come un
object . In C# ogni tipo deriva direttamente o indirettamente dal tipo classe object e object è la classe di
base principale di tutti i tipi. I valori dei tipi riferimento vengono trattati come oggetti semplicemente
visualizzando tali valori come tipi object . I valori dei tipi valore vengono trattati come oggetti mediante
l'esecuzione di operazioni di boxing e unboxing. Nell'esempio seguente un valore int viene convertito in
object e quindi convertito nuovamente in int .
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing

Quando un valore di un tipo di valore viene assegnato a un object riferimento, viene allocata una "casella" per
conservare il valore. Questa casella è un'istanza di un tipo di riferimento e il valore viene copiato in tale casella.
Viceversa, quando object viene eseguito il cast di un riferimento a un tipo valore, viene eseguito un controllo
che l'oggetto a cui si fa riferimento object è una casella del tipo di valore corretto. Se il controllo ha esito
positivo, il valore nella casella viene copiato nel tipo di valore.
Il sistema di tipi unificato di C# significa che i tipi di valore vengono considerati come object Riferimenti "su
richiesta". A causa dell'unificazione, le librerie di uso generico che usano il tipo object possono essere usate
con tutti i tipi che derivano da object , inclusi sia i tipi di riferimento che i tipi di valore.
In C# sono disponibili diversi tipi di variabili, inclusi campi, elementi matrice, variabili locali e parametri. Le
variabili rappresentano posizioni di archiviazione. Ogni variabile dispone di un tipo che determina quali valori
possono essere archiviati nella variabile, come mostrato di seguito.
Tipo valore non-nullable
Valore esattamente del tipo indicato
Tipo valore nullable
Valore null o valore esattamente del tipo indicato
object
Riferimento null , riferimento a un oggetto di qualsiasi tipo riferimento oppure riferimento a un
valore boxed di qualsiasi tipo valore
Tipo classe
Riferimento null , riferimento a un'istanza del tipo classe oppure riferimento a un'istanza di una
classe derivata dal tipo classe
Tipo interfaccia
Riferimento null , riferimento a un'istanza di un tipo classe che implementa il tipo interfaccia oppure
riferimento a un valore boxed di un tipo valore che implementa il tipo interfaccia
Tipo matrice
Riferimento null , riferimento a un'istanza del tipo matrice oppure riferimento a un'istanza di un tipo
matrice compatibile
Tipo delegato
Riferimento null oppure riferimento a un'istanza di un tipo delegato compatibile

Struttura del programma


I concetti aziendali chiave in C# sono i programmi , gli spazi dei nomi , i tipi , i membri e gli assembly . I
programmi dichiarano i tipi, che contengono i membri e possono essere organizzati in spazi dei nomi. Classi,
struct e interfacce sono esempi di tipi. I campi, i metodi, le proprietà e gli eventi sono esempi di membri. Quando
i programmi C# vengono compilati, vengono inseriti fisicamente in assembly. Gli assembly in genere hanno
l'estensione .exe o .dll , a seconda che implementino rispettivamente applicazioni o librerie .
Come piccolo esempio, si consideri un assembly che contiene il codice seguente:
using System;

namespace Acme.Collections
{
public class Stack<T>
{
Entry _top;

public void Push(T data)


{
_top = new Entry(_top, data);
}

public T Pop()
{
if (_top == null)
{
throw new InvalidOperationException();
}
T result = _top.Data;
_top = _top.Next;

return result;
}

class Entry
{
public Entry Next { get; set; }
public T Data { get; set; }

public Entry(Entry next, T data)


{
Next = next;
Data = data;
}
}
}
}

Il nome completo di questa classe è Acme.Collections.Stack . La classe contiene vari membri: un campo top ,
due metodi Push e Pop e una classe annidata Entry . La classe Entry contiene altri tre membri: un campo
next , un campo data e un costruttore. Stack È una classe generica . Ha un parametro di tipo, T che viene
sostituito con un tipo concreto quando viene usato.

NOTE
Uno stack è una raccolta "First in-last out" (filo). I nuovi elementi vengono aggiunti all'inizio dello stack. Quando un
elemento viene rimosso, viene rimosso dall'inizio dello stack.

Gli assembly contengono codice eseguibile sotto forma di istruzioni di linguaggio intermedio (Intermediate
Language, IL) e informazioni simboliche sotto forma di metadati. Prima che venga eseguita, il compilatore JIT
( just-in-Time) di .NET Common Language Runtime converte il codice IL in un assembly in codice specifico del
processore.
Poiché un assembly è un'unità di funzionalità autodescrittiva che contiene sia il codice che i metadati, non sono
necessarie #include direttive e file di intestazione in C#. I membri e i tipi pubblici contenuti in un determinato
assembly vengono resi disponibili in un programma C# semplicemente facendo riferimento a tale assembly
durante la compilazione del programma. Questo programma usa ad esempio la classe Acme.Collections.Stack
dell'assembly acme.dll :
using System;
using Acme.Collections;

class Example
{
public static void Main()
{
var s = new Stack<int>();
s.Push(1); // stack contains 1
s.Push(10); // stack contains 1, 10
s.Push(100); // stack contains 1, 10, 100
Console.WriteLine(s.Pop()); // stack contains 1, 10
Console.WriteLine(s.Pop()); // stack contains 1
Console.WriteLine(s.Pop()); // stack is empty
}
}

Per compilare il programma, è necessario fare riferimento all'assembly contenente la classe Stack definita
nell'esempio precedente.
I programmi C# possono essere archiviati in diversi file di origine. Quando viene compilato un programma C#,
vengono elaborati insieme tutti i file di origine e i file di origine possono farvi riferimento liberamente.
Concettualmente, è come se tutti i file di origine fossero concatenati in un file di grandi dimensioni prima di
essere elaborati. In C# non sono mai necessarie dichiarazioni con più edizioni perché, con poche eccezioni,
l'ordine di dichiarazione non è significativo. C# non limita un file di origine alla dichiarazione di un solo tipo
pubblico né richiede che il nome del file di origine corrisponda a un tipo dichiarato nel file di origine.
Altri articoli in questa presentazione spiegano questi blocchi organizzativi.

AVA N TI
Tipi e membri
28/01/2021 • 11 minutes to read • Edit Online

Classi e oggetti
Le classi sono il tipo C# più importante. Una classe è una struttura di dati che combina in una singola unità lo
stato (campi) e le azioni (metodi e altri membri di funzione). Una classe fornisce una definizione per le istanze
della classe, note anche come oggetti. Le classi supportano l'ereditarietà e il polimorfismo, meccanismi in base
ai quali le classi derivate possono estendere e specializzare le classi di base.
Le nuove classi vengono create tramite dichiarazioni di classe. Una dichiarazione di classe inizia con
un'intestazione. L'intestazione specifica:
Attributi e modificatori della classe
Nome della classe.
Classe base (in caso di ereditarietà da una classe base)
Interfacce implementate dalla classe.
L'intestazione è seguita dal corpo della classe, costituito da un elenco di dichiarazioni di membro scritte tra i
delimitatori { e } .
Nel codice seguente viene illustrata una dichiarazione di una classe semplice denominata Point :

public class Point


{
public int X { get; }
public int Y { get; }

public Point(int x, int y) => (X, Y) = (x, y);


}

Le istanze delle classi vengono create usando l'operatore new , che alloca memoria per una nuova istanza,
richiama un costruttore per inizializzare l'istanza e restituisce un riferimento all'istanza. Le istruzioni seguenti
creano due Point oggetti e archiviano i riferimenti a tali oggetti in due variabili:

var p1 = new Point(0, 0);


var p2 = new Point(10, 20);

La memoria occupata da un oggetto viene automaticamente recuperata nel momento in cui l'oggetto non è più
raggiungibile. Non è né necessario né possibile deallocare in modo esplicito gli oggetti in C#.
Parametri di tipo
Le classi generiche definiscono * parametri di tipo _. I parametri di tipo sono un elenco di nomi di parametri di
tipo racchiusi tra parentesi angolari. I parametri di tipo seguono il nome della classe. I parametri di tipo possono
essere quindi usati nel corpo delle dichiarazioni di classe per definire i membri della classe. Nell'esempio
seguente i parametri di tipo di Pair sono TFirst e TSecond :
public class Pair<TFirst, TSecond>
{
public TFirst First { get; }
public TSecond Second { get; }

public Pair(TFirst first, TSecond second) =>


(First, Second) = (first, second);
}

Un tipo di classe dichiarato per accetta parametri di tipo è denominato tipo classe _generic *. I tipi struct,
Interface e Delegate possono anche essere generici. Quando si usa la classe generica, è necessario specificare
argomenti di tipo per ogni parametro di tipo:

var pair = new Pair<int, string>(1, "two");


int i = pair.First; // TFirst int
string s = pair.Second; // TSecond string

Un tipo generico per il quale sono stati specificati argomenti di tipo, come Pair<int,string> nell'esempio
precedente, prende il nome di tipo costruito.
Classi di base
Una dichiarazione di classe può specificare una classe di base. Seguire il nome della classe e i parametri di tipo
con i due punti e il nome della classe di base. L'omissione della specifica della classe di base equivale alla
derivazione dal tipo object . Nell'esempio seguente la classe di base di Point3D è Point . Dal primo esempio,
la classe base di Point è object :

public class Point3D : Point


{
public int Z { get; set; }

public Point3D(int x, int y, int z) : base(x, y)


{
Z = z;
}
}

Una classe eredita i membri della relativa classe di base. L'ereditarietà indica che una classe contiene in modo
implicito quasi tutti i membri della relativa classe di base. Una classe non eredita l'istanza e i costruttori statici e
il finalizzatore. Una classe derivata può aggiungere nuovi membri ai membri che eredita, ma non può rimuovere
la definizione di un membro ereditato. Nell'esempio precedente Point3D eredita i X Y membri e da Point e
ogni Point3D istanza contiene tre proprietà,, X Y e Z .
Un tipo di classe viene implicitamente convertito in uno dei relativi tipi di classe di base. Una variabile di un tipo
di classe può fare riferimento a un'istanza della classe o a un'istanza di una classe derivata. Nel caso delle
dichiarazioni di classe precedenti, ad esempio, una variabile di tipo Point può fare riferimento a Point o
Point3D :

Point a = new Point(10, 20);


Point b = new Point3D(10, 20, 30);

Struct
Le classi definiscono i tipi che supportano l'ereditarietà e il polimorfismo. Consentono di creare comportamenti
sofisticati basati su gerarchie di classi derivate. Al contrario, i tipi * struct sono più semplici, il cui scopo
principale è archiviare i valori dei dati. Gli struct non possono dichiarare un tipo di base; derivano in modo
implicito da System.ValueType . Non è possibile derivare altri struct tipi da un struct tipo. Sono
implicitamente sealed.

public struct Point


{
public double X { get; }
public double Y { get; }

public Point(double x, double y) => (X, Y) = (x, y);


}

Interfacce
Un' interfaccia definisce un contratto che può essere implementato da classi e struct. Può contenere metodi,
proprietà, eventi e indicizzatori. Un'interfaccia in genere non fornisce implementazioni dei membri che definisce:
specifica semplicemente i membri che devono essere forniti da classi o struct che implementano l'interfaccia.
Le interfacce possono utilizzare più ereditarietà. Nell'esempio seguente l'interfaccia IComboBox eredita da
ITextBox e IListBox .

interface IControl
{
void Paint();
}

interface ITextBox : IControl


{
void SetText(string text);
}

interface IListBox : IControl


{
void SetItems(string[] items);
}

interface IComboBox : ITextBox, IListBox { }

Classi e struct possono implementare più interfacce. Nell'esempio seguente la classe EditBox implementa
IControl e IDataBound .

interface IDataBound
{
void Bind(Binder b);
}

public class EditBox : IControl, IDataBound


{
public void Paint() { }
public void Bind(Binder b) { }
}

Quando una classe o un tipo struct implementa un'interfaccia specifica, le istanze di tale classe o struct possono
essere convertite in modo implicito in quel tipo di interfaccia. Ad esempio:
EditBox editBox = new EditBox();
IControl control = editBox;
IDataBound dataBound = editBox;

Enumerazioni
Un tipo enum definisce un set di valori costanti. Il codice seguente enum dichiara costanti che definiscono
ortaggi radice diversi:

public enum SomeRootVegetable


{
HorseRadish,
Radish,
Turnip
}

È inoltre possibile definire un oggetto enum da utilizzare in combinazione come flag. La dichiarazione seguente
dichiara un set di flag per le quattro stagioni. È possibile applicare qualsiasi combinazione di stagioni, incluso un
All valore che includa tutte le stagioni:

[Flags]
public enum Seasons
{
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
All = Summer | Autumn | Winter | Spring
}

Nell'esempio seguente vengono illustrate le dichiarazioni di entrambe le enumerazioni precedenti:

var turnip = SomeRootVegetable.Turnip;

var spring = Seasons.Spring;


var startingOnEquinox = Seasons.Spring | Seasons.Autumn;
var theYear = Seasons.All;

Tipi nullable
Le variabili di qualsiasi tipo possono essere dichiarate come non nullable o ammettono valori null. Una variabile
nullable può mantenere un null valore aggiuntivo, che non indica alcun valore. I tipi di valore Nullable (struct o
enum) sono rappresentati da System.Nullable<T> . I tipi di riferimento non nullable e nullable sono entrambi
rappresentati dal tipo di riferimento sottostante. La distinzione è rappresentata dai metadati letti dal compilatore
e da alcune librerie. Il compilatore fornisce avvisi quando i riferimenti nullable vengono dereferenziati senza
prima verificare il relativo valore null . Il compilatore fornisce anche avvisi quando a un riferimento non
nullable viene assegnato un valore che può essere null . Nell'esempio seguente viene dichiarato un valore
nullable int, che viene inizializzato in null . Imposta quindi il valore su 5 . Viene illustrato lo stesso concetto
con una stringa Nullable. Per altre informazioni, vedere tipi di valore Nullable e tipi di riferimento Nullable.
int? optionalInt = default;
optionalInt = 5;
string? optionalText = default;
optionalText = "Hello World.";

Tuple
C# supporta _ Tuple *, che fornisce sintassi concisa per raggruppare più elementi di dati in una struttura di dati
lightweight. Per creare un'istanza di una tupla, dichiarare i tipi e i nomi dei membri tra ( e ) , come illustrato
nell'esempio seguente:

(double Sum, int Count) t2 = (4.5, 3);


Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Le tuple rappresentano un'alternativa per la struttura dei dati con più membri, senza usare i blocchi predefiniti
descritti nell'articolo successivo.

P R E C E D E N TE AVA N TI
Blocchi predefiniti del programma
28/01/2021 • 42 minutes to read • Edit Online

I tipi descritti nell'articolo precedente vengono compilati usando questi blocchi predefiniti: * membri _,
espressioni e istruzioni.

Membri
I membri di un oggetto class sono membri statici o membri di istanza. I primi appartengono a classi, mentre i
secondi appartengono a oggetti, ovvero a istanze di classi.
Nell'elenco seguente viene fornita una panoramica dei tipi di membri che possono essere contenuti in una
classe.
_ * Costanti * *: valori costanti associati alla classe
Fields : variabili associate alla classe
Metodi : azioni che possono essere eseguite dalla classe
Proprietà : azioni associate alla lettura e alla scrittura di proprietà denominate della classe
Indicizzatori : azioni associate a istanze di indicizzazione della classe come una matrice
Eventi : notifiche che possono essere generate dalla classe
Operatori : conversioni e operatori di espressione supportati dalla classe
Costruttori : azioni necessarie per inizializzare istanze della classe o della classe stessa
Finalizzatori : le azioni eseguite prima delle istanze della classe vengono eliminate definitivamente
Tipi : tipi annidati dichiarati dalla classe

Accessibilità
Ogni membro di una classe dispone di un'accessibilità associata, che controlla le aree del testo del programma
che possono accedere al membro. Esistono sei diverse forme di accessibilità, I modificatori di accesso sono
riepilogati di seguito.
public : L'accesso non è limitato.
private : L'accesso è limitato a questa classe.
protected : L'accesso è limitato a questa classe o alle classi derivate da questa classe.
internal : L'accesso è limitato all'assembly corrente ( .exe o .dll ).
protected internal : L'accesso è limitato a questa classe, le classi derivate da questa classe o le classi
all'interno dello stesso assembly.
private protected : L'accesso è limitato a questa classe o alle classi derivate da questo tipo all'interno dello
stesso assembly.

Campi
Un campo è una variabile associata a una classe o a un'istanza di una classe.
Un campo dichiarato con il modificatore static definisce un campo statico, che identifica esattamente una
posizione di memoria. Indipendentemente dal numero di istanze di una classe create, esiste una sola copia di un
campo statico.
Un campo dichiarato senza il modificatore static definisce un campo di istanza. Ogni istanza di una classe
contiene una copia separata di tutti i campi di istanza della classe.
Nell'esempio seguente, ogni istanza della Color classe dispone di una copia separata dei campi di R G
istanza, e B , ma è presente una sola copia dei Black campi statici,, White , Red Green e Blue :

public class Color


{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);

public byte R;
public byte G;
public byte B;

public Color(byte r, byte g, byte b)


{
R = r;
G = g;
B = b;
}
}

Come illustrato nell'esempio precedente, i campi di sola lettura possono essere dichiarati con un modificatore
readonly . L'assegnazione a un campo di sola lettura può essere eseguita solo come parte della dichiarazione
del campo o in un costruttore della stessa classe.

Metodi
Un metodo è un membro che implementa un calcolo o un'azione che può essere eseguita da un oggetto o una
classe. I metodi statici sono accessibili tramite la classe, mentre i metodi di istanza sono accessibili tramite
istanze della classe.
I metodi possono avere un elenco di parametri, che rappresentano valori o riferimenti a variabili passati al
metodo. I metodi hanno un tipo restituito, che specifica il tipo di valore calcolato e restituito dal metodo. Il tipo
restituito di un metodo è void se non restituisce un valore.
Come i tipi, anche i metodi possono avere un set di parametri di tipo per i quali è necessario specificare
argomenti di tipo quando vengono chiamati. A differenza dei tipi, gli argomenti di tipo possono essere spesso
dedotti dagli argomenti di una chiamata al metodo e non devono essere assegnati in modo esplicito.
La firma di un metodo deve essere univoca nell'ambito della classe in cui viene dichiarato il metodo. La firma di
un metodo è costituita dal nome del metodo, dal numero di parametri di tipo e dal numero, dai modificatori e
dai tipi dei relativi parametri. La firma di un metodo non include il tipo restituito.
Quando il corpo di un metodo è una singola espressione, il metodo può essere definito usando un formato di
espressione compatta, come illustrato nell'esempio seguente:

public override string ToString() => "This is an object";

Parametri
I parametri consentono di passare ai metodi valori o riferimenti a variabili. I parametri di un metodo ottengono i
valori effettivi dagli argomenti specificati quando viene richiamato il metodo. Esistono quattro tipi di parametri:
parametri di valore, parametri di riferimento, i parametri di output e matrici di parametri.
Un parametro di valore viene usato per passare argomenti di input. Corrisponde a una variabile locale che
ottiene il valore iniziale dall'argomento passato per il parametro. Le modifiche a un parametro di valore non
influiscono sull'argomento passato per il parametro.
I parametri di valore possono essere facoltativi specificando un valore predefinito. In questo caso gli argomenti
corrispondenti possono essere omessi.
Un parametro di riferimento viene usato per passare argomenti per riferimento. L'argomento passato per un
parametro di riferimento deve essere una variabile con un valore definito. Durante l'esecuzione del metodo, il
parametro Reference rappresenta lo stesso percorso di archiviazione della variabile argument. Un parametro di
riferimento viene dichiarato con il modificatore ref . Nell'esempio seguente viene illustrato l'uso di parametri
ref .

static void Swap(ref int x, ref int y)


{
int temp = x;
x = y;
y = temp;
}

public static void SwapExample()


{
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine($"{i} {j}"); // "2 1"
}

Un parametro di output viene usato per passare argomenti per riferimento. È simile a un parametro di
riferimento, ad eccezione del fatto che non è necessario assegnare in modo esplicito un valore per l'argomento
specificato dal chiamante. Un parametro di output viene dichiarato con il modificatore out . L'esempio seguente
illustra come usare i parametri out con la sintassi introdotta in C# 7.

static void Divide(int x, int y, out int result, out int remainder)
{
result = x / y;
remainder = x % y;
}

public static void OutUsage()


{
Divide(10, 3, out int res, out int rem);
Console.WriteLine($"{res} {rem}"); // "3 1"
}

Una matrice di parametri consente di passare un numero variabile di argomenti a un metodo. Una matrice di
parametri viene dichiarata con il modificatore params . Solo l'ultimo parametro di un metodo può essere
costituito da una matrice di parametri, che deve essere sempre di tipo unidimensionale. I Write WriteLine
metodi e della System.Console classe sono ottimi esempi di utilizzo delle matrici di parametri. Sono dichiarati
come indicato di seguito.

public class Console


{
public static void Write(string fmt, params object[] args) { }
public static void WriteLine(string fmt, params object[] args) { }
// ...
}

All'interno di un metodo, una matrice di parametri si comporta esattamente come un normale parametro di tipo
matrice. Tuttavia, in una chiamata a un metodo con una matrice di parametri, è possibile passare un solo
argomento del tipo di matrice di parametri o un numero qualsiasi di argomenti del tipo di elemento della
matrice di parametri. Nel secondo caso, un'istanza di matrice viene automaticamente creata e inizializzata con gli
argomenti specificati. Questo esempio

int x, y, z;
x = 3;
y = 4;
z = 5;
Console.WriteLine("x={0} y={1} z={2}", x, y, z);

è equivalente alla sintassi seguente.

int x = 3, y = 4, z = 5;

string s = "x={0} y={1} z={2}";


object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

Corpo del metodo e variabili locali


Il corpo di un metodo specifica le istruzioni da eseguire quando viene richiamato il metodo.
Il corpo di un metodo può dichiarare variabili specifiche per la chiamata del metodo. Queste variabili prendono
il nome di variabili locali. Una dichiarazione di variabile locale specifica un nome di tipo, un nome di variabile e
possibilmente un valore iniziale. Nell'esempio seguente viene dichiarata una variabile locale i con un valore
iniziale pari a zero e una variabile locale j senza valore iniziale.

class Squares
{
public static void WriteSquares()
{
int i = 0;
int j;
while (i < 10)
{
j = i * i;
Console.WriteLine($"{i} x {i} = {j}");
i = i + 1;
}
}
}

In C# è necessario assegnare esplicitamente una variabile locale prima di poterne ottenere il valore. Se, ad
esempio, la dichiarazione del precedente i non include un valore iniziale, il compilatore segnalerà un errore
per gli utilizzi successivi di i perché i non verrà assegnato definitivamente a tali punti nel programma.
Un metodo può usare istruzioni return per restituire il controllo al chiamante. In un metodo che restituisce
void , le return istruzioni non possono specificare un'espressione. In un metodo che restituisce un valore
diverso da void, le istruzioni return devono includere un'espressione che calcola il valore restituito.
Metodi statici e di istanza
Un metodo dichiarato con un static modificatore è un metodo statico. Un metodo statico non agisce su
un'istanza specifica e può accedere direttamente ai membri statici.
Un metodo dichiarato senza un static modificatore è un metodo di istanza. Questo metodo agisce su
un'istanza specifica e può accedere a membri statici e di istanza. L'istanza in cui è stato richiamato un metodo di
istanza è accessibile in modo esplicito come this . Si tratta di un errore per fare riferimento a this in un
metodo statico.
La classe Entity seguente contiene sia membri statici sia membri di istanza.

class Entity
{
static int s_nextSerialNo;
int _serialNo;

public Entity()
{
_serialNo = s_nextSerialNo++;
}

public int GetSerialNo()


{
return _serialNo;
}

public static int GetNextSerialNo()


{
return s_nextSerialNo;
}

public static void SetNextSerialNo(int value)


{
s_nextSerialNo = value;
}
}

Ogni Entity istanza contiene un numero di serie (e presumibilmente alcune altre informazioni non illustrate
qui). Il costruttore Entity (simile a un metodo di istanza) inizializza la nuova istanza con il successivo numero di
serie disponibile. Poiché il costruttore è un membro di istanza, è consentito accedere sia al campo di _serialNo
istanza sia al s_nextSerialNo campo statico.
I metodi statici GetNextSerialNo e SetNextSerialNo possono accedere al campo statico s_nextSerialNo , ma si
verificherebbe un errore se accedessero direttamente al campo di istanza _serialNo .
Nell'esempio seguente viene illustrato l'utilizzo della Entity classe.

Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"

I SetNextSerialNo GetNextSerialNo metodi statici e vengono richiamati sulla classe, mentre il GetSerialNo
metodo di istanza viene richiamato sulle istanze della classe.
Metodi virtuali, di override e astratti
Se una dichiarazione di metodo di istanza include un modificatore virtual , il metodo viene definito metodo
virtuale. Se non è presente alcun modificatore virtual, il metodo diventa un metodo non virtuale.
Quando viene richiamato un metodo virtuale, il tipo in fase di esecuzione dell'istanza per cui viene eseguita la
chiamata determina l'implementazione effettiva del metodo da richiamare. In una chiamata a un metodo non
virtuale, il fattore determinante è il tipo in fase di compilazione dell'istanza.
Un metodo virtuale può essere sottoposto a override in una classe derivata. Se una dichiarazione di metodo di
istanza include un modificatore override, il metodo esegue l'override di un metodo virtuale ereditato con la
stessa firma. Una dichiarazione di metodo virtuale introduce un nuovo metodo. Una dichiarazione di metodo di
override specializza un metodo virtuale ereditato esistente fornendo una nuova implementazione del metodo.
Un metodo astratto è un metodo virtuale senza implementazione. Un metodo astratto viene dichiarato con il
abstract modificatore ed è consentito solo in una classe astratta. Un metodo astratto deve essere sottoposto a
override in ogni classe derivata non astratta.
Nell'esempio seguente viene dichiarata una classe astratta, Expression , che rappresenta un nodo dell'albero
delle espressioni, e tre classi derivate, Constant , VariableReference e Operation , che implementano i nodi
dell'albero delle espressioni relativi a costanti, riferimenti a variabili e operazioni aritmetiche. Questo esempio è
simile a, ma non è correlato ai tipi di albero delle espressioni.
public abstract class Expression
{
public abstract double Evaluate(Dictionary<string, object> vars);
}

public class Constant : Expression


{
double _value;

public Constant(double value)


{
_value = value;
}

public override double Evaluate(Dictionary<string, object> vars)


{
return _value;
}
}

public class VariableReference : Expression


{
string _name;

public VariableReference(string name)


{
_name = name;
}

public override double Evaluate(Dictionary<string, object> vars)


{
object value = vars[_name] ?? throw new Exception($"Unknown variable: {_name}");
return Convert.ToDouble(value);
}
}

public class Operation : Expression


{
Expression _left;
char _op;
Expression _right;

public Operation(Expression left, char op, Expression right)


{
_left = left;
_op = op;
_right = right;
}

public override double Evaluate(Dictionary<string, object> vars)


{
double x = _left.Evaluate(vars);
double y = _right.Evaluate(vars);
switch (_op)
{
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;

default: throw new Exception("Unknown operator");


}
}
}

Le quattro classi precedenti possono essere usate per modellare espressioni aritmetiche. Usando istanze di
queste classi, l'espressione x + 3 , ad esempio, può essere rappresentata come illustrato di seguito.

Expression e = new Operation(


new VariableReference("x"),
'+',
new Constant(3));

Il metodo Evaluate di un'istanza Expression viene richiamato per valutare l'espressione specificata e generare
un valore double . Il metodo accetta un argomento Dictionary che contiene nomi di variabili (come chiavi delle
voci) e valori (come valori delle voci). Poiché Evaluate è un metodo astratto, le classi non astratte derivate da
Expression devono eseguire l'override di Evaluate .

L'implementazione di un valore Constant del metodo Evaluate restituisce semplicemente la costante


memorizzata. L'implementazione di un valore VariableReference cerca il nome della variabile nel dizionario e
restituisce il valore ottenuto. L'implementazione di un valore Operation valuta prima gli operandi sinistro e
destro (richiamando in modo ricorsivo i metodi Evaluate ) e quindi esegue l'operazione aritmetica specificata.
Il programma seguente usa le classi Expression per valutare l'espressione x * (y + 2) per valori diversi di x
e y.

Expression e = new Operation(


new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Dictionary<string, object> vars = new Dictionary<string, object>();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // "16.5"

Overload di un metodo
L'overload di un metodo consente a più metodi della stessa classe di avere lo stesso nome, purché abbiano
firme univoche. Quando si compila una chiamata di un metodo di overload, il compilatore usa la risoluzione
dell'overload per determinare il metodo specifico da richiamare. La risoluzione dell'overload trova il metodo che
meglio corrisponde agli argomenti. Se non è possibile trovare una corrispondenza migliore, viene restituito un
errore. Nell'esempio seguente viene illustrato il funzionamento effettivo della risoluzione dell'overload. Il
commento per ogni chiamata nel UsageExample metodo indica quale metodo viene richiamato.
class OverloadingExample
{
static void F() => Console.WriteLine("F()");
static void F(object x) => Console.WriteLine("F(object)");
static void F(int x) => Console.WriteLine("F(int)");
static void F(double x) => Console.WriteLine("F(double)");
static void F<T>(T x) => Console.WriteLine("F<T>(T)");
static void F(double x, double y) => Console.WriteLine("F(double, double)");

public static void UsageExample()


{
F(); // Invokes F()
F(1); // Invokes F(int)
F(1.0); // Invokes F(double)
F("abc"); // Invokes F<string>(string)
F((double)1); // Invokes F(double)
F((object)1); // Invokes F(object)
F<int>(1); // Invokes F<int>(int)
F(1, 1); // Invokes F(double, double)
}
}

Come illustrato nell'esempio, è sempre possibile selezionare un particolare metodo eseguendo il cast esplicito
degli argomenti ai tipi di parametro esatti e agli argomenti di tipo.

Altri membri di funzione


I membri che contengono codice eseguibile sono noti come membri funzione di una classe. Nella sezione
precedente vengono descritti i metodi, ovvero i tipi principali di membri di funzione. In questa sezione vengono
descritti altri membri di funzione supportati da C#: costruttori, proprietà, indicizzatori, eventi, operatori e
finalizzatori.
Nell'esempio seguente viene illustrata una classe generica denominata MyList<T> , che implementa un elenco
di oggetti espandibile. Nella classe sono contenuti alcuni esempi di membri di funzione più comuni.

public class MyList<T>


{
const int DefaultCapacity = 4;

T[] _items;
int _count;

public MyList(int capacity = DefaultCapacity)


{
_items = new T[capacity];
}

public int Count => _count;

public int Capacity


{
get => _items.Length;
set
{
if (value < _count) value = _count;
if (value != _items.Length)
{
T[] newItems = new T[value];
Array.Copy(_items, 0, newItems, 0, _count);
_items = newItems;
}
}
}
public T this[int index]
{
get => _items[index];
set
{
_items[index] = value;
OnChanged();
}
}

public void Add(T item)


{
if (_count == Capacity) Capacity = _count * 2;
_items[_count] = item;
_count++;
OnChanged();
}
protected virtual void OnChanged() =>
Changed?.Invoke(this, EventArgs.Empty);

public override bool Equals(object other) =>


Equals(this, other as MyList<T>);

static bool Equals(MyList<T> a, MyList<T> b)


{
if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
if (Object.ReferenceEquals(b, null) || a._count != b._count)
return false;
for (int i = 0; i < a._count; i++)
{
if (!object.Equals(a._items[i], b._items[i]))
{
return false;
}
}
return true;
}

public event EventHandler Changed;

public static bool operator ==(MyList<T> a, MyList<T> b) =>


Equals(a, b);

public static bool operator !=(MyList<T> a, MyList<T> b) =>


!Equals(a, b);
}

Costruttori
C# supporta sia costruttori di istanza sia costruttori statici. Un costruttore di istanza è un membro che
implementa le azioni necessarie per inizializzare un'istanza di una classe, Un costruttore statico è un membro
che implementa le azioni necessarie per inizializzare una classe quando viene caricata per la prima volta.
Un costruttore viene dichiarato come un metodo, senza tipo restituito e con lo stesso nome della classe in cui è
contenuto. Se una dichiarazione di costruttore include un static modificatore, dichiara un costruttore statico.
In caso contrario, dichiara un costruttore di istanza.
I costruttori di istanza possono essere sottoposti a overload e avere parametri facoltativi. La classe MyList<T> ,
ad esempio, dichiara un costruttore di istanza con un singolo parametro int facoltativo. I costruttori di istanza
vengono richiamati con l'operatore new . Le istruzioni seguenti allocano due istanze MyList<string> usando il
costruttore della classe MyList con e senza l'argomento facoltativo.
MyList<string> list1 = new MyList<string>();
MyList<string> list2 = new MyList<string>(10);

Diversamente da altri membri, i costruttori di istanza non vengono ereditati. Una classe non dispone di
costruttori di istanza diversi da quelli effettivamente dichiarati nella classe. Se per una classe non è specificato
alcun costruttore di istanza, ne viene automaticamente fornito uno vuoto senza parametri.
Proprietà
Le proprietà sono una naturale estensione dei campi. Entrambi sono membri denominati con tipi associati e la
sintassi per accedere ai campi e alle proprietà è identica. Tuttavia, a differenza dei campi, le proprietà non
denotano le posizioni di archiviazione. Al contrario, le proprietà dispongono di funzioni di accesso che
specificano le istruzioni eseguite quando i relativi valori vengono letti o scritti.
Una proprietà viene dichiarata come un campo, ad eccezione del fatto che la dichiarazione termina con una
funzione di accesso get o una funzione di accesso set scritta tra i delimitatori { e } anziché terminare con un
punto e virgola. Una proprietà che include entrambe le funzioni di accesso get e set è una proprietà di
lettura/scrittura, una proprietà che include solo una funzione di accesso get è una proprietà di sola lettura e una
proprietà che include solo una funzione di accesso set è una proprietà di sola scrittura.
Una funzione di accesso get corrisponde a un metodo senza parametri con un valore restituito del tipo di
proprietà. Una funzione di accesso set corrisponde a un metodo con un singolo valore di parametro denominato
e senza tipo restituito. La funzione di accesso Get calcola il valore della proprietà. La funzione di accesso set
fornisce un nuovo valore per la proprietà. Quando la proprietà è la destinazione di un'assegnazione o
l'operando di ++ o -- , viene richiamata la funzione di accesso set. Negli altri casi in cui viene fatto riferimento
alla proprietà, viene richiamata la funzione di accesso get.
La classe MyList<T> dichiara due proprietà, Count e Capacity , che sono rispettivamente di sola lettura e di
lettura/scrittura. Il codice seguente è un esempio di utilizzo di queste proprietà:

MyList<string> names = new MyList<string>();


names.Capacity = 100; // Invokes set accessor
int i = names.Count; // Invokes get accessor
int j = names.Capacity; // Invokes get accessor

Come per i campi e i metodi, C# supporta sia proprietà di istanza sia proprietà statiche. Le proprietà statiche
vengono dichiarate con il modificatore static, mentre le proprietà di istanza vengono dichiarate senza tale
modificatore.
Le funzioni di accesso di una proprietà possono essere virtuali. Se una dichiarazione di proprietà contiene un
modificatore virtual , abstract o override , questo viene applicato anche alle funzioni di accesso della
proprietà.
Indicizzatori
Un indicizzatore è un membro che consente di indicizzare gli oggetti esattamente come una matrice. Un
indicizzatore viene dichiarato come una proprietà, ma a differenza di questa il nome del membro è this
seguito da un elenco di parametri scritti tra i delimitatori [ e ] . I parametri sono disponibili nelle funzioni di
accesso dell'indicizzatore. Analogamente alle proprietà, gli indicizzatori possono essere di lettura/scrittura, di
sola lettura o di sola scrittura e le funzioni di accesso di un indicizzatore possono essere virtuali.
La classe MyList<T> dichiara un indicizzatore di lettura/scrittura che accetta un parametro int e consente di
indicizzare istanze MyList<T> con valori int . Ad esempio:
MyList<string> names = new MyList<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
string s = names[i];
names[i] = s.ToUpper();
}

Gli indicizzatori possono essere sottoposti a overload. Una classe può dichiarare più indicizzatori, purché il
numero o i tipi dei parametri siano diversi.
Eventi
Un evento è un membro che consente a una classe o a un oggetto di inviare notifiche. Un evento viene
dichiarato come un campo, ad eccezione del fatto che la dichiarazione include una event parola chiave e il tipo
deve essere un tipo delegato.
All'interno di una classe che dichiara un membro evento, l'evento si comporta esattamente come un campo di
un tipo delegato (purché l'evento non sia astratto e non dichiara le funzioni di accesso). Il campo archivia un
riferimento a un delegato che rappresenta i gestori eventi aggiunti all'evento. Se non è presente alcun gestore
eventi, il campo è null .
La classe MyList<T> dichiara un singolo membro di evento denominato Changed , con cui si indica che un nuovo
elemento è stato aggiunto all'elenco. L'evento Changed viene generato dal metodo virtuale OnChanged , che
prima controlla se l'evento è null , ovvero se non è presente alcun gestore. La nozione di generazione di un
evento è esattamente equivalente alla chiamata del delegato rappresentato dall'evento. Nessun costrutto di
linguaggio speciale per la generazione di eventi.
I client rispondono agli eventi tramite gestori eventi, che possono essere aggiunti con l'operatore += e rimossi
con l'operatore -= . Nell'esempio seguente un gestore eventi viene aggiunto all'evento Changed di
MyList<string> .

class EventExample
{
static int s_changeCount;

static void ListChanged(object sender, EventArgs e)


{
s_changeCount++;
}

public static void Usage()


{
var names = new MyList<string>();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(s_changeCount); // "3"
}
}

Per gli scenari avanzati in cui si desidera controllare l'archiviazione sottostante di un evento, una dichiarazione di
evento può fornire in modo esplicito le add remove funzioni di accesso e, che sono simili alla set funzione di
accesso di una proprietà.
Operatori
Un operatore è un membro che definisce il significato dell'applicazione di un particolare operatore di
espressione alle istanze di una classe. È possibile definire tre tipi di operatori: unari, binari e di conversione. Tutti
gli operatori devono essere dichiarati come public e static .
La MyList<T> classe dichiara due operatori, operator == e operator != . Questi operatori sottoposti a override
forniscono nuovo significato a espressioni che applicano tali operatori alle MyList istanze di. In particolare, gli
operatori definiscono l'uguaglianza di due MyList<T> istanze di confrontando ognuno degli oggetti contenuti
usando i relativi Equals metodi. Nell'esempio seguente viene usato l'operatore == per confrontare due istanze
di MyList<int> .

MyList<int> a = new MyList<int>();


a.Add(1);
a.Add(2);
MyList<int> b = new MyList<int>();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Outputs "True"
b.Add(3);
Console.WriteLine(a == b); // Outputs "False"

Il primo Console.WriteLine restituisce True perché i due elenchi contengono lo stesso numero di oggetti con
gli stessi valori e nello stesso ordine. Se in MyList<T> non fosse stato definito l'operatore operator == , il primo
Console.WriteLine avrebbe restituito False perché a e b fanno riferimento a istanze di MyList<int> diverse.

Finalizzatori
Un finalizzatore è un membro che implementa le azioni necessarie per finalizzare un'istanza di una classe. Per
rilasciare le risorse non gestite, è in genere necessario un finalizzatore. I finalizzatori non possono avere
parametri, non possono avere modificatori di accessibilità e non possono essere richiamati in modo esplicito. Il
finalizzatore di un'istanza viene richiamato automaticamente durante la procedura di Garbage Collection. Per
altri dettagli, vedere l'articolo sui finalizzatori.
Al Garbage Collector viene lasciata ampia scelta per decidere quando raccogliere oggetti ed eseguire
finalizzatori. In particolare, la tempistica delle chiamate del finalizzatore non è deterministica e i finalizzatori
possono essere eseguiti su qualsiasi thread. Per questi e altri motivi, le classi devono implementare finalizzatori
solo se non è praticabile nessun'altra soluzione.
L'istruzione using offre una soluzione più efficace per l'eliminazione di oggetti.

Espressioni
Le espressioni sono costituite da operandi e operatori. Gli operatori di un'espressione indicano le operazioni che
devono essere eseguite sugli operandi. Alcuni esempi di operatori sono + , - , * , / e new , mentre i valori
effettivi, i campi, le variabili locali e le espressioni sono esempi di operandi.
Quando un'espressione contiene più operatori, la precedenza degli operatori controlla l'ordine in cui vengono
valutati i singoli operatori. L'espressione x + y * z , ad esempio, viene valutata come x + (y * z) poiché
l'operatore * ha la precedenza sull'operatore + .
Quando un operando si trova tra due operatori con la stessa precedenza, l'ordine di esecuzione delle operazioni
viene determinato dall'associatività degli operatori:
Ad eccezione degli operatori di assegnazione e di Unione null, tutti gli operatori binari sono associativi a
sinistra, ovvero le operazioni vengono eseguite da sinistra a destra. L'espressione x + y + z viene ad
esempio valutata come (x + y) + z .
Gli operatori di assegnazione, gli operatori e l'Unione di valori null ?? ??= e l'operatore condizionale ?:
sono associativi a destra, ovvero le operazioni vengono eseguite da destra a sinistra. L'espressione
x = y = z viene ad esempio valutata come x = (y = z) .
È possibile controllare la precedenza e l'associatività usando le parentesi. Ad esempio, x + y * z prima
moltiplica y per z e quindi somma il risultato a x , ma (x + y) * z prima somma x e y e quindi
moltiplica il risultato per z .
La maggior parte degli operatori può essere sottoposta a overload. L'overload degli operatori consente di
specificare implementazioni di operatori definite dall'utente per le operazioni in cui uno o entrambi gli operandi
appartengono a un tipo struct o a una classe definita dall'utente.
C# offre diversi operatori per eseguire operazioni aritmetiche, logiche, di spostamento e bit per bit, nonché
confronti di uguaglianza e ordinamento.
Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere Operatori C#.

Istruzioni
Le azioni di un programma vengono espresse mediante istruzioni. C# supporta numerosi tipi di istruzioni,
alcune delle quali sono definite in termini di istruzioni nidificate.
Un blocco consente di scrivere più istruzioni nei contesti in cui ne è consentita una sola. Un blocco è
costituito da un elenco di istruzioni scritte tra i delimitatori { e } .
Le istruzioni di dichiarazione vengono usate per dichiarare le costanti e le variabili locali.
Le istruzioni di espressione vengono usate per valutare le espressioni. Le espressioni che possono essere
usate come istruzioni includono le chiamate ai metodi, le allocazioni di oggetti mediante l'operatore new , le
assegnazioni mediante = e gli operatori di assegnazione composta, le operazioni di incremento e
decremento mediante gli operatori ++ e -- e le espressioni await .
Le istruzioni di selezione vengono usate per selezionare una tra più istruzioni che è possibile eseguire sulla
base del valore di alcune espressioni. Questo gruppo contiene le if switch istruzioni e.
Le istruzioni di iterazione vengono usate per eseguire più volte un'istruzione nidificata. Questo gruppo
contiene le while do istruzioni,, for e foreach .
Le istruzioni di spostamento vengono usate per trasferire il controllo. Questo gruppo contiene le break
continue istruzioni,, goto , throw , return e yield .
L'istruzione try ... catch viene usata per rilevare le eccezioni che si verificano durante l'esecuzione di un
blocco, mentre l'istruzione try ... finally viene usata per specificare il codice di finalizzazione che viene
eseguito sempre, indipendentemente dal fatto che si sia verificata un'eccezione.
Le istruzioni checked e unchecked vengono usate per verificare il contesto di controllo dell'overflow per le
conversioni e le operazioni aritmetiche di tipo integrale.
L'istruzione lock viene usata per ottenere il blocco a esclusione reciproca per un oggetto specificato,
eseguire un'istruzione e quindi rilasciare il blocco.
L'istruzione using viene usata per ottenere una risorsa, eseguire un'istruzione e quindi eliminare la risorsa.

Di seguito sono elencati i tipi di istruzioni che è possibile utilizzare:


Dichiarazione di variabile locale.
Dichiarazione di costante locale.
Istruzione di espressione.
if istruzione.
switch istruzione.
while istruzione.
do istruzione.
for istruzione.
foreach istruzione.
break istruzione.
continue istruzione.
goto istruzione.
return istruzione.
yield istruzione.
throw istruzioni e try istruzioni.
checked``unchecked istruzioni e.
lock istruzione.
using istruzione.

P R E C E D E N TE AVA N TI
Aree principali del linguaggio
28/01/2021 • 16 minutes to read • Edit Online

Matrici, raccolte e LINQ


C# e .NET forniscono molti tipi di raccolta diversi. Le matrici hanno una sintassi definita dal linguaggio. I tipi di
raccolta generici sono elencati nello System.Collections.Generic spazio dei nomi. Le raccolte specializzate
includono System.Span<T> per accedere alla memoria continua nel stack frame e System.Memory<T> per
accedere alla memoria continua nell'heap gestito. Tutte le raccolte, incluse le matrici, Span<T> e Memory<T>
condividono un principio unificatore per l'iterazione. Si usa l' System.Collections.Generic.IEnumerable<T>
interfaccia. Questo principio di unificazione significa che qualsiasi tipo di raccolta può essere utilizzato con query
LINQ o altri algoritmi. I metodi vengono scritti usando IEnumerable<T> e tali algoritmi funzionano con qualsiasi
raccolta.
Matrici
Una * matrice _ è una struttura di dati che contiene un numero di variabili a cui si accede tramite indici calcolati.
Le variabili contenute in una matrice, denominate anche elementi della matrice, sono dello stesso tipo. Questo
tipo è denominato tipo di elemento della matrice.
Poiché i tipi di matrice sono tipi di riferimento, la dichiarazione di una variabile di matrice si limita a riservare
spazio per un riferimento a un'istanza di matrice. Le istanze di matrice effettive vengono create dinamicamente
in fase di esecuzione usando l' new operatore. L' new operazione specifica la lunghezza della nuova istanza
della matrice, che viene quindi corretta per la durata dell'istanza. Gli indici degli elementi di una matrice sono
compresi tra 0 e Length - 1 . L'operatore new inizializza automaticamente gli elementi di una matrice sul
rispettivo valore predefinito che, ad esempio, equivale a zero per tutti i tipi numerici e a null per tutti i tipi di
riferimento.
Nell'esempio seguente viene creata una matrice di elementi int , viene inizializzata la matrice e ne viene
stampato il contenuto.

int[] a = new int[10];


for (int i = 0; i < a.Length; i++)
{
a[i] = i * i;
}
for (int i = 0; i < a.Length; i++)
{
Console.WriteLine($"a[{i}] = {a[i]}");
}

Questo esempio crea e opera su una matrice unidimensionale. C# supporta anche matrici multidimensionali. Il
numero di dimensioni di un tipo di matrice, noto anche come rango del tipo di matrice, è uno più il numero di
virgole scritte tra le parentesi quadre del tipo di matrice. Nell'esempio seguente vengono allocate,
rispettivamente, una matrice unidimensionale, una bidimensionale e una tridimensionale.

int[] a1 = new int[10];


int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

La matrice a1 contiene 10 elementi, la matrice a2 contiene 50 (10 × 5) elementi e la a3 matrice contiene 100
(10 × 5 × 2) elementi. L'elemento di una matrice può essere di qualsiasi tipo, anche di tipo matrice. Una matrice
con elementi di un tipo di matrice viene talvolta definita matrice irregolare perché le lunghezze delle matrici di
elementi non devono necessariamente essere uguali. Nell'esempio seguente viene allocata una matrice di
matrici di int :

int[][] a = new int[3][];


a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

La prima riga crea una matrice con tre elementi, ognuno di tipo int[] e con un valore iniziale di null . Le righe
successive inizializzano quindi i tre elementi con riferimenti a singole istanze di matrici di lunghezza variabile.
L' new operatore consente di specificare i valori iniziali degli elementi della matrice utilizzando un inizializzatore
di matrice, ovvero un elenco di espressioni scritte tra i delimitatori { e } . Nell'esempio seguente viene
allocata e inizializzata una matrice int[] con tre elementi.

int[] a = new int[] { 1, 2, 3 };

La lunghezza della matrice viene dedotta dal numero di espressioni tra { e } . L'inizializzazione della matrice
può essere abbreviata ulteriormente in modo da non dover riaffermare il tipo di matrice.

int[] a = { 1, 2, 3 };

Entrambi gli esempi precedenti sono equivalenti al codice seguente:

int[] t = new int[3];


t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;

L' foreach istruzione può essere utilizzata per enumerare gli elementi di qualsiasi raccolta. Il codice seguente
enumera la matrice dall'esempio precedente:

foreach (int item in a)


{
Console.WriteLine(item);
}

L' foreach istruzione utilizza l' IEnumerable<T> interfaccia, pertanto può funzionare con qualsiasi raccolta.

Interpolazione di stringhe
L' interpolazione di stringhe C# consente di formattare le stringhe definendo espressioni i cui risultati vengono
inseriti in una stringa di formato. Ad esempio, nell'esempio seguente viene stampata la temperatura in un
determinato giorno da un set di dati meteorologici:

Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-DD-YYYY}");


Console.WriteLine($" was {weatherData.LowTemp} and {weatherData.HighTemp}.");
// Output (similar to):
// The low and high temperature on 08-11-2020
// was 5 and 30.
Una stringa interpolata viene dichiarata utilizzando il $ token. L'interpolazione di stringhe valuta le espressioni
tra { e } , quindi converte il risultato in un oggetto string e sostituisce il testo tra parentesi quadre con il
risultato della stringa dell'espressione. : Nella prima espressione {weatherData.Date:MM-DD-YYYY} specifica il
_format stringa *. Nell'esempio precedente, specifica che la data deve essere stampata nel formato "MM-gg-
aaaa".

Criteri di ricerca
Il linguaggio C# fornisce espressioni * corrispondenti _ per eseguire query sullo stato di un oggetto ed
eseguire codice basato su tale stato. È possibile controllare i tipi e i valori delle proprietà e dei campi per
determinare l'azione da eseguire. L' switch espressione è l'espressione primaria per i criteri di ricerca.

Delegati ed espressioni lambda


Un tipo delegato rappresenta i riferimenti ai metodi con un elenco di parametri e un tipo restituito specifici. I
delegati consentono di trattare i metodi come entità che è possibile assegnare a variabili e passare come
parametri. I delegati sono simili al concetto di puntatori a funzione disponibili in altri linguaggi. A differenza dei
puntatori a funzione, i delegati sono orientati agli oggetti e indipendenti dai tipi.
Nell'esempio seguente viene dichiarato e usato un tipo delegato denominato Function .

delegate double Function(double x);

class Multiplier
{
double _factor;

public Multiplier(double factor) => _factor = factor;

public double Multiply(double x) => x * _factor;


}

class DelegateExample
{
static double[] Apply(double[] a, Function f)
{
var result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}

public static void Main()


{
double[] a = { 0.0, 0.5, 1.0 };
double[] squares = Apply(a, (x) => x * x);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new Multiplier(2.0);
double[] doubles = Apply(a, m.Multiply);
}
}

Un'istanza del tipo delegato Function può fare riferimento a qualsiasi metodo che accetta un argomento
double e restituisce un valore double . Il Apply metodo applica un oggetto specificato Function agli elementi
di un oggetto double[] , restituendo un oggetto double[] con i risultati. Nel metodo Main , Apply viene usato
per applicare a double[] tre funzioni differenti.
Un delegato può fare riferimento a un metodo statico, come Square o Math.Sin nell'esempio precedente, o a
un metodo di istanza, come m.Multiply nell'esempio precedente. Un delegato che fa riferimento a un metodo
di istanza fa riferimento anche a un oggetto particolare. Quando il metodo di istanza viene richiamato tramite il
delegato, l'oggetto diventa this nella chiamata.
È anche possibile creare delegati usando funzioni anonime, ovvero "metodi inline" creati quando vengono
dichiarati. Le funzioni anonime possono vedere le variabili locali dei metodi circostanti. Nell'esempio seguente
non viene creata una classe:

double[] doubles = Apply(a, (double x) => x * 2.0);

Un delegato non conosce né interessa la classe del metodo a cui fa riferimento. È importante che il metodo a cui
si fa riferimento abbia gli stessi parametri e il tipo restituito del delegato.

async/await
C# supporta i programmi asincroni con due parole chiave: async e await . Il async modificatore viene
aggiunto a una dichiarazione di metodo per dichiarare che il metodo è asincrono. L' await operatore indica al
compilatore di attendere in modo asincrono il completamento di un risultato. Il controllo viene restituito al
chiamante e il metodo restituisce una struttura che gestisce lo stato del lavoro asincrono. La struttura è in
genere un oggetto System.Threading.Tasks.Task<TResult> , ma può essere qualsiasi tipo che supporta il modello
awaiter. Queste funzionalità consentono di scrivere codice che legge come controparte sincrona, ma viene
eseguita in modo asincrono. Il codice seguente, ad esempio, Scarica il home page per Microsoft docs:

public async Task<int> RetrieveDocsHomePage()


{
var client = new HttpClient();
byte[] content = await client.GetByteArrayAsync("https://docs.microsoft.com/");

Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished downloading.");


return content.Length;
}

In questo piccolo esempio vengono illustrate le principali funzionalità per la programmazione asincrona:
La dichiarazione di metodo include il async modificatore.
Il corpo del metodo await s restituisce il risultato del GetByteArrayAsync metodo.
Il tipo specificato nell' return istruzione corrisponde all'argomento di tipo nella Task<T> dichiarazione per il
metodo. Un metodo che restituisce un oggetto Task utilizzerebbe return istruzioni senza argomenti.

Attributi
Tipi, membri e altre entità di un programma C# supportano modificatori che controllano alcuni aspetti del loro
comportamento. L'accessibilità di un metodo, ad esempio, è controllata con i modificatori public , protected ,
internal e private . Il linguaggio C# generalizza questa funzionalità in modo che i tipi di informazioni
dichiarative definiti dall'utente possano essere associati a entità di programma e recuperati in fase di
esecuzione. I programmi specificano queste informazioni dichiarative aggiuntive definendo e usando gli attributi
* _.
Nell'esempio seguente viene dichiarato un attributo HelpAttribute che può essere inserito in entità di
programma per fornire collegamenti alla documentazione correlata.
public class HelpAttribute : Attribute
{
string _url;
string _topic;

public HelpAttribute(string url) => _url = url;

public string Url => _url;

public string Topic


{
get => _topic;
set => _topic = value;
}
}

Tutte le classi di attributi derivano dalla Attribute classe di base fornita dalla libreria .NET. Gli attributi possono
essere applicati specificandone il nome (con eventuali argomenti) tra parentesi quadre appena prima della
dichiarazione associata. Se il nome dell'attributo termina con Attribute , questa parte del nome può essere
omessa quando si fa riferimento all'attributo. Ad esempio, è possibile usare HelpAttribute come segue.

[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features",
Topic = "Display")]
public void Display(string text) { }
}

In questo esempio viene associato un attributo HelpAttribute alla classe Widget e viene aggiunto un altro
attributo HelpAttribute al metodo Display nella classe. I costruttori pubblici di una classe di attributo
controllano le informazioni che devono essere specificate quando l'attributo è associato a un'entità di
programma. È possibile specificare informazioni aggiuntive facendo riferimento a proprietà di lettura/scrittura
pubbliche della classe di attributo, ad esempio il riferimento alla proprietà Topic precedente.
I metadati definiti dagli attributi possono essere letti e modificati in fase di esecuzione usando la reflection. Se
un attributo viene richiesto usando questa tecnica, il costruttore della classe di attributo viene richiamato con le
informazioni specificate nell'origine del programma e viene restituita l'istanza dell'attributo risultante. Se sono
state fornite informazioni aggiuntive tramite proprietà, queste vengono impostate sui valori specificati prima
che venga restituita l'istanza dell'attributo.
L'esempio di codice seguente illustra come ottenere le istanze di HelpAttribute associate alla classe Widget e
al relativo metodo Display .
Type widgetType = typeof(Widget);

object[] widgetClassAttributes = widgetType.GetCustomAttributes(typeof(HelpAttribute), false);

if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}

System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));

object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);

if (displayMethodAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}

Altre informazioni
Per approfondire la conoscenza di C#, provare una delle esercitazioni.

IN D IE TR O
Esercitazioni su C#
28/01/2021 • 6 minutes to read • Edit Online

Queste sono le esercitazioni su C#. iniziano con lezioni interattive che è possibile eseguire nel browser. Le
esercitazioni successive e più avanzate consentono di usare gli strumenti di sviluppo .NET per creare programmi
C# nel computer.

Introduzione a C# - Esercitazioni interattive


Se si vuole avviare l'esplorazione in formato video, la serie di video di c# 101 fornisce un'introduzione a c#. In
queste esercitazioni verranno fornite informazioni sui concetti che è possibile esplorare.
Nella prima lezione vengono spiegati i concetti di C# usando piccoli frammenti di codice. Si apprenderanno le
nozioni di base della sintassi di C# e si scoprirà come usare i tipi di dati come stringhe, numeri e valori booleani.
Tutte le istruzioni sono interattive e si imparerà a scrivere e a eseguire codice in pochi minuti. Per queste prime
lezioni non è richiesta alcuna conoscenza pregressa di programmazione o del linguaggio C#.

Salve, mondo
Nell'esercitazione Hello World verrà creato il programma C# più semplice. Si esaminerà il tipo string e
verranno illustrate le procedure per lavorare con il testo.

Numeri in C#
Nell'esercitazione Numeri in C# viene descritto il modo in cui i computer archiviano numeri per eseguire calcoli
con tipi numerici diversi. Verranno illustrati i concetti di base degli arrotondamenti e le procedure per eseguire
calcoli matematici con C#. Questa esercitazione è disponibile anche per l'esecuzione in locale nel computer.
Questa esercitazione presuppone che sia stata già completata la lezione Hello World.

Rami e cicli
L'esercitazione Rami e cicli presenta i concetti di base della selezione di percorsi diversi di esecuzione del codice
in base ai valori archiviati in variabili. Si apprenderanno i concetti fondamentali del flusso di controllo, ovvero i
meccanismi in base ai quali i programmi prendono decisioni e scelgono azioni diverse. Questa esercitazione è
disponibile anche per l'esecuzione in locale nel computer.
Questa esercitazione presuppone che siano state già completate le lezioni Hello World e Numeri in C#.

Raccolte di elenchi
La lezione Raccolte di elenchi offre una panoramica delle raccolte di tipo List che consentono di archiviare
sequenze di dati. Si apprenderà come aggiungere e rimuovere elementi, eseguire la ricerca di elementi e
ordinare gli elenchi. Verranno esaminati diversi tipi di elenchi. Questa esercitazione è disponibile anche per
l'esecuzione in locale nel computer.
Questa esercitazione presuppone che siano state già completate le lezioni elencate sopra.

Introduzione a C# - Lavorare in locale


Tutte le esercitazioni introduttive successive alla lezione relativa a Hello World sono disponibili nell'ambiente di
sviluppo locale. Alla fine di ogni esercitazione, è possibile scegliere se continuare con la successiva lezione online
o nel proprio computer. Sono disponibili collegamenti che aiutano a configurare l'ambiente e continuare con
l'esercitazione successiva nel computer.

Esplorare le nuove funzionalità in C#


Interpolazione di stringhe: illustra come usare l'interpolazione di stringhe per creare stringhe formattate in
C#.
Tipi riferimento nullable: viene illustrato come usare i tipi riferimento nullable per esprimere le proprie
intenzioni per i riferimenti Null.
Aggiornare un progetto per usare i tipi riferimento nullable: illustra le tecniche per aggiornare un progetto
esistente per poter usare i tipi riferimento nullable.
Estendere le funzionalità per i dati usando i criteri di ricerca: illustra come usare i criteri di ricerca per
estendere i tipi oltre le funzionalità di base.
Usare le sequenze di dati con indici e intervalli: illustra una nuova sintassi pratica per accedere a singoli
elementi o intervalli di un contenitore di dati sequenziali.

Esercitazioni generali
Le esercitazioni seguenti consentono di creare programmi C# usando .NET Core:
Applicazione console: illustra l'I/O della console, la struttura di un'applicazione console e le nozioni di base
del modello di programmazione asincrono basato su attività.
Client REST: questa esercitazione illustra le comunicazioni Web, la serializzazione JSON e le funzionalità
orientate agli oggetti nel linguaggio C#.
Ereditarietà in C# e .NET: questa esercitazione illustra l'ereditarietà in C#, tra cui l'uso dell'ereditarietà per
definire le classi di base, le classi di base astratte e le classi derivate.
Uso di LINQ: questa esercitazione illustra molte delle funzionalità di LINQ e gli elementi del linguaggio che
supportano questa tecnologia.
Uso degli attributi: illustra come creare e usare gli attributi in C#.
L'esercitazione Interpolazione di stringhe mostra come inserire valori in una stringa. Verrà illustrato come
creare una stringa interpolata con espressioni C# incorporate e come controllare l'aspetto del testo dei
risultati dell'espressione nella stringa di risultato. Questa esercitazione è disponibile anche per l'esecuzione in
locale nel computer.
Introduzione a C#
02/11/2020 • 6 minutes to read • Edit Online

Queste sono le esercitazioni di introduzione a C#. Queste lezioni iniziano con codice interattivo che è possibile
eseguire nel browser. Prima di iniziare queste lezioni interattive, è possibile apprendere le nozioni di base di C#
dalla serie di video su c# 101 .

Nella prima lezione vengono spiegati i concetti di C# usando piccoli frammenti di codice. Si apprenderanno le
nozioni di base della sintassi di C# e si scoprirà come usare i tipi di dati come stringhe, numeri e valori booleani.
Tutte le istruzioni sono interattive e si imparerà a scrivere e a eseguire codice in pochi minuti. Per queste prime
lezioni non è richiesta alcuna conoscenza pregressa di programmazione o del linguaggio C#.
È possibile provare queste esercitazioni in ambienti diversi. I concetti che si apprenderanno sono gli stessi. La
differenza è rappresentata dall'esperienza preferita:
Nella piattaforma docs del browser: questa esperienza incorpora una finestra del codice C# eseguibile nelle
pagine di docs. È possibile scrivere ed eseguire codice C# nel browser.
Nell'esperienza Microsoft Learn. Questo percorso di apprendimento contiene diversi moduli che insegnano
le nozioni di base di C#.
In Jupyter on Binder. È possibile sperimentare il codice C# in un notebook di Jupyter in Binder.
Nel computer locale. Una volta esplorato online, è possibile scaricare il .NET Core SDK e creare programmi
nel computer.
Tutte le esercitazioni introduttive successive alla lezione relativa a Hello World sono disponibili tramite
l'esperienza del browser online o nell'ambiente di sviluppo locale. Alla fine di ogni esercitazione, è possibile
scegliere se continuare con la successiva lezione online o nel proprio computer. Sono disponibili collegamenti
che aiutano a configurare l'ambiente e continuare con l'esercitazione successiva nel computer.

Salve, mondo
Nell'esercitazione Hello World verrà creato il programma C# più semplice. Si esaminerà il tipo string e
verranno illustrate le procedure per lavorare con il testo. È anche possibile usare il percorso su Microsoft Learn o
Jupyter sul Binder.

Numeri in C#
Nell'esercitazione Numeri in C# viene descritto il modo in cui i computer archiviano numeri per eseguire calcoli
con tipi numerici diversi. Verranno illustrati i concetti di base degli arrotondamenti e le procedure per eseguire
calcoli matematici con C#. Questa esercitazione è disponibile anche per l'esecuzione in locale nel computer.
Questa esercitazione presuppone che sia stata completata la lezione Hello World .

Rami e cicli
L'esercitazione Rami e cicli presenta i concetti di base della selezione di percorsi diversi di esecuzione del codice
in base ai valori archiviati in variabili. Si apprenderanno i concetti fondamentali del flusso di controllo, ovvero i
meccanismi in base ai quali i programmi prendono decisioni e scelgono azioni diverse. Questa esercitazione è
disponibile anche per l'esecuzione in locale nel computer.
In questa esercitazione si presuppone che siano state completate le lezioni Hello World e numbers in C# .
Raccolte di elenchi
La lezione Raccolte di elenchi offre una panoramica delle raccolte di tipo List che consentono di archiviare
sequenze di dati. Si apprenderà come aggiungere e rimuovere elementi, eseguire la ricerca di elementi e
ordinare gli elenchi. Verranno esaminati diversi tipi di elenchi. Questa esercitazione è disponibile anche per
l'esecuzione in locale nel computer.
In questa esercitazione si presuppone che siano state completate le lezioni sopra elencate.

Introduzione alle classi


Questa esercitazione è disponibile solo per l'esecuzione nel computer, usando l'ambiente di sviluppo locale e
.NET Core. Verrà illustrato come creare un'applicazione console e verranno presentate le funzionalità orientate a
oggetti di base che fanno parte del linguaggio C#.
Questa esercitazione presuppone che siano state completate le esercitazioni introduttive online e che siano stati
installati .NET Core SDK e Visual Studio Code.

Programmazione orientata a oggetti


Questa esercitazione illustra i concetti usati nella programmazione orientata a oggetti. Verranno illustrati i
concetti di astrazione, incapsulamento, ereditarietàe polimorfismo con esempi di C#.
In questa esercitazione si presuppone che siano state completate le esercitazioni introduttive online e che siano
stati installati .NET Core SDK e Visual Studio Code o Visual Studio nel computer di sviluppo.

101 esempi LINQ


Per questo esempio è necessario lo strumento globale DotNet-try . Una volta installato lo strumento e clonato il
repository try-Samples , è possibile apprendere Language Integrated Query (LINQ) tramite un set di esempi
101 che è possibile eseguire in modo interattivo. È possibile esplorare diversi modi per eseguire query,
esplorare e trasformare le sequenze di dati.
Acquisire familiarità con gli strumenti di sviluppo
.NET
18/03/2020 • 3 minutes to read • Edit Online

Il primo passaggio dell'esecuzione di un'esercitazione nel computer consiste nel configurare l'ambiente di
sviluppo. L'esercitazione di .NET Hello World in 10 minuti contiene istruzioni per la configurazione dell'ambiente
di sviluppo locale in Windows, Linux o macOS.
In alternativa è possibile installare .NET Core SDK e Visual Studio Code.

Flusso di sviluppo di applicazioni di base


Le applicazioni verranno dotnet new create utilizzando il comando . Questo comando genera i file e gli asset
necessari per l'applicazione. Le esercitazioni introduttive su C# usano tutte il tipo di applicazione console . Dopo
aver acquisito i concetti di base, è possibile passare a tipi di applicazione più complessi.
Gli altri comandi che dotnet build verranno utilizzati dotnet run sono per compilare l'eseguibile e per
eseguire l'eseguibile.

Selezionare l'esercitazione
È possibile iniziare con una qualsiasi delle esercitazioni seguenti:

Numeri in C#
Nell'esercitazione Numeri in C# viene descritto il modo in cui i computer archiviano numeri per eseguire calcoli
con tipi numerici diversi. Verranno presentati i concetti di base dell'arrotondamento e informazioni su come
eseguire calcoli matematici con C#.
Questa esercitazione presuppone che sia stata già completata la lezione Hello World.

Rami e cicli
L'esercitazione Rami e cicli presenta i concetti di base della selezione di percorsi diversi di esecuzione del codice
in base ai valori archiviati in variabili. Si apprenderanno i concetti fondamentali del flusso di controllo, ovvero i
meccanismi in base ai quali i programmi prendono decisioni e scelgono azioni diverse.
Questa esercitazione presuppone che siano state già completate le lezioni Hello World e Numeri in C#.

Raccolte di elenchi
La lezione Raccolte di elenchi offre una panoramica delle raccolte di tipo List che consentono di archiviare
sequenze di dati. Si apprenderà come aggiungere e rimuovere elementi, eseguire la ricerca di elementi e
ordinare gli elenchi. Verranno esaminati diversi tipi di elenchi.
Questa esercitazione presuppone che siano state già completate le lezioni elencate sopra.

Introduzione alle classi


L'esercitazione introduttiva finale su C# è disponibile per l'esecuzione solo nel computer, usando l'ambiente di
sviluppo locale e .NET Core. Verrà illustrato come creare un'applicazione console e verranno presentate le
funzionalità orientate a oggetti di base che fanno parte del linguaggio C#.
Modificare numeri a virgola mobile e integrali in C#
06/05/2020 • 16 minutes to read • Edit Online

Questa esercitazione presenta in modo interattivo i tipi numerici in C#. Si scriveranno piccole quantità di codice,
quindi si compilerà ed eseguirà tale codice. L'esercitazione contiene una serie di lezioni che esplorano numeri e
operazioni matematiche in C#. Queste lezioni presentano le nozioni fondamentali del linguaggio C#.
Questa esercitazione prevede la presenza di un computer da usare per lo sviluppo. L'esercitazione .NET Hello
World in 10 minuti contiene le istruzioni per configurare l'ambiente di sviluppo locale in Windows, Linux o
MacOS. Una breve panoramica dei comandi usati è disponibile in Acquisire familiarità con gli strumenti di
sviluppo, che contiene collegamenti a informazioni più dettagliate.

Esplorare le operazioni matematiche su interi


Creare una directory denominata numbers-quickstart. Rendere la directory corrente ed eseguire il comando
seguente:

dotnet new console -n NumbersInCSharp -o .

Aprire Program.cs nell'editor preferito e sostituire la riga Console.WriteLine("Hello World!"); con il codice
seguente:

int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);

Eseguire questo codice digitando dotnet run nella finestra di comando.


È stata rilevata una delle operazioni matematiche fondamentali con numeri interi. Il int tipo rappresenta un
intero , un numero intero zero, positivo o negativo. Per l'addizione si usa il simbolo + . Altre operazioni
matematiche comuni per gli interi includono:
- per la sottrazione
* per la moltiplicazione
/ per la divisione
Per iniziare, esplorare le diverse operazioni. Aggiungere queste righe dopo quella indicante il valore di c :

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);

// division
c = a / b;
Console.WriteLine(c);
Eseguire questo codice digitando dotnet run nella finestra di comando.
È anche possibile provare a scrivere più operazioni matematiche nella stessa riga, se si preferisce. Provare,
esempio, c = a + b - 12 * 17; . È consentita la combinazione di variabili e numeri costanti.

TIP
Mentre si impara a usare C# (o qualsiasi linguaggio di programmazione) sicuramente si commetteranno errori durante la
scrittura del codice. Il compilatore troverà questi errori e li segnalerà. Quando l'output contiene messaggi di errore,
esaminare attentamente il codice di esempio e il codice nella finestra per scoprire che cosa correggere. Questo esercizio
sarà utile per imparare la struttura del codice C#.

Il primo passaggio è stato completato. Prima di iniziare la sezione successiva, è necessario spostare il codice
corrente in un metodo separato. In questo modo sarà più semplice iniziare a lavorare con un nuovo esempio.
Rinominare il metodo Main in WorkingWithIntegers e scrivere un nuovo metodo Main che chiama
WorkingWithIntegers . Al termine, il codice dovrebbe essere simile al seguente:

using System;

namespace NumbersInCSharp
{
class Program
{
static void WorkingWithIntegers()
{
int a = 18;
int b = 6;

// addition
int c = a + b;
Console.WriteLine(c);

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);

// division
c = a / b;
Console.WriteLine(c);
}

static void Main(string[] args)


{
WorkingWithIntegers();
}
}
}

Esplorare l'ordine delle operazioni


Impostare come commento la chiamata a WorkingWithIntegers() . L'output risulterà in questo modo meno
disordinato quando si usa questa sezione:

//WorkingWithIntegers();
// avvia un commento in C#. Un commento è un testo che si vuole conservare nel codice sorgente senza
eseguirlo come codice. Il compilatore non genera alcun codice eseguibile dai commenti.
Il linguaggio C# stabilisce un ordine di precedenza per le diverse operazioni matematiche, con regole coerenti
con quelle della matematica. La moltiplicazione e la divisione hanno la precedenza rispetto ad addizione e
sottrazione. Per esplorare questo comportamento, è possibile aggiungere il codice seguente al metodo Main ed
eseguire dotnet run :

int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);

L'output dimostra che la moltiplicazione viene eseguita prima dell'addizione.


È possibile forzare un ordine diverso per le operazioni racchiudendo tra parentesi l'operazione o le operazioni
che si vuole eseguire per prime. Aggiungere le righe seguenti e ripetere l'esecuzione:

d = (a + b) * c;
Console.WriteLine(d);

Sperimentare ulteriormente combinando molte operazioni diverse. Aggiungere righe simili alle seguenti in
fondo al metodo Main . Provare di nuovo dotnet run .

d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);

È possibile che si sia notato un comportamento interessante per gli interi. La divisione di interi genera sempre
un risultato intero, anche quando ci si aspetta che il risultato includa una parte decimale o frazionaria.
Se questo comportamento non è stato notato, provare il codice seguente alla fine del metodo Main :

int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);

Digitare di nuovo dotnet run per visualizzare i risultati.


Prima di continuare, tutto il codice scritto in questa sezione verrà inserito in un nuovo metodo. Chiamare il
nuovo metodo OrderPrecedence . È necessario scrivere un codice simile al seguente:
using System;

namespace NumbersInCSharp
{
class Program
{
static void WorkingWithIntegers()
{
int a = 18;
int b = 6;

// addition
int c = a + b;
Console.WriteLine(c);

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);

// division
c = a / b;
Console.WriteLine(c);
}

static void OrderPrecedence()


{
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);

d = (a + b) * c;
Console.WriteLine(d);

d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);

int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
}

static void Main(string[] args)


{
WorkingWithIntegers();

OrderPrecedence();

}
}
}

Esplorare la precisione e i limiti delle operazioni su interi


L'ultimo esempio dimostra che la divisione di interi tronca il risultato. È possibile ottenere il resto usando
l'operatore modulo , ovvero il carattere % . Provare il codice seguente nel metodo Main :
int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");

Il tipo integer in C# è diverso dagli interi matematici per un altro aspetto, ovvero per il tipo int esistono limiti
minimi e massimi. Aggiungere questo codice al metodo Main per visualizzare tali limiti:

int max = int.MaxValue;


int min = int.MinValue;
Console.WriteLine($"The range of integers is {min} to {max}");

Se un calcolo produce un valore che supera questi limiti, si genera una condizione di underflow o overflow . La
risposta sembra proseguire da un limite all'altro. Aggiungere queste due righe al metodo Main per visualizzare
un esempio:

int what = max + 3;


Console.WriteLine($"An example of overflow: {what}");

Si noti che la risposta è molto vicina all'intero minimo (negativo). È uguale a min + 2 . L'operazione di addizione
ha causato l'overflow dei valori consentiti per gli interi. La risposta è un numero negativo molto grande,
poiché un overflow "ritorna a capo" proseguendo dal valore intero più grande possibile a quello più piccolo.
Esistono altri tipi numerici con limiti e precisione diversi che è possibile usare quando il tipo int non soddisfa
le proprie esigenze. Verranno ora esaminati gli altri tipi.
Ancora una volta il codice scritto in questa sezione verrà spostato in un metodo separato. Denominarlo
TestLimits .

Usare il tipo double


Il tipo numerico double rappresenta un numero a virgola mobile a precisione doppia. Questi termini
potrebbero risultare sconosciuti. Un numero a virgola mobile è utile per rappresentare numeri non integrali,
con ordine di grandezza molto grande o molto piccolo. La precisione doppia è un termine relativo che
descrive il numero di cifre binarie usate per archiviare il valore. I numeri a doppia precisione hanno due volte
il numero di cifre binarie come precisione singola . Nei computer moderni è più comune usare la precisione
doppia rispetto ai numeri a precisione singola. I numeri a precisione singola vengono dichiarati utilizzando la
float parola chiave. Per iniziare a esplorare questo tipo, Aggiungere il codice seguente e visualizzare il
risultato:

double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);

Si noti che la risposta include la parte decimale del quoziente. Provare ora un'espressione leggermente più
complessa con valori double:
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);

L'intervallo di un valore double è molto maggiore rispetto ai valori integer. Provare il codice seguente sotto il
codice già scritto finora:

double max = double.MaxValue;


double min = double.MinValue;
Console.WriteLine($"The range of double is {min} to {max}");

Questi valori vengono stampati con la notazione scientifica. Il numero a sinistra di E rappresenta il
significando. Il numero a destra è l'esponente, come potenza di 10.
Come per i numeri decimali in matematica, i valori double in C# possono presentare errori di arrotondamento.
Provare questo codice:

double third = 1.0 / 3.0;


Console.WriteLine(third);

Si sa che 0.3 la ripetizione non è esattamente identica 1/3 a.


Esercizio
Provare altri calcoli con numeri grandi, numeri piccoli, moltiplicazioni e divisioni usando double il tipo. Provare
calcoli più complessi.
Dopo avere dedicato un po' di tempo all'esercizio, inserire il codice scritto in un nuovo metodo. Denominare il
nuovo metodo WorkWithDoubles .

Usare i tipi decimali


Sono già stati presentati i tipi numerici di base in C#, ovvero integer e double. Ecco un altro tipo di
apprendimento: il decimal tipo. Il tipo decimal ha un intervallo più piccolo, ma maggiore precisione di double .
Esaminare il codice seguente:

decimal min = decimal.MinValue;


decimal max = decimal.MaxValue;
Console.WriteLine($"The range of the decimal type is {min} to {max}");

Si noti che l'intervallo è minore rispetto al tipo double . Per vedere la maggiore precisione del tipo decimal,
provare il codice seguente:

double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);

decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);

Il suffisso M nei numeri è il modo in cui si indica che una costante deve usare il tipo decimal . In caso contrario,
il compilatore double presuppone il tipo.
NOTE
La lettera M è stata scelta come lettera più visivamente distinta tra double le decimal parole chiave e.

Si noti che le operazioni matematiche con il tipo decimal includono più cifre a destra del separatore decimale.
Esercizio
Dopo aver esaminato i diversi tipi numerici, scrivere codice che calcola l'area di un cerchio con raggio di 2,5 cm.
Ricordarsi che l'area di un cerchio si calcola moltiplicando il quadrato del raggio per Pi greco. Suggerimento:
.NET contiene una costante per Pi greco, Math.PI, che è possibile usare per tale valore. Math.PI, come tutte le
costanti dichiarate System.Math nello spazio dei nomi double , è un valore. Per questo motivo, è consigliabile
usare double anziché decimal i valori per questa richiesta di verifica.
Si otterrà una risposta compresa tra 19 e 20. È possibile controllare la risposta esaminando il codice di esempio
completato su GitHub.
È anche possibile provare alcune altre formule.
È stata completata la guida introduttiva "Numeri in C#". È possibile continuare con la guida introduttiva Rami e
cicli nel proprio ambiente di sviluppo.
È possibile ottenere altre informazioni sui numeri in C# negli articoli seguenti:
Tipi numerici integrali
Tipi numerici a virgola mobile
Conversioni numeriche predefinite
Informazioni sulla logica condizionale con istruzioni
per rami e cicli
14/05/2020 • 17 minutes to read • Edit Online

Questa esercitazione descrive come scrivere codice che esamina le variabili e modifica il percorso di esecuzione
in base a queste variabili. Verranno descritte le procedure per scrivere codice C# e visualizzare i risultati della
compilazione ed esecuzione del codice. L'esercitazione contiene una serie di lezioni che esplorano i costrutti per
rami e cicli in C#. Queste lezioni presentano le nozioni fondamentali del linguaggio C#.
Questa esercitazione prevede la presenza di un computer da usare per lo sviluppo. L'esercitazione .NET Hello
World in 10 minuti contiene le istruzioni per configurare l'ambiente di sviluppo locale in Windows, Linux o
MacOS. Una breve panoramica dei comandi usati è disponibile in Acquisire familiarità con gli strumenti di
sviluppo, che contiene collegamenti a informazioni più dettagliate.

Prendere decisioni usando l'istruzione if


Creare una directory denominata branches-tutorial. Rendere la directory corrente ed eseguire il comando
seguente:

dotnet new console -n BranchesAndLoops -o .

Questo comando crea una nuova applicazione console .NET Core nella directory corrente.
Aprire Program.cs nell'editor preferito e sostituire la riga Console.WriteLine("Hello World!"); con il codice
seguente:

int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");

Per provare questo codice, digitare dotnet run nella finestra della console. Verrà visualizzato il messaggio "The
answer is greater than 10." nella console.
Modificare la dichiarazione di b in modo che la somma sia minore di 10:

int b = 3;

Digitare di nuovo dotnet run . Dato che la risposta è minore a 10, non viene visualizzato nulla. La condizione
testata è false. Non esiste codice da eseguire perché è stato scritto solo uno dei possibili rami per un'istruzione
if , ovvero il ramo true.

TIP
Mentre si impara a usare C# (o qualsiasi linguaggio di programmazione) sicuramente si commetteranno errori durante la
scrittura del codice. Il compilatore troverà e segnalerà gli errori. Esaminare attentamente l'output dell'errore e il codice che
ha generato l'errore. L'errore del compilatore consente in genere di trovare il problema.
Questo primo esempio dimostra le potenzialità di if e dei tipi booleani. Un valore booleano è una variabile che
può avere uno di due valori: true o false . C# definisce un tipo speciale, bool per le variabili booleane.
L'istruzione if controlla il valore di un bool . Quando il valore è true , viene eseguita l'istruzione che segue
if . In caso contrario, viene ignorato.

Questo processo di verifica delle condizioni ed esecuzione di istruzioni basate su tali condizioni è potente.

Usare insieme if ed else


Per eseguire codice diverso per i rami true e false, è necessario creare un ramo else che viene eseguito quando
la condizione è false. Provare questo codice. Aggiungere le ultime due righe del codice seguente al metodo
Main (le prime quattro dovrebbero essere già presenti):

int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");

L'istruzione che segue la parola chiave else viene eseguita solo quando la condizione testata è false . La
combinazione di if e else con condizioni booleane offre tutte le potenzialità necessarie per gestire sia una
condizione true che una condizione false .

IMPORTANT
Il rientro applicato alle righe successive alle istruzioni if e else è pensato per i lettori umani. Nel linguaggio C# i rientri
o gli spazi vuoti non sono significativi. L'istruzione che segue la parola chiave if o else verrà eseguita in base alla
condizione. Tutti gli esempi in questa esercitazione seguono una procedura comune che prevede il rientro delle righe in
base al flusso di controllo delle istruzioni.

Poiché il rientro non è significativo, è necessario usare { e } per indicare quando si desidera che più di
un'istruzione faccia parte del blocco eseguito in modo condizionale. I programmatori C# usano in genere le
parentesi graffe in tutte le clausole if e else . L'esempio seguente è identico a quello creato dall'utente.
Modificare il codice precedente in modo che corrisponda al codice seguente:

int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}

TIP
Nelle parti restanti di questa esercitazione, tutti gli esempi di codice includono le parentesi graffe conformemente alle
consuetudini comuni.

È possibile testare condizioni più complesse. Aggiungere il codice seguente nel metodo Main dopo il codice
scritto finora:
int c = 4;
if ((a + b + c > 10) && (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
}

Il simbolo == verifica l'uguaglianza. Il simbolo == distingue il test per l'uguaglianza dall'assegnazione,


illustrata in a = 5 .
&& rappresenta "e" e significa che entrambe le condizioni devono essere true per eseguire l'istruzione nel ramo
true. Questi esempi mostrano anche che è possibile includere più istruzioni in ogni ramo condizionale, a
condizione di racchiuderle tra { e } .
È anche possibile usare || per rappresentare "or". Aggiungere il codice seguente dopo il codice già scritto
finora:

if ((a + b + c > 10) || (a == b))


{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not equal to the second");
}

Modificare i valori di a , b e c e passare da && a || per esplorare. Si otterranno più informazioni sul
funzionamento degli operatori && e || .
Il primo passaggio è stato completato. Prima di iniziare la sezione successiva, è necessario spostare il codice
corrente in un metodo separato. In questo modo sarà più semplice iniziare a lavorare con un nuovo esempio.
Rinominare il metodo Main in ExploreIf e scrivere un nuovo metodo Main che chiama ExploreIf . Al
termine, il codice dovrebbe essere simile al seguente:
using System;

namespace BranchesAndLoops
{
class Program
{
static void ExploreIf()
{
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}

int c = 4;
if ((a + b + c > 10) && (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the second");
}

if ((a + b + c > 10) || (a > b))


{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is greater than the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not greater than the second");
}
}

static void Main(string[] args)


{
ExploreIf();
}
}
}

Impostare come commento la chiamata a ExploreIf() . L'output risulterà in questo modo meno disordinato
quando si usa questa sezione:

//ExploreIf();

// avvia un commento in C#. Un commento è un testo che si vuole conservare nel codice sorgente senza
eseguirlo come codice. Il compilatore non genera alcun codice eseguibile dai commenti.

Usare i cicli per ripetere le operazioni


In questa sezione si usano i cicli per ripetere le istruzioni. Provare questo codice nel metodo Main :
int counter = 0;
while (counter < 10)
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
}

L'istruzione while verifica una condizione ed esegue l'istruzione o il blocco di istruzioni che segue while .
Ripete la verifica della condizione e l'esecuzione di tali istruzioni fino a quando la condizione è false.
Questo esempio include un altro operatore nuovo. I caratteri ++ dopo la variabile counter rappresentano
l'operatore di incremento . Questo operatore aggiunge 1 al valore di counter e archivia il valore nella variabile
counter .

IMPORTANT
Assicurarsi che la condizione del ciclo while passi a false quando si esegue il codice. In caso contrario, si crea un ciclo
infinito in cui il programma non termina mai. Tale situazione non è illustrata in questo esempio, perché è necessario
forzare l'uscita dal programma usando CTRL+C o in altro modo.

Il ciclo while testa la condizione prima di eseguire il codice dopo while . Il ciclo do ... while esegue prima il
codice e poi controlla la condizione, Il ciclo do while è illustrato nel codice seguente:

int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);

Questo ciclo do e il ciclo while precedente generano lo stesso output.

Usare il ciclo for


Il ciclo for viene in genere usato in C#. Provare questo codice nel metodo Main():

for (int index = 0; index < 10; index++)


{
Console.WriteLine($"Hello World! The index is {index}");
}

Il codice precedente esegue le stesse operazioni del while ciclo e del do ciclo già usato. L'istruzione for è
composta da tre parti che ne controllano il funzionamento.
La prima parte è l' inizializzatore for : int index = 0; dichiara che index è la variabile del ciclo e imposta il
valore iniziale su 0 .
La parte intermedia è la condizione for : index < 10 dichiara che questo for ciclo continua a essere eseguito
fino a quando il valore del contatore è minore di 10.
La parte finale è l' iteratore for : index++ specifica come modificare la variabile del ciclo dopo l'esecuzione del
blocco dopo l' for istruzione. In questo caso, specifica che index deve essere incrementato di 1 a ogni
esecuzione del blocco.
Sperimentare. Provare ognuna delle varianti seguenti:
Cambiare l'inizializzatore in modo che inizi da un valore diverso.
Cambiare la condizione in modo che si interrompa in corrispondenza di un valore diverso.
Al termine, passare alla prossima lezione che prevede la scrittura di codice per usare quanto finora appreso.
Esiste un'altra istruzione di ciclo che non è trattata in questa esercitazione: l' foreach istruzione. L' foreach
istruzione ripete la relativa istruzione per ogni elemento di una sequenza di elementi. Viene spesso usato con le
raccolte, quindi viene trattato nell'esercitazione successiva.

Cicli annidati creati


Un while do ciclo, o for può essere annidato all'interno di un altro ciclo per creare una matrice utilizzando la
combinazione di ogni elemento del ciclo esterno con ogni elemento del ciclo interno. A questo scopo, è
necessario compilare un set di coppie alfanumeriche per rappresentare righe e colonne.
Un for ciclo può generare le righe:

for (int row = 1; row < 11; row++)


{
Console.WriteLine($"The row is {row}");
}

Un altro ciclo può generare le colonne:

for (char column = 'a'; column < 'k'; column++)


{
Console.WriteLine($"The column is {column}");
}

È possibile annidare un ciclo all'interno dell'altro per formare coppie:

for (int row = 1; row < 11; row++)


{
for (char column = 'a'; column < 'k'; column++)
{
Console.WriteLine($"The cell is ({row}, {column})");
}
}

È possibile osservare che il ciclo esterno viene incrementato una volta per ogni esecuzione completa del ciclo
interno. Invertire l'annidamento di righe e colonne e visualizzare le modifiche per se stessi.

Combinare rami e cicli


Dopo aver esaminato l'istruzione if e i costrutti per i cicli nel linguaggio C#, provare a scrivere codice C# per
ottenere la somma di tutti i numeri interi da 1 a 20 divisibili per 3. Ecco alcuni suggerimenti:
L'operatore % restituisce il resto di un'operazione di divisione.
L'istruzione if offre la condizione per stabilire se un numero deve fare parte della somma.
Il ciclo for può essere utile per ripetere una serie di passaggi per tutti i numeri da 1 a 20.

Sperimentare e quindi controllare i risultati. Verrà visualizzata una risposta 63. Per vedere una possibile risposta,
visualizzare il codice completo in GitHub.
È stata completata l'esercitazione "Cicli e rami".
È possibile continuare con l'esercitazione Matrici e raccolte nel proprio ambiente di sviluppo.
Per altre informazioni su questi concetti, vedere questi articoli:
Istruzioni if ed else
Istruzione while
Istruzione do
Istruzione for
Informazioni su come gestire le raccolte dati tramite
il tipo di elenco generico
02/11/2020 • 10 minutes to read • Edit Online

Questa esercitazione introduttiva offre un'introduzione al linguaggio C# e i concetti di base relativi alla classe
List<T>.
Questa esercitazione prevede la presenza di un computer da usare per lo sviluppo. L'esercitazione .NET Hello
World in 10 minuti contiene le istruzioni per configurare l'ambiente di sviluppo locale in Windows, Linux o
MacOS. Una breve panoramica dei comandi usati è disponibile in Acquisire familiarità con gli strumenti di
sviluppo, che contiene collegamenti a informazioni più dettagliate.

Esempio di elenco di base


Creare una directory denominata list-tutorial. Impostarla come directory corrente ed eseguire
dotnet new console .

Aprire Program.cs nell'editor preferito e sostituire il codice esistente con il seguente:

using System;
using System.Collections.Generic;

namespace list_tutorial
{
class Program
{
static void Main(string[] args)
{
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
}
}

Sostituire <name> con il proprio nome. Salvare Program.cs. Digitare dotnet run nella finestra della console per
provare il codice.
Questo codice consente di creare un elenco di stringhe, aggiungere tre nomi all'elenco e stampare i nomi in
lettere maiuscole. Vengono usati i concetti appresi nelle esercitazioni precedenti per eseguire il ciclo dell'elenco.
Il codice per visualizzare i nomi usa la funzionalità di interpolazione di stringhe. Anteponendo il carattere $ a
string , è possibile incorporare il codice C# nella dichiarazione della stringa. La stringa effettiva sostituisce il
codice C# con il valore generato. In questo esempio, {name.ToUpper()} viene sostituito con ogni nome,
convertito in lettere maiuscole, perché è stato chiamato il metodo ToUpper.
L'esplorazione continua nelle prossime lezioni.

Modificare il contenuto dell'elenco


La raccolta creata usa il tipo List<T>. Questo tipo archivia sequenze di elementi. Il tipo degli elementi viene
specificato tra parentesi uncinate.
Un aspetto importante di questo tipo di List<T> è che supporta estensioni o riduzioni, consentendo l'aggiunta o
la rimozione di elementi. Aggiungere questo codice prima della parentesi } di chiusura nel metodo Main :

Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Sono stati aggiunti altri due nomi alla fine dell'elenco e ne è stato anche rimosso uno. Salvare il file e digitare
dotnet run per provare il codice.

List<T> consente di fare riferimento a singoli elementi anche in base all'indice . Inserire l'indice tra i token [ e
] dopo il nome dell'elenco. C# usa il valore 0 per il primo indice. Aggiungere questo codice direttamente sotto
il codice appena aggiunto e provarlo:

Console.WriteLine($"My name is {names[0]}");


Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");

Non è possibile accedere a un indice oltre la fine dell'elenco. Tenere presente che gli indici iniziano da 0,
pertanto l'indice massimo valido è minore di un'unità rispetto al numero di elementi nell'elenco. È possibile
controllare la lunghezza dell'elenco tramite la proprietà Count. Aggiungere il codice seguente alla fine del
metodo Main:

Console.WriteLine($"The list has {names.Count} people in it");

Salvare il file e digitare di nuovo dotnet run per visualizzare i risultati.

Eseguire ricerche negli elenchi e ordinarli


In questi esempi vengono usati elenchi relativamente piccoli, ma le applicazioni reali creano spesso elenchi con
molti più elementi, a volte anche migliaia. Per trovare elementi in raccolte di tali dimensioni, è necessario avere
la possibilità di eseguire ricerche nell'elenco. Il metodo IndexOf cerca un elemento e ne restituisce l'indice. Se
l'elemento non è presente nell'elenco, IndexOf restituisce -1 . Aggiungere questo codice in fondo al metodo
Main :
var index = names.IndexOf("Felipe");
if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns {index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");
}

index = names.IndexOf("Not Found");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns {index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");

Gli elementi in un elenco possono anche essere ordinati. Il Sort metodo ordina in ordine normale tutti gli
elementi dell'elenco (in ordine alfabetico per le stringhe). Aggiungere questo codice in fondo al metodo Main :

names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Salvare il file e digitare dotnet run per provare quest'ultima versione.


Prima di iniziare la sezione successiva, è necessario spostare il codice corrente in un metodo separato. In questo
modo sarà più semplice iniziare a lavorare con un nuovo esempio. Rinominare il metodo Main in
WorkingWithStrings e scrivere un nuovo metodo Main che chiama WorkingWithStrings . Al termine, il codice
dovrebbe essere simile al seguente:
using System;
using System.Collections.Generic;

namespace list_tutorial
{
class Program
{
static void Main(string[] args)
{
WorkingWithStrings();
}

static void WorkingWithStrings()


{
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Console.WriteLine($"My name is {names[0]}");


Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");

Console.WriteLine($"The list has {names.Count} people in it");

var index = names.IndexOf("Felipe");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns {index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");
}

index = names.IndexOf("Not Found");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns {index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");

names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
}
}

Elenchi di altri tipi


Finora è stato usato il tipo string negli elenchi. Di seguito verrà creato un List<T> con un tipo diverso. Per
iniziare, creare un set di numeri.
Aggiungere il codice seguente alla fine del nuovo metodo Main :

var fibonacciNumbers = new List<int> {1, 1};

Questo codice crea un elenco di interi e imposta i primi due interi sul valore 1. Questi sono i primi due valori di
una successione di Fibonacci, ovvero una sequenza di numeri. Ogni numero della successione di Fibonacci viene
ottenuto dalla somma dei due numeri precedenti. Aggiungere questo codice:

var previous = fibonacciNumbers[fibonacciNumbers.Count - 1];


var previous2 = fibonacciNumbers[fibonacciNumbers.Count - 2];

fibonacciNumbers.Add(previous + previous2);

foreach (var item in fibonacciNumbers)


Console.WriteLine(item);

Salvare il file e digitare dotnet run per visualizzare i risultati.

TIP
Per concentrarsi solo su questa sezione, è possibile impostare come commento il codice che chiama
WorkingWithStrings(); . Inserire due caratteri / prima della chiamata come di seguito: // WorkingWithStrings(); .

Sfida
È il momento di scoprire se è possibile mettere in pratica alcuni dei concetti appresi in questa lezione e in quelle
precedenti. Applicare i concetti appresi finora in relazione ai numeri di Fibonacci. Provare a scrivere il codice per
generare i primi 20 numeri nella successione. Tenere presente che il 20° numero di Fibonacci è 6765.

Completare l'esercizio
È possibile visualizzare una soluzione di esempio esaminando il codice di esempio completato su GitHub.
A ogni iterazione del ciclo, gli ultimi due interi nell'elenco vengono sommati e il valore risultante viene aggiunto
all'elenco. Il ciclo viene ripetuto fino ad aggiungere 20 elementi all'elenco.
Complimenti, è stata completata questa esercitazione dedicata agli elenchi. È possibile continuare con
l'esercitazione Introduzione alle classi nell'ambiente di sviluppo.
Per altre informazioni sull'uso del List tipo, vedere l'articolo Nozioni fondamentali su .NET nelle raccolte.
Questo argomento include anche informazioni su molti altri tipi di raccolta.
Esplorare la programmazione orientata agli oggetti
con classi e oggetti
02/11/2020 • 17 minutes to read • Edit Online

Questa esercitazione prevede la presenza di un computer da usare per lo sviluppo. L'esercitazione .NET Hello
World in 10 minuti contiene le istruzioni per configurare l'ambiente di sviluppo locale in Windows, Linux o
MacOS. Una breve panoramica dei comandi usati è disponibile in Acquisire familiarità con gli strumenti di
sviluppo, che contiene collegamenti a informazioni più dettagliate.

Creare l'applicazione
Usare una finestra del terminale per creare una directory denominata classes. L'applicazione verrà creata in
questa posizione. Passare alla directory e digitare dotnet new console nella finestra della console. Questo
comando crea l'applicazione. Aprire Program.cs. Avrà un aspetto simile al seguente:

using System;

namespace classes
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

In questa esercitazione verranno creati nuovi tipi che rappresentano un conto bancario. In genere, gli
sviluppatori definiscono ogni classe in un file di testo diverso. In questo modo è più semplice gestire l'aumento
di dimensioni di un programma. Creare un nuovo file denominato BankAccount.cs nella directory classes.
Questo file conterrà la definizione di un *conto bancario . La programmazione orientata a oggetti organizza il
codice creando tipi sotto forma di classi. Queste classi contengono il codice che rappresenta un'entità specifica.
La classe BankAccount rappresenta un conto bancario. Il codice implementa operazioni specifiche tramite
metodi e proprietà. In questa esercitazione il conto bancario supporta questo comportamento:
1. Esiste un numero di 10 cifre che identifica in modo univoco il conto.
2. Esiste una stringa in cui vengono archiviati i nomi dei titolari.
3. È possibile recuperare il saldo.
4. Il conto accetta versamenti.
5. Il conto accetta prelievi.
6. Il saldo iniziale deve essere positivo.
7. I prelievi non possono risultare in un saldo negativo.

Definire il tipo di conto bancario


Per iniziare, è possibile creare gli elementi di base di una classe che definisce questo comportamento. Creare un
nuovo file usando il comando _*file: New**. Denominarlo BankAccount.cs. Aggiungere il codice seguente al file
BankAccount.cs :
using System;

namespace classes
{
public class BankAccount
{
public string Number { get; }
public string Owner { get; set; }
public decimal Balance { get; }

public void MakeDeposit(decimal amount, DateTime date, string note)


{
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
}
}
}

Prima di procedere, è opportuno esaminare il codice finora creato. La dichiarazione namespace offre un modo
per organizzare logicamente il codice. Poiché questa esercitazione è relativamente breve, si inserirà il codice in
uno spazio dei nomi.
public class BankAccount definisce la classe, o tipo, che si sta creando. Tutti gli elementi all'interno di { e }
che seguono la dichiarazione di classe definiscono lo stato e il comportamento della classe. Sono disponibili
cinque *membri _ della BankAccount classe. Le prime tre sono Proprietà. Le proprietà sono elementi di dati e
possono contenere codice per l'applicazione della convalida o di altre regole. Gli ultimi due sono i Metodi. I
metodi sono blocchi di codice che eseguono una singola funzione. La lettura dei nomi di ogni membro
dovrebbe fornire informazioni sufficienti per consentire all'utente o a un altro sviluppatore di capire gli scopi
della classe.

Aprire un nuovo conto


La prima funzionalità da implementare è l'apertura di un conto bancario. Quando un cliente apre un conto, deve
specificare il saldo iniziale e informazioni sul titolare o i titolari del conto.
La creazione di un nuovo oggetto di BankAccount tipo significa definire un Costruttore che assegna tali valori.
Un Costruttore è un membro con lo stesso nome della classe. e viene usato per inizializzare oggetti di quel tipo
di classe. Aggiungere il costruttore seguente al BankAccount tipo. Inserire il codice seguente sopra la
dichiarazione di MakeDeposit :

public BankAccount(string name, decimal initialBalance)


{
this.Owner = name;
this.Balance = initialBalance;
}

I costruttori vengono chiamati quando si crea un oggetto usando new . Sostituire la riga
Console.WriteLine("Hello World!"); in _Program. cs * con il codice seguente (sostituire <name> con il proprio
nome):

var account = new BankAccount("<name>", 1000);


Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial
balance.");

Eseguiamo ora le operazioni create. Se si usa Visual Studio, scegliere Avvia senza eseguire debug dal menu
Esegui . Se si usa una riga di comando, digitare dotnet run la directory in cui è stato creato il progetto.
Si è notato che il numero di conto è vuoto? È tempo di correggere questa mancanza. Il numero di conto deve
essere assegnato al momento della costruzione dell'oggetto, ma la sua creazione non deve essere responsabilità
del chiamante. Il codice della classe BankAccount deve sapere come assegnare nuovi numeri di conto. Un modo
semplice per eseguire questa operazione consiste nell'iniziare con un numero da 10 cifre e incrementarlo per la
creazione di ogni nuovo conto. Infine, il numero di conto corrente deve essere archiviato quando viene costruito
un oggetto.
Aggiungere una dichiarazione di membro alla BankAccount classe. Posizionare la riga di codice seguente dopo la
parentesi graffa { di apertura all'inizio della BankAccount classe:

private static int accountNumberSeed = 1234567890;

Questo è un membro dati. Il membro è private , ovvero è accessibile solo dal codice all'interno della classe
BankAccount . Si tratta di un modo per separare le responsabilità pubbliche, ad esempio la presenza di un
numero di conto, dall'implementazione privata (in che modo vengono generati i numeri di account). È anche
static , ovvero è condiviso tra tutti gli oggetti BankAccount . Il valore di una variabile non statica è univoco per
ogni istanza dell'oggetto BankAccount . Aggiungere le due righe seguenti al costruttore per assegnare il numero
di conto. Posizionarli dopo la riga seguente this.Balance = initialBalance :

this.Number = accountNumberSeed.ToString();
accountNumberSeed++;

Digitare dotnet run per visualizzare i risultati.

Creare versamenti e prelievi


La classe del conto bancario deve accettare versamenti e prelievi per funzionare correttamente. I versamenti e i
prelievi vengono implementati tramite la creazione di un registro di ogni transazione per il conto. Ciò presenta
alcuni vantaggi rispetto al semplice aggiornamento del saldo per ogni transazione. È possibile usare la
cronologia per controllare tutte le transazioni e gestire i saldi giornalieri. Grazie alla possibilità di calcolare il
saldo dalla cronologia di tutte le transazioni all'occorrenza, eventuali errori in una singola transazione risolti
saranno rispecchiati correttamente nel saldo dopo il calcolo successivo.
Per iniziare, occorre creare un nuovo tipo per rappresentare una transazione. Si tratta di un tipo semplice senza
responsabilità, che richiede alcune proprietà. Creare un nuovo file denominato Transaction.cs. Aggiungere alla
classe il codice seguente:
using System;

namespace classes
{
public class Transaction
{
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }

public Transaction(decimal amount, DateTime date, string note)


{
this.Amount = amount;
this.Date = date;
this.Notes = note;
}
}
}

A questo punto, aggiungere un List<T> di oggetti Transaction alla classe BankAccount . Aggiungere la
dichiarazione seguente dopo il costruttore nel file BankAccount.cs :

private List<Transaction> allTransactions = new List<Transaction>();

La classe List<T> richiede di importare uno spazio dei nomi diverso. Aggiungere il codice seguente all'inizio di
BankAccount.cs:

using System.Collections.Generic;

A questo punto, verrà calcolato correttamente Balance . Il saldo corrente può essere trovato sommando i valori
di tutte le transazioni. Poiché il codice è attualmente, è possibile ottenere solo il saldo iniziale dell'account, quindi
è necessario aggiornare la Balance Proprietà. Sostituire la riga public decimal Balance { get; } in
BankAccount.cs con il codice seguente:

public decimal Balance


{
get
{
decimal balance = 0;
foreach (var item in allTransactions)
{
balance += item.Amount;
}

return balance;
}
}

Questo esempio illustra un aspetto importante delle**Proprietà***. Il saldo viene ora calcolato quando un altro
programmatore richiede il valore. Il calcolo enumera tutte le transazioni e fornisce la somma come saldo
corrente.
Implementare ora i metodi MakeDeposit e MakeWithdrawal . Questi metodi applicheranno le due regole finali,
ovvero che il saldo iniziale deve essere positivo e che qualsiasi prelievo non può risultare in un saldo negativo.
Viene introdotto il concetto di eccezioni. Il modo standard per indicare che un metodo non può completare
correttamente le operazioni previste consiste nel generare un'eccezione. Il tipo di eccezione e il messaggio
associato descrivono l'errore. In questo caso, il metodo MakeDeposit genera un'eccezione se l'importo del
versamento è negativo. Il MakeWithdrawal metodo genera un'eccezione se l'importo del prelievo è negativo o se
l'applicazione del prelievo comporta un saldo negativo. Aggiungere il codice seguente dopo la dichiarazione
dell' allTransactions elenco:

public void MakeDeposit(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
}
var deposit = new Transaction(amount, date, note);
allTransactions.Add(deposit);
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
if (Balance - amount < 0)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
}

L' throw istruzione _genera* un'eccezione. L'esecuzione del blocco corrente termina e il controllo viene
trasferito al primo blocco catch corrispondente trovato nello stack di chiamate. Più avanti si aggiungerà un
blocco catch per testare questo codice.
Il costruttore deve ottenere una modifica in modo da aggiungere una transazione iniziale, invece di aggiornare
direttamente il saldo. Dato che il metodo MakeDeposit è già stato scritto, chiamarlo dal costruttore. Il costruttore
completato dovrebbe essere simile al seguente:

public BankAccount(string name, decimal initialBalance)


{
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;

this.Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now è una proprietà che restituisce la data e l'ora correnti. Testare questo problema aggiungendo
alcuni depositi e prelievi nel Main metodo, seguendo il codice che crea un nuovo BankAccount :

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");


Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);

Successivamente, verificare che si verifichino le condizioni di errore provando a creare un account con un saldo
negativo. Aggiungere il codice seguente dopo il codice precedente appena aggiunto:
// Test that the initial balances must be positive.
try
{
var invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Exception caught creating account with negative balance");
Console.WriteLine(e.ToString());
}

Usare le try catch istruzioni e per contrassegnare un blocco di codice che può generare eccezioni e per
rilevare gli errori previsti. È possibile utilizzare la stessa tecnica per testare il codice che genera un'eccezione per
un saldo negativo. Aggiungere il codice seguente alla fine del Main Metodo:

// Test for a negative balance.


try
{
account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
Console.WriteLine("Exception caught trying to overdraw");
Console.WriteLine(e.ToString());
}

Salvare il file e digitare dotnet run per provare il codice.

Esercizio: registrare tutte le transazioni


Per completare questa esercitazione, è possibile scrivere il metodo GetAccountHistory , che crea un oggetto
string per la cronologia delle transazioni. Aggiungere questo metodo al tipo BankAccount :

public string GetAccountHistory()


{
var report = new System.Text.StringBuilder();

decimal balance = 0;
report.AppendLine("Date\t\tAmount\tBalance\tNote");
foreach (var item in allTransactions)
{
balance += item.Amount;
report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
}

return report.ToString();
}

Questo codice usa la classe StringBuilder per formattare una stringa che contiene una riga per ogni transazione.
Il codice per la formattazione delle stringhe è stato presentato in precedenza in queste esercitazioni. \t è un
nuovo carattere, che consente di inserire una tabulazione per formattare l'output.
Aggiungere questa riga per testarla in Program.cs:

Console.WriteLine(account.GetAccountHistory());

Eseguire il programma per visualizzare i risultati.


Passaggi successivi
Se si è rimasti bloccati, è possibile visualizzare l'origine di questa esercitazione nel repository GitHub.
È possibile continuare con l'esercitazione relativa alla programmazione orientata a oggetti .
Per altre informazioni su questi concetti, vedere questi articoli:
Istruzioni if ed else
Istruzione while
Istruzione do
Istruzione for
Programmazione Object-Oriented (C#)
28/01/2021 • 21 minutes to read • Edit Online

C# è un linguaggio orientato a oggetti. Quattro delle tecniche chiave utilizzate nella programmazione orientata a
oggetti sono:
Astrazione significa nascondere i dettagli superflui dai consumer dei tipi.
L'incapsulamento indica che un gruppo di proprietà, metodi e altri membri correlati vengono considerati
come una singola unità o un singolo oggetto.
L'ereditarietà indica la capacità di creare nuove classi sulla base di una classe esistente.
Il polimorfismo indica la capacità di usare più classi in modo intercambiabile, anche se in ognuna di esse le
stesse proprietà o gli stessi metodi sono implementati in modi diversi.
Nell'esercitazione precedente, Introduzione alle classi è stato visto sia astrazione che incapsulamento. La
BankAccount classe fornisce un'astrazione per il concetto di un conto bancario. È possibile modificare la relativa
implementazione senza influire sul codice che usava la BankAccount classe. Entrambe le BankAccount
Transaction classi e forniscono l'incapsulamento dei componenti necessari per descrivere tali concetti nel
codice.
In questa esercitazione si estenderà l'applicazione in modo da usare l' ereditarietà e il polimorfismo per
aggiungere nuove funzionalità. Verranno anche aggiunte funzionalità alla BankAccount classe, sfruttando le
tecniche di astrazione e incapsulamento apprese nell'esercitazione precedente.

Creazione di tipi diversi di account


Dopo la compilazione di questo programma, si ottengono richieste per aggiungere funzionalità. Funziona
benissimo nella situazione in cui è presente un solo tipo di conto bancario. Nel tempo, sono necessarie
modifiche e sono richiesti tipi di account correlati:
Un account di guadagno di interesse che accumula interesse alla fine di ogni mese.
Una linea di credito che può avere un saldo negativo, ma quando si verifica un saldo, ogni mese viene
addebitato un interesse.
Un account con carta regalo prepagata che inizia con un solo deposito e che può essere pagato solo. È
possibile ricaricare una volta all'inizio di ogni mese.
Tutti questi account diversi sono simili alla BankAccount classe definita nell'esercitazione precedente. È possibile
copiare il codice, rinominare le classi e apportare modifiche. Questa tecnica funzionerebbe a breve termine, ma
sarebbe più opportuno lavorare nel tempo. Tutte le modifiche verranno copiate in tutte le classi interessate.
È invece possibile creare nuovi tipi di conto bancario che ereditano i metodi e i dati dalla BankAccount classe
creata nell'esercitazione precedente. Queste nuove classi possono estendere la BankAccount classe con il
comportamento specifico necessario per ogni tipo:
public class InterestEarningAccount : BankAccount
{
}

public class LineOfCreditAccount : BankAccount


{
}

public class GiftCardAccount : BankAccount


{
}

Ognuna di queste classi eredita il comportamento condiviso dalla relativa classe base condivisa, ovvero la
BankAccount classe. Scrivere le implementazioni per funzionalità nuove e diverse in ognuna delle classi derivate.
Queste classi derivate dispongono già di tutto il comportamento definito nella BankAccount classe.
È consigliabile creare ogni nuova classe in un file di origine diverso. In Visual Studioè possibile fare clic con il
pulsante destro del mouse sul progetto e selezionare Aggiungi classe per aggiungere una nuova classe in un
nuovo file. In Visual Studio Codeselezionare nuovo file per creare un nuovo file di origine. In uno degli strumenti
assegnare un nome al file in modo che corrisponda alla classe: InterestEarningAccount.cs,
LineOfCreditAccount.cs e GiftCardAccount.cs.
Quando si creano le classi come illustrato nell'esempio precedente, si noterà che nessuna delle classi derivate
viene compilata. Un costruttore è responsabile dell'inizializzazione di un oggetto. Un costruttore della classe
derivata deve inizializzare la classe derivata e fornire istruzioni su come inizializzare l'oggetto della classe di
base incluso nella classe derivata. L'inizializzazione corretta avviene normalmente senza codice aggiuntivo. La
BankAccount classe dichiara un costruttore pubblico con la firma seguente:

public BankAccount(string name, decimal initialBalance)

Il compilatore non genera un costruttore predefinito quando si definisce manualmente un costruttore. Ciò
significa che ogni classe derivata deve chiamare in modo esplicito questo costruttore. Si dichiara un costruttore
che può passare argomenti al costruttore della classe base. Il codice seguente illustra il costruttore per
InterestEarningAccount :

public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)


{
}

I parametri per questo nuovo costruttore corrispondono al tipo di parametro e ai nomi del costruttore della
classe base. Usare la : base() sintassi per indicare una chiamata a un costruttore della classe base. Alcune
classi definiscono più costruttori. questa sintassi consente di selezionare il costruttore della classe base che si
chiama. Una volta aggiornati i costruttori, è possibile sviluppare il codice per ognuna delle classi derivate. I
requisiti per le nuove classi possono essere dichiarati come segue:
Un account di guadagno di interesse:
Otterrà un credito pari al 2% del saldo finale del mese.
Una linea di credito:
Può avere un saldo negativo, ma non essere maggiore in un valore assoluto rispetto al limite di
credito.
Ogni mese comporterà un addebito per gli interessi, in cui il saldo della fine del mese non è 0.
Comporterà una tariffa per ogni prelievo che supera il limite di credito.
Un account di carta da regalo:
Può essere riempito con un importo specificato una volta al mese, l'ultimo giorno del mese.
Può essere riempito con un importo specificato una volta al mese, l'ultimo giorno del mese.
È possibile osservare che tutti e tre questi tipi di conto hanno un'azione che prende le posizioni alla fine di ogni
mese. Tuttavia, ogni tipo di conto esegue diverse attività. Per implementare questo codice, è possibile utilizzare il
polimorfismo . Creare un singolo virtual metodo nella BankAccount classe:

public virtual void PerformMonthEndTransactions() { }

Il codice precedente Mostra come usare la virtual parola chiave per dichiarare un metodo nella classe di base
per cui una classe derivata può fornire un'implementazione diversa per. Un virtual metodo è un metodo in cui
qualsiasi classe derivata può scegliere di reimplementare. Le classi derivate usano la override parola chiave per
definire la nuova implementazione. In genere si fa riferimento a questo come "override dell'implementazione
della classe base". La virtual parola chiave specifica che le classi derivate possono eseguire l'override del
comportamento. È anche possibile dichiarare abstract metodi in cui le classi derivate devono eseguire
l'override del comportamento. La classe base non fornisce un'implementazione per un abstract metodo.
Successivamente, è necessario definire l'implementazione per due delle nuove classi create. Iniziare con
InterestEarningAccount :

public override void PerformMonthEndTransactions()


{
if (Balance > 500m)
{
var interest = Balance * 0.05m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}

Aggiungere il codice seguente a LineOfCreditAccount . Il codice nega il saldo per calcolare un importo di
interesse positivo che viene prelevato dall'account:

public override void PerformMonthEndTransactions()


{
if (Balance < 0)
{
// Negate the balance to get a positive interest charge:
var interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest");
}
}

La GiftCardAccount classe richiede due modifiche per implementare la funzionalità di fine mese. Per prima cosa,
modificare il costruttore in modo da includere un importo facoltativo da aggiungere ogni mese:

private decimal _monthlyDeposit = 0m;

public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name,


initialBalance)
=> _monthlyDeposit = monthlyDeposit;

Il costruttore fornisce un valore predefinito per il monthlyDeposit valore, in modo che i chiamanti possano
omettere un 0 per nessun deposito mensile. Eseguire quindi l'override del PerformMonthEndTransactions
metodo per aggiungere il deposito mensile, se è stato impostato su un valore diverso da zero nel costruttore:
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}

L'override applica il set di versamenti mensili nel costruttore. Aggiungere il codice seguente al Main metodo
per testare queste modifiche per GiftCardAccount e InterestEarningAccount :

var giftCard = new GiftCardAccount("gift card", 100, 50);


giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending money");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("savings account", 10000);


savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

Verificare i risultati. A questo punto, aggiungere un set di codice di test simile al seguente LineOfCreditAccount :

var lineOfCredit = new LineOfCreditAccount("line of credit", 0);


// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Quando si aggiunge il codice precedente ed Esegui il programma, verrà visualizzato un errore simile al
seguente:

Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must be positive (Parameter


'amount')
at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date, String note) in
BankAccount.cs:line 42
at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance) in BankAccount.cs:line 31
at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal initialBalance) in
LineOfCreditAccount.cs:line 9
at OOProgramming.Program.Main(String[] args) in Program.cs:line 29

NOTE
L'output effettivo include il percorso completo della cartella con il progetto. I nomi delle cartelle sono stati omessi per
brevità. Inoltre, a seconda del formato del codice, i numeri di riga possono essere leggermente diversi.

Questo codice ha esito negativo perché BankAccount presuppone che il saldo iniziale deve essere maggiore di 0.
Un altro presupposto cotto nella BankAccount classe è che il saldo non può andare negativo. Al contrario,
qualsiasi prelievo che si basa sull'account viene rifiutato. È necessario modificare entrambi i presupposti. La riga
dell'account di credito inizia da 0 e in genere avrà un saldo negativo. Inoltre, se un cliente prende in prestito
troppa quantità di denaro, viene addebitata una tariffa. La transazione viene accettata, ma solo costi aggiuntivi.
La prima regola può essere implementata aggiungendo un argomento facoltativo al BankAccount costruttore
che specifica il saldo minimo. Il valore predefinito è 0 . La seconda regola richiede un meccanismo che consente
alle classi derivate di modificare l'algoritmo predefinito. In un certo senso, la classe di base "chiede" il tipo
derivato che dovrebbe verificarsi quando è presente un overdraft. Il comportamento predefinito consiste nel
rifiutare la transazione generando un'eccezione.
Iniziamo aggiungendo un secondo costruttore che include un minimumBalance parametro facoltativo. Questo
nuovo costruttore esegue tutte le azioni eseguite dal costruttore esistente. Inoltre, imposta la proprietà di
bilanciamento minimo. È possibile copiare il corpo del costruttore esistente. Ciò significa che due posizioni
cambiano in futuro. In alternativa, è possibile usare il concatenamento di costruttori per chiamare un altro
costruttore. Il codice seguente illustra i due costruttori e il nuovo campo aggiuntivo:

private readonly decimal minimumBalance;

public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }

public BankAccount(string name, decimal initialBalance, decimal minimumBalance)


{
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;

this.Owner = name;
this.minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

Il codice precedente mostra due nuove tecniche. In primo luogo, il minimumBalance campo è contrassegnato
come readonly . Ciò significa che il valore non può essere modificato dopo la costruzione dell'oggetto. Una
volta BankAccount creato, l'oggetto minimumBalance non può essere modificato. In secondo luogo, il costruttore
che accetta due parametri USA : this(name, initialBalance, 0) { } come implementazione. L' : this()
espressione chiama l'altro costruttore, quello con tre parametri. Questa tecnica consente di avere un'unica
implementazione per inizializzare un oggetto anche se il codice client può scegliere uno dei molti costruttori.
Questa implementazione chiama MakeDeposit solo se il saldo iniziale è maggiore di 0 . In questo modo si
conserva la regola che i depositi devono essere positivi, ma consente di aprire l'account di credito con un 0
saldo.
Ora che la BankAccount classe dispone di un campo di sola lettura per il saldo minimo, la modifica finale
consiste nel modificare il codice 0 rigido minimumBalance in nel MakeWithdrawal Metodo:

if (Balance - amount < minimumBalance)

Dopo l'estensione della BankAccount classe, è possibile modificare il LineOfCreditAccount costruttore per
chiamare il nuovo costruttore di base, come illustrato nel codice seguente:

public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name,


initialBalance, -creditLimit)
{
}

Si noti che il LineOfCreditAccount Costruttore modifica il segno del creditLimit parametro in modo che
corrisponda al significato del minimumBalance parametro.
Regole di Overdraft diverse
L'ultima funzionalità da aggiungere consente LineOfCreditAccount a di addebitare una tariffa per il
superamento del limite di credito anziché rifiutare la transazione.
Una tecnica consiste nel definire una funzione virtuale in cui implementare il comportamento richiesto. La
BankAccount classe effettua il refactoring del MakeWithdrawal metodo in due metodi. Il nuovo metodo esegue
l'azione specificata quando il ritiro accetta il saldo al di sotto del valore minimo. Il MakeWithdrawal metodo
esistente presenta il codice seguente:

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
if (Balance - amount < minimumBalance)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
}

Sostituirlo con il seguente codice:

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
var overdraftTransaction = CheckWithdrawalLimit(Balance - amount < minimumBalance);
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
allTransactions.Add(overdraftTransaction);
}

protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)


{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
else
{
return default;
}
}

Il metodo aggiunto è protected , il che significa che può essere chiamato solo dalle classi derivate. Questa
dichiarazione impedisce ad altri client di chiamare il metodo. È anche in virtual modo che le classi derivate
possano modificare il comportamento. Il tipo restituito è un oggetto Transaction? . L' ? annotazione indica
che il metodo può restituire null . Aggiungere l'implementazione seguente in LineOfCreditAccount per
addebitare una tariffa quando viene superato il limite di prelievo:
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;

L'override restituisce una transazione tariffaria quando l'account è sottoposto a override. Se il ritiro non supera
il limite, il metodo restituisce una null transazione. Indica che non è previsto alcun costo. Testare queste
modifiche aggiungendo il codice seguente al Main metodo nella Program classe:

var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000);


// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Eseguire il programma e verificare i risultati.

Riepilogo
Se si è rimasti bloccati, è possibile visualizzare l'origine di questa esercitazione nel repository GitHub.
In questa esercitazione sono state illustrate molte delle tecniche utilizzate nella programmazione Object-
Oriented:
È stata usata l' astrazione quando sono stati conservati molti dettagli private in ogni classe.
È stato usato l' incapsulamento quando sono state definite classi per ognuno dei diversi tipi di conto. Tali
classi descrivono il comportamento per quel tipo di account.
L' ereditarietà è stata utilizzata quando si utilizza l'implementazione già creata nella BankAccount classe per
salvare il codice.
Il polimorfismo è stato usato quando sono stati creati virtual metodi che le classi derivate potrebbero
eseguire l'override per creare un comportamento specifico per il tipo di conto
Sono state completate tutte le esercitazioni di introduzione a C#. Per altre informazioni, provare altre
esercitazioni.
Creazione di tipi di record
28/01/2021 • 20 minutes to read • Edit Online

In C# 9 sono stati introdotti record, un nuovo tipo di riferimento che è possibile creare anziché classi o struct. I
record sono distinti dalle classi in cui i tipi di record utilizzano l' uguaglianza basata sul valore. Due variabili di
un tipo di record sono uguali se le definizioni dei tipi di record sono identiche e, se per ogni campo, i valori in
entrambi i record sono uguali. Due variabili di un tipo di classe sono uguali se gli oggetti a cui si fa riferimento
sono dello stesso tipo di classe e le variabili fanno riferimento allo stesso oggetto. L'uguaglianza basata su
valore implica altre funzionalità che probabilmente si desiderano nei tipi di record. Il compilatore genera molti di
questi membri quando si dichiara un record anziché un class .
In questa esercitazione si apprenderà come:
Decidere se dichiarare un oggetto class o un oggetto record .
Dichiarare i tipi di record e i tipi di record posizionali.
Sostituire i metodi generati dal compilatore nei record.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET 5 o versione successiva, incluso il compilatore C#
9,0 o versione successiva. Il compilatore C# 9,0 è disponibile a partire da Visual Studio 2019 versione 16,8 o
.NET 5,0 SDK.

Caratteristiche dei record


Per definire un record , dichiarare un tipo con la record parola chiave, anziché la class struct parola chiave
o. Un record è un tipo di riferimento e segue la semantica di uguaglianza basata sul valore. Per applicare la
semantica dei valori, il compilatore genera diversi metodi per il tipo di record:
Override di Object.Equals(Object) .
Metodo virtuale Equals il cui parametro è il tipo di record.
Override di Object.GetHashCode() .
Metodi per operator == e operator != .
I tipi di record implementano System.IEquatable<T> .
Inoltre, i record forniscono un override di Object.ToString() . Il compilatore sintetizza i metodi per la
visualizzazione dei record con Object.ToString() . Verranno esaminati i membri durante la scrittura del codice per
questa esercitazione. Registra with le espressioni di supporto per abilitare la mutazione non distruttiva di
record.
È anche possibile dichiarare record posizionali usando una sintassi più concisa. Il compilatore sintetizza più
metodi quando si dichiarano i record posizionali:
Un costruttore primario i cui parametri corrispondono ai parametri posizionali della dichiarazione di record.
Proprietà pubbliche di sola inizializzazione per ogni parametro di un costruttore primario.
Deconstruct Metodo per estrarre le proprietà dal record.

Dati della temperatura di compilazione


I dati e le statistiche sono tra gli scenari in cui è consigliabile utilizzare i record. Per questa esercitazione verrà
compilata un'applicazione che calcola i giorni di laurea per diversi usi. I giorni di laurea sono una misura del
calore (o mancanza di calore) in un periodo di giorni, settimane o mesi. In grado di tenere traccia e stimare
l'utilizzo dell'energia. Un maggior numero di giorni più caldi significa una maggiore climatizzazione e giorni più
freddi significano un uso più fornace. I giorni di laurea consentono di gestire i popolamenti vegetali. I giorni di
laurea sono correlati alla crescita della pianta mentre cambiano le stagioni. I giorni di laurea consentono di
tenere traccia delle migrazioni di animali per le specie che si spostano per individuare il clima
La formula è basata sulla temperatura media in un determinato giorno e una temperatura di base. Per calcolare i
giorni di laurea nel tempo, è necessaria la temperatura massima e bassa ogni giorno per un certo periodo di
tempo. Iniziamo creando una nuova applicazione. Creare una nuova applicazione console. Creare un nuovo tipo
di record in un nuovo file denominato "DailyTemperature.cs":

public record DailyTemperature(double HighTemp, double LowTemp);

Il codice precedente definisce un record posizionale. È stato creato un tipo di riferimento che contiene due
proprietà: HighTemp , e LowTemp . Tali proprietà sono proprietà solo init, ovvero possono essere impostate nel
costruttore o utilizzando un inizializzatore di proprietà. Il DailyTemperature tipo dispone anche di un costruttore
primario che dispone di due parametri corrispondenti alle due proprietà. Usare il costruttore primario per
inizializzare un DailyTemperature record:

private static DailyTemperature[] data = new DailyTemperature[]


{
new DailyTemperature(HighTemp: 57, LowTemp: 30),
new DailyTemperature(60, 35),
new DailyTemperature(63, 33),
new DailyTemperature(68, 29),
new DailyTemperature(72, 47),
new DailyTemperature(75, 55),
new DailyTemperature(77, 55),
new DailyTemperature(72, 58),
new DailyTemperature(70, 47),
new DailyTemperature(77, 59),
new DailyTemperature(85, 65),
new DailyTemperature(87, 65),
new DailyTemperature(85, 72),
new DailyTemperature(83, 68),
new DailyTemperature(77, 65),
new DailyTemperature(72, 58),
new DailyTemperature(77, 55),
new DailyTemperature(76, 53),
new DailyTemperature(80, 60),
new DailyTemperature(85, 66)
};

È possibile aggiungere proprietà o metodi personalizzati ai record, inclusi i record posizionali. È necessario
calcolare la temperatura media per ogni giorno. È possibile aggiungere tale proprietà al DailyTemperature
record:

public record DailyTemperature(double HighTemp, double LowTemp)


{
public double Mean => (HighTemp + LowTemp) / 2.0;
}

Verificare che sia possibile usare questi dati. Aggiungere il codice seguente al metodo Main :
foreach (var item in data)
Console.WriteLine(item);

Eseguire l'applicazione. verrà visualizzato un output simile al seguente (alcune righe rimosse per lo spazio):

DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }


DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }

DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }


DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }

Il codice precedente mostra l'output dell'override di ToString sintetizzato dal compilatore. Se si preferisce un
testo diverso, è possibile scrivere una versione personalizzata di ToString . Che impedisce al compilatore di
sintetizzare una versione.

Giorni di laurea di calcolo


Per calcolare i giorni di laurea, è necessario prendere la differenza rispetto a una temperatura di base e alla
temperatura media in un determinato giorno. Per misurare il calore nel tempo, si eliminano tutti i giorni in cui la
temperatura media è inferiore alla baseline. Per misurare il tempo di indisponibilità, eliminare tutti i giorni in cui
la temperatura media è superiore alla baseline. Ad esempio, USA 65F come base per i giorni di riscaldamento e
di raffreddamento. Questa è la temperatura in cui non è necessario riscaldamento o raffreddamento. Se un
giorno ha una temperatura media pari a 70F, il giorno corrisponde a 5 giorni di raffreddamento e 0 giorni di
riscaldamento. Viceversa, se la temperatura media è 55F, il giorno corrisponde a 10 giorni di riscaldamento e 0
giorni di raffreddamento.
È possibile esprimere queste formule come una piccola gerarchia di tipi di record: un tipo di giorno di laurea
astratto e due tipi concreti per i giorni di laurea e di raffreddamento. Questi tipi possono essere anche record
posizionali. Accettano una temperatura di base e una sequenza di record di temperatura giornalieri come
argomenti per il costruttore primario:

public abstract record DegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords);

public record HeatingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)


: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean < BaseTemperature).Sum(s => BaseTemperature -
s.Mean);
}

public sealed record CoolingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)


: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean > BaseTemperature).Sum(s => s.Mean -
BaseTemperature);
}

Il DegreeDays record astratto è la classe di base condivisa per i HeatingDegreeDays CoolingDegreeDays record e.
Le dichiarazioni del costruttore primario nei record derivati mostrano come gestire l'inizializzazione dei record
di base. Il record derivato dichiara i parametri per tutti i parametri nel costruttore primario del record di base. Il
record di base dichiara e inizializza tali proprietà. Il record derivato non li nasconde, ma crea e inizializza solo le
proprietà per i parametri che non sono dichiarati nel record di base. In questo esempio, i record derivati non
aggiungono nuovi parametri del costruttore primario. Testare il codice aggiungendo il codice seguente al Main
Metodo:
var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);

var coolingDegreeDays = new CoolingDegreeDays(65, data);


Console.WriteLine(coolingDegreeDays);

Verrà visualizzato un output simile al seguente:

HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }


CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }

Definire metodi con sintetizzazione del compilatore


Il codice calcola il numero corretto di giorni di riscaldamento e di raffreddamento per quel periodo di tempo.
Tuttavia, in questo esempio viene illustrato il motivo per cui è consigliabile sostituire alcuni dei metodi
sintetizzati per i record. È possibile dichiarare una versione personalizzata di uno dei metodi sintetizzati dal
compilatore in un tipo di record, ad eccezione del metodo Clone. Il metodo clone ha un nome generato dal
compilatore e non è possibile fornire un'implementazione diversa. Questi metodi sintetizzati includono un
costruttore di copia, i membri dell' System.IEquatable<T> interfaccia, i test di uguaglianza e di disuguaglianza, e
GetHashCode() . A tale scopo, verrà sintetizzato PrintMembers . È anche possibile dichiarare il proprio ToString ,
ma PrintMembers offre un'opzione migliore per gli scenari di ereditarietà. Per fornire la propria versione di un
metodo sintetizzato, la firma deve corrispondere al metodo sintetizzato.
L' TempRecords elemento nell'output della console non è utile. Viene visualizzato il tipo, ma nient'altro. È
possibile modificare questo comportamento fornendo una propria implementazione del metodo sintetizzato
PrintMembers . La firma dipende dai modificatori applicati alla record dichiarazione:

Se un tipo di record è sealed , la firma è private bool PrintMembers(StringBuilder builder);


Se un tipo di record non è sealed e deriva da object , ovvero non dichiara un record di base, la firma è
protected virtual bool PrintMembers(StringBuilder builder);
Se un tipo di record non è sealed e deriva da un altro record, la firma è
protected override bool PrintMembers(StringBuilder builder);

Queste regole sono più semplici da comprendere per comprendere lo scopo di PrintMembers . PrintMembers
aggiunge a una stringa informazioni su ogni proprietà in un tipo di record. Il contratto richiede record di base
per aggiungere i membri alla visualizzazione e presuppone che i membri derivati aggiungono i relativi membri.
Ogni tipo di record sintetizza una ToString sostituzione simile all'esempio seguente per HeatingDegreeDays :

public override string ToString()


{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("HeatingDegreeDays");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}

Si dichiara un PrintMembers metodo nel DegreeDays record che non stampa il tipo della raccolta:
protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
return true;
}

La firma dichiara un virtual protected metodo in modo che corrisponda alla versione del compilatore. Non
preoccuparsi se si ottengono le funzioni di accesso errate. il linguaggio impone la firma corretta. Se si
dimenticano i modificatori corretti per qualsiasi metodo sintetizzato, il compilatore genera avvisi o errori che
consentono di ottenere la firma corretta.

Mutazione non distruttiva


I membri sintetizzati in un record posizionale non modificano lo stato del record. L'obiettivo è la possibilità di
creare record non modificabili in modo più semplice. Esaminare di nuovo le dichiarazioni precedenti per
HeatingDegreeDays e CoolingDegreeDays . I membri aggiunti eseguono calcoli sui valori per il record, ma non
mutano lo stato. I record posizionali semplificano la creazione di tipi di riferimento non modificabili.
La creazione di tipi di riferimento non modificabili significa che è consigliabile usare una mutazione non
distruttiva. È possibile creare nuove istanze di record simili alle istanze di record esistenti tramite with
espressioni. Queste espressioni sono una costruzione di copia con assegnazioni aggiuntive che modificano la
copia. Il risultato è una nuova istanza di record in cui ogni proprietà è stata copiata dal record esistente e,
facoltativamente, modificata. Il record originale è invariato.
Aggiungere alcune funzionalità al programma che dimostrano le with espressioni. Per prima cosa, creiamo un
nuovo record per calcolare i giorni di grado crescente usando gli stessi dati. Il numero di giorni crescenti USA in
genere 41F come base e misura le temperature al di sopra della linea di base. Per usare gli stessi dati, è possibile
creare un nuovo record simile a coolingDegreeDays , ma con una temperatura di base diversa:

// Growing degree days measure warming to determine plant growing rates


var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);

È possibile confrontare il numero di gradi calcolato per i numeri generati con una temperatura di base
superiore. Tenere presente che i record sono tipi di riferimento e che queste copie sono copie superficiali. La
matrice per i dati non viene copiata, ma entrambi i record fanno riferimento agli stessi dati. Questo fatto
rappresenta un vantaggio in un altro scenario. Per i giorni di grado crescenti, è utile tenere traccia del totale per i
5 giorni precedenti. È possibile creare nuovi record con dati di origine diversi usando le with espressioni. Il
codice seguente compila una raccolta di questi accumuli, quindi Visualizza i valori:

// showing moving accumulation of 5 days using range syntax


List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..(start + rangeSize)] };
movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
Console.WriteLine(item);
}

È anche possibile usare with espressioni per creare copie di record. Non specificare alcuna proprietà tra le
parentesi graffe per l' with espressione. Ciò significa creare una copia e non modificare le proprietà:

var growingDegreeDaysCopy = growingDegreeDays with { };

Eseguire l'applicazione finita per visualizzare i risultati.

Riepilogo
Questa esercitazione ha mostrato diversi aspetti dei record. I record forniscono una sintassi concisa per i tipi di
riferimento in cui l'utilizzo fondamentale consiste nell'archiviare i dati. Per le classi orientate a oggetti, l'utilizzo
fondamentale è la definizione di responsabilità. Questa esercitazione è stata incentrata sui record posizionali, in
cui è possibile usare una sintassi concisa per dichiarare le proprietà solo init per un record. Il compilatore
sintetizza diversi membri del record per la copia e il confronto dei record. È possibile aggiungere altri membri
necessari per i tipi di record. È possibile creare tipi di record non modificabili sapendo che nessuno dei membri
generati dal compilatore avrebbe mutato lo stato. Per i record posizionali, le with espressioni semplificano il
supporto di mutazioni non distruttive.
I record aggiungono un altro modo per definire i tipi. class Le definizioni vengono utilizzate per creare
gerarchie orientate a oggetti che si concentrano sulle responsabilità e sul comportamento degli oggetti. Si
creano struct tipi per le strutture di dati che archiviano i dati e sono sufficientemente piccoli da poter essere
copiati in modo efficiente. I record vengono creati quando si desidera l'uguaglianza e il confronto basati su
valori, non si desidera copiare i valori e si desidera utilizzare variabili di riferimento.
È possibile apprendere la descrizione completa dei record leggendo la specifica del tipo di record proposto.
Esercitazione: esplorare le idee usando le istruzioni
di primo livello per compilare il codice durante
l'apprendimento
28/01/2021 • 12 minutes to read • Edit Online

In questa esercitazione si apprenderà come:


Informazioni sulle regole che regolano l'utilizzo di istruzioni di primo livello.
Usare le istruzioni di primo livello per esplorare gli algoritmi.
Effettuare il refactoring delle esplorazioni in componenti riutilizzabili.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET 5, che include il compilatore C# 9. Il compilatore
C# 9 è disponibile a partire da Visual Studio 2019 versione 16,9 Preview 1 o .NET 5,0 SDK.
Per questa esercitazione si presuppone che l'utente abbia familiarità con C# e .NET, inclusa l'interfaccia della riga
di comando di .NET Core o Visual Studio.

Inizia a esplorare
Le istruzioni di primo livello consentono di evitare la cerimonia aggiuntiva necessaria inserendo il punto di
ingresso del programma in un metodo statico in una classe. Il punto di partenza tipico per una nuova
applicazione console è simile al codice seguente:

using System;

namespace Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Il codice precedente è il risultato dell'esecuzione del dotnet new console comando e della creazione di una
nuova applicazione console. Le 11 righe contengono solo una riga di codice eseguibile. È possibile semplificare il
programma con la nuova funzionalità di istruzioni di primo livello. Che consente di rimuovere tutte le righe
tranne due delle righe del programma:

using System;

Console.WriteLine("Hello World!");

Questa azione semplifica gli elementi necessari per iniziare a esplorare nuove idee. È possibile utilizzare le
istruzioni di primo livello per gli scenari di scripting o per esplorare. Una volta ottenute le nozioni di base, è
possibile iniziare a eseguire il refactoring del codice e creare metodi, classi o altri assembly per i componenti
riutilizzabili compilati. Le istruzioni di primo livello consentono la sperimentazione rapida e le esercitazioni per
principianti. Forniscono anche un percorso uniforme dalla sperimentazione ai programmi completi.
Le istruzioni di primo livello vengono eseguite nell'ordine in cui appaiono nel file. Le istruzioni di primo livello
possono essere usate solo in un file di origine dell'applicazione. Il compilatore genera un errore se viene usato
in più di un file.

Creare una macchina virtuale di risposta di Magic .NET


Per questa esercitazione, verrà compilata un'applicazione console che risponde a una domanda "Sì" o "No" con
una risposta casuale. La funzionalità verrà compilata passo per passo. È possibile concentrarsi sull'attività invece
che sulla cerimonia necessaria per la struttura di un programma tipico. Quindi, quando si è soddisfatti della
funzionalità, è possibile effettuare il refactoring dell'applicazione nel modo appropriato.
Un punto di partenza valido consiste nel riportare la domanda alla console. È possibile iniziare scrivendo il
codice seguente:

using System;

Console.WriteLine(args);

Non viene dichiarata una args variabile. Per il singolo file di origine che contiene le istruzioni di primo livello, il
compilatore riconosce args di indicare gli argomenti della riga di comando. Il tipo di args è string[] , come in
tutti i programmi C#.
È possibile testare il codice eseguendo il comando seguente dotnet run :

dotnet run -- Should I use top level statements in all my programs?

Gli argomenti dopo l'oggetto -- nella riga di comando vengono passati al programma. È possibile visualizzare
il tipo della args variabile, perché viene stampato nella console:

System.String[]

Per scrivere la domanda nella console, è necessario enumerare gli argomenti e separarli con uno spazio.
Sostituire la WriteLine chiamata con il codice seguente:

Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();

A questo punto, quando si esegue il programma, la domanda viene visualizzata correttamente come una stringa
di argomenti.

Rispondere con una risposta casuale


Al termine della domanda, è possibile aggiungere il codice per generare la risposta casuale. Per iniziare,
aggiungere una matrice di risposte possibili:
string[] answers =
{
"It is certain.", "Reply hazy, try again.", "Don’t count on it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
};

Questa matrice ha 12 risposte affermativa, sei che non sono vincolanti e sei negativo. Aggiungere quindi il
codice seguente per generare e visualizzare una risposta casuale dalla matrice:

var index = new Random().Next(answers.Length - 1);


Console.WriteLine(answers[index]);

Per visualizzare i risultati, è possibile eseguire nuovamente l'applicazione. Verrà visualizzato un output simile al
seguente:

dotnet run -- Should I use top level statements in all my programs?

Should I use top level statements in all my programs?


Better not tell you now.

Questo codice risponde alle domande, ma aggiungiamo un'altra funzionalità. Si vuole che l'app per le domande
simula la risposta. A tale scopo, è possibile aggiungere un bit di animazione ASCII e sospendere il lavoro.
Aggiungere il codice seguente dopo la riga che restituisce la domanda:

for (int i = 0; i < 20; i++)


{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();

Sarà inoltre necessario aggiungere un' using istruzione all'inizio del file di origine:

using System.Threading.Tasks;

Le using istruzioni devono essere prima di qualsiasi altra istruzione nel file. In caso contrario, si tratta di un
errore del compilatore. È possibile eseguire di nuovo il programma e visualizzare l'animazione. Ciò garantisce
un'esperienza migliore. Sperimentare la lunghezza del ritardo in base al gusto.
Il codice precedente crea un set di linee rotanti separate da uno spazio. L'aggiunta della await parola chiave
indica al compilatore di generare il punto di ingresso del programma come metodo con il async modificatore e
restituisce un System.Threading.Tasks.Task . Questo programma non restituisce un valore, quindi il punto di
ingresso del programma restituisce Task . Se il programma restituisce un valore integer, è necessario
aggiungere un'istruzione return alla fine delle istruzioni di primo livello. Tale istruzione return specifica il valore
integer da restituire. Se le istruzioni di primo livello includono un' await espressione, il tipo restituito diventa
System.Threading.Tasks.Task<TResult> .

Refactoring per il futuro


Il programma dovrebbe essere simile al codice seguente:

using System;
using System.Threading.Tasks;

Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();

for (int i = 0; i < 20; i++)


{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();

string[] answers =
{
"It is certain.", "Reply hazy, try again.", "Don’t count on it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
};

var index = new Random().Next(answers.Length - 1);


Console.WriteLine(answers[index]);

Il codice precedente è ragionevole. Viene eseguito correttamente. Ma non è riutilizzabile. Ora che l'applicazione
è funzionante, è il momento di estrarre le parti riutilizzabili.
Un candidato è il codice che Visualizza l'animazione in attesa. Il frammento può diventare un metodo:
È possibile iniziare creando una funzione locale nel file. Sostituire l'animazione corrente con il codice seguente:
await ShowConsoleAnimation();

static async Task ShowConsoleAnimation()


{
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
}

Il codice precedente crea una funzione locale all'interno del metodo Main. Che non è ancora riutilizzabile.
Estrarre quindi il codice in una classe. Creare un nuovo file denominato Utilities.cs e aggiungere il codice
seguente:

using System;
using System.Threading.Tasks;

namespace MyNamespace
{
public static class Utilities
{
public static async Task ShowConsoleAnimation()
{
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
}
}
}

Le istruzioni di primo livello possono essere presenti solo in un file e il file non può contenere spazi dei nomi o
tipi.
Infine, è possibile pulire il codice di animazione per rimuovere alcune duplicazioni:
foreach (string s in new[] { "| -", "/ \\", "- |", "\\ /", })
{
Console.Write(s);
await Task.Delay(50);
Console.Write("\b\b\b");
}

A questo punto si dispone di un'applicazione completa ed è stato eseguito il refactoring delle parti riutilizzabili
per un uso successivo.

Riepilogo
Le istruzioni di livello superiore semplificano la creazione di semplici programmi da utilizzare per l'esplorazione
di nuovi algoritmi. È possibile sperimentare gli algoritmi provando diversi frammenti di codice. Una volta
appreso il funzionamento, è possibile effettuare il refactoring del codice in modo che sia più gestibile.
Le istruzioni di primo livello semplificano i programmi basati su applicazioni console. Tra cui funzioni di Azure,
azioni di GitHub e altre piccole utilità.
Usare i criteri di ricerca per compilare il
comportamento della classe per migliorare il codice
28/01/2021 • 17 minutes to read • Edit Online

Le funzionalità di criteri di ricerca in C# forniscono la sintassi per esprimere gli algoritmi. È possibile utilizzare
queste tecniche per implementare il comportamento nelle classi. È possibile combinare progettazione di classi
orientate a oggetti con un'implementazione orientata ai dati per fornire codice conciso durante la modellazione
di oggetti reali.
In questa esercitazione si apprenderà come:
Esprimere le classi orientate agli oggetti usando i modelli di dati.
Implementare tali modelli usando le funzionalità di criteri di ricerca di C#.
Utilizzare la diagnostica del compilatore per convalidare l'implementazione.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET 5, incluso il compilatore C# 9,0. Il compilatore C#
9,0 è disponibile a partire da Visual Studio 2019 versione 16,8 Preview o .NET 5,0 SDK Preview.

Creare una simulazione di un blocco del canale


In questa esercitazione verrà compilata una classe C# che simula un blocco del canale. In breve, un blocco del
canale è un dispositivo che aumenta e riduce le imbarcazioni mentre passano tra due estesi di acqua a livelli
diversi. Un blocco presenta due controlli e un meccanismo per modificare il livello di acqua.
Nella normale operazione, una barca entra in uno dei cancelli mentre il livello dell'acqua nel blocco corrisponde
al livello di acqua sul lato della barca. Una volta nel blocco, il livello di acqua viene modificato in modo che
corrisponda al livello di acqua in cui la barca lascerà il blocco. Quando il livello Water corrisponde a tale lato,
viene aperto il controllo sul lato di uscita. Le misure di sicurezza assicurano che un operatore non possa creare
una situazione pericolosa nel canale. Il livello di acqua può essere modificato solo quando entrambi i cancelli
sono chiusi. È possibile aprire al massimo un Gate. Per aprire un controllo, il livello di acqua nel blocco deve
corrispondere al livello di acqua esterno al gate aperto.
È possibile compilare una classe C# per modellare questo comportamento. Una CanalLock classe supporta i
comandi per aprire o chiudere uno o più controlli. Avrebbe altri comandi per aumentare o ridurre l'acqua. La
classe deve inoltre supportare le proprietà per leggere lo stato corrente delle attività di controllo e del livello di
acqua. I metodi implementano le misure di sicurezza.

Definire una classe


Verrà compilata un'applicazione console per testare la CanalLock classe. Creare un nuovo progetto console per
.NET 5 usando Visual Studio o l'interfaccia della riga di comando di .NET. Quindi, aggiungere una nuova classe e
denominarla CanalLock . Successivamente, progettare l'API pubblica, ma lasciare i metodi non implementati:
public enum WaterLevel
{
Low,
High
}
public class CanalLock
{
// Query canal lock state:
public WaterLevel CanalLockWaterLevel { get; private set; } = WaterLevel.Low;
public bool HighWaterGateOpen { get; private set; } = false;
public bool LowWaterGateOpen { get; private set; } = false;

// Change the upper gate.


public void SetHighGate(bool open)
{
throw new NotImplementedException();
}

// Change the lower gate.


public void SetLowGate(bool open)
{
throw new NotImplementedException();
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
throw new NotImplementedException();
}

public override string ToString() =>


$"The lower gate is {(LowWaterGateOpen ? "Open" : "Closed")}. " +
$"The upper gate is {(HighWaterGateOpen ? "Open" : "Closed")}. " +
$"The water level is {CanalLockWaterLevel}.";
}

Il codice precedente Inizializza l'oggetto in modo che entrambe le attività di controllo siano chiuse e che il livello
di acqua sia basso. Successivamente, scrivere il codice di test seguente nel Main metodo per guidare l'utente
durante la creazione di una prima implementazione della classe:
// Create a new canal lock:
var canalGate = new CanalLock();

// State should be doors closed, water level low:


Console.WriteLine(canalGate);

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");

Console.WriteLine("Boat enters lock from lower gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");

canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");
Console.WriteLine(canalGate);

canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate: {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");


Console.WriteLine("Boat enters lock from upper gate");

canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");

canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");

Aggiungere quindi una prima implementazione di ogni metodo nella CanalLock classe. Il codice seguente
implementa i metodi della classe senza alcuna preoccupazione per le regole di sicurezza. Verranno aggiunti i test
di sicurezza in un secondo momento:

// Change the upper gate.


public void SetHighGate(bool open)
{
HighWaterGateOpen = open;
}

// Change the lower gate.


public void SetLowGate(bool open)
{
LowWaterGateOpen = open;
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = newLevel;
}

Test scritti finora. Sono state implementate le nozioni di base. A questo punto, scrivere un test per la prima
condizione di errore. Alla fine dei test precedenti, entrambe le attività di controllo sono chiuse e il livello di acqua
è impostato su basso. Aggiungere un test per provare ad aprire il Gate superiore:
Console.WriteLine("=============================================");
Console.WriteLine(" Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
canalGate = new CanalLock();
canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation: Can't open the high gate. Water is low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");

Questo test ha esito negativo perché si apre il controllo. Come prima implementazione, è possibile correggerla
con il codice seguente:

// Change the upper gate.


public void SetHighGate(bool open)
{
if (open && (CanalLockWaterLevel == WaterLevel.High))
HighWaterGateOpen = true;
else if (open && (CanalLockWaterLevel == WaterLevel.Low))
throw new InvalidOperationException("Cannot open high gate when the water is low");
}

I test sono stati superati. Tuttavia, quando si aggiungono altri test, si aggiungono più if clausole e si testano
proprietà diverse. Questi metodi saranno presto troppo complicati quando si aggiungono più condizionali.

Implementare i comandi con i modelli


Un metodo migliore consiste nell'usare i modelli per determinare se l'oggetto è in uno stato valido per eseguire
un comando. È possibile esprimere se un comando è consentito come funzione di tre variabili: lo stato del
controllo, il livello di acqua e la nuova impostazione:

N UO VA IM P O STA Z IO N E STATO DEL GAT E L IVEL LO DI A C Q UA RISULTATO

Chiuso Chiuso Alta Chiuso

Chiuso Chiuso Basso Chiuso

Chiuso Apri Alta Apri

Chiuso Apri Bassa Chiuso

Apri Chiuso Alta Apri

Apri Chiuso Basso Chiuso (errore)

Apri Apri Alta Apri

Apri Apri Bassa Chiuso (errore)

Il quarto e l'ultima riga della tabella hanno un testo in sciopero perché non sono validi. Il codice che si sta
aggiungendo è necessario assicurarsi che il controllo High Water Gate non venga mai aperto quando l'acqua è
bassa. Questi Stati possono essere codificati come un'unica espressione switch (tenere presente che false
indica "closed"):

HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch


{
(false, false, WaterLevel.High) => false,
(false, false, WaterLevel.Low) => false,
(false, true, WaterLevel.High) => false,
(false, true, WaterLevel.Low) => false, // should never happen
(true, false, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the
water is low"),
(true, true, WaterLevel.High) => true,
(true, true, WaterLevel.Low) => false, // should never happen
};

Provare questa versione. I test sono stati superati, convalidando il codice. La tabella completa Mostra le possibili
combinazioni di input e risultati. Ciò significa che l'utente e altri sviluppatori possono esaminare rapidamente la
tabella e verificare che siano stati trattati tutti gli input possibili. Ancora più semplice, il compilatore può essere
utile. Dopo aver aggiunto il codice precedente, è possibile notare che il compilatore genera un avviso: CS8524
indica che l'espressione switch non copre tutti gli input possibili. Il motivo di tale avviso è che uno degli input è
un enum tipo. Il compilatore interpreta tutti gli input possibili come tutti gli input dal tipo sottostante, in genere
un int . Questa switch espressione controlla solo i valori dichiarati in enum . Per rimuovere l'avviso, è
possibile aggiungere un modello di scarto catch-all per l'ultimo ARM dell'espressione. Questa condizione
genera un'eccezione, perché indica un input non valido:

_ => throw new InvalidOperationException("Invalid internal state"),

Il cambio ARM precedente deve essere l'ultimo nell' switch espressione perché corrisponde a tutti gli input.
Provare a spostarlo prima nell'ordine. Ciò provoca un errore del compilatore CS8510 per il codice non
eseguibile in un modello. La struttura naturale delle espressioni switch consente al compilatore di generare
errori e avvisi per possibili errori. Il compilatore "safety net" semplifica la creazione di codice corretto in un
minor numero di iterazioni e la libertà di combinare le armi di cambio con i caratteri jolly. Il compilatore
emetterà errori se la combinazione produce bracci non raggiungibili non previsti e avvisi se si rimuove un ARM
necessario.
La prima modifica consiste nel combinare tutte le armi in cui il comando deve chiudere il controllo; Questo è
sempre consentito. Aggiungere il codice seguente come primo ARM nell'espressione switch:

(false, _, _) => false,

Dopo aver aggiunto il Commuter precedente, si otterranno quattro errori del compilatore, uno su ognuna delle
armi in cui il comando è false . Queste armi sono già coperte dal nuovo ARM aggiunto. È possibile rimuovere
le quattro righe in modo sicuro. Questo nuovo switch ARM è stato progettato per sostituire tali condizioni.
Successivamente, è possibile semplificare le quattro armi in cui il comando deve aprire il controllo. In entrambi i
casi, il livello di acqua è elevato, il Gate può essere aperto. (In uno, è già aperto). Un caso in cui il livello di acqua
è basso genera un'eccezione e l'altro non dovrebbe verificarsi. Se il blocco dell'acqua è già in uno stato non
valido, deve essere sicuro generare la stessa eccezione. È possibile apportare le semplificazioni seguenti per le
armi:

(true, _, WaterLevel.High) => true,


(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water
is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
Eseguire di nuovo i test e passarli. Di seguito è illustrata la versione finale del SetHighGate Metodo:

// Change the upper gate.


public void SetHighGate(bool open)
{
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when
the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}

Implementare i modelli manualmente


Ora che è stata esaminata la tecnica, compilare SetLowGate manualmente i SetWaterLevel metodi e. Per iniziare,
aggiungere il codice seguente per testare le operazioni non valide su questi metodi:

Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetLowGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't open the lower gate. Water is high.");
}
Console.WriteLine($"Try to open lower gate: {canalGate}");
// change water level with gate open (2 tests)
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetLowGate(open: true);
canalGate.SetWaterLevel(WaterLevel.High);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't raise water when the lower gate is open.");
}
Console.WriteLine($"Try to raise water with lower gate open: {canalGate}");
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetWaterLevel(WaterLevel.Low);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't lower water when the high gate is open.");
}
Console.WriteLine($"Try to lower water with high gate open: {canalGate}");

Eseguire di nuovo l'applicazione. È possibile vedere che i nuovi test hanno esito negativo e il blocco del canale
entra in uno stato non valido. Provare a implementare manualmente i metodi rimanenti. Il metodo per
impostare il Gate inferiore dovrebbe essere simile al metodo per impostare il controllo superiore. Il metodo che
modifica il livello di acqua ha controlli diversi, ma deve seguire una struttura simile. Potrebbe risultare utile
usare lo stesso processo per il metodo che imposta il livello di acqua. Iniziare con tutti e quattro gli input: lo
stato di entrambi i controlli, lo stato corrente del livello di acqua e il nuovo livello di acqua richiesto.
L'espressione switch deve iniziare con:

CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch


{
// elided
};

Sono disponibili 16 bracci di cambio totali per il riempimento. Quindi, test e semplificazione.
È stato fatto un metodo simile al seguente?

// Change the lower gate.


public void SetLowGate(bool open)
{
LowWaterGateOpen = (open, LowWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.Low) => true,
(true, false, WaterLevel.High) => throw new InvalidOperationException("Cannot open high gate when
the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch
{
(WaterLevel.Low, WaterLevel.Low, true, false) => WaterLevel.Low,
(WaterLevel.High, WaterLevel.High, false, true) => WaterLevel.High,
(WaterLevel.Low, _, false, false) => WaterLevel.Low,
(WaterLevel.High, _, false, false) => WaterLevel.High,
(WaterLevel.Low, WaterLevel.High, false, true) => throw new InvalidOperationException("Cannot lower
water when the high gate is open"),
(WaterLevel.High, WaterLevel.Low, true, false) => throw new InvalidOperationException("Cannot raise
water when the low gate is open"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}

I test devono essere superati e il blocco del canale dovrebbe funzionare in modo sicuro.

Riepilogo
In questa esercitazione si è appreso come usare i criteri di ricerca per controllare lo stato interno di un oggetto
prima di applicare le eventuali modifiche apportate a tale stato. È possibile controllare le combinazioni di
proprietà. Una volta create le tabelle per una di queste transizioni, si testa il codice, quindi si semplifica la
leggibilità e la gestibilità. Questi refactoring iniziali possono suggerire ulteriori refactoring che convalidano lo
stato interno o gestiscono altre modifiche dell'API. Questa esercitazione combina classi e oggetti con un
approccio basato su modelli più orientato ai dati per implementare tali classi.
Usare l'interpolazione di stringhe per la costruzione
di stringhe formattate
06/05/2020 • 14 minutes to read • Edit Online

Questa esercitazione descrive come usare l'interpolazione di stringhe in C# per inserire i valori in un'unica
stringa di risultato. Verranno descritte le procedure per scrivere codice C# e visualizzare i risultati della
compilazione ed esecuzione del codice. L'esercitazione contiene una serie di lezioni che descrivono come
inserire valori in una stringa e formattare questi valori in modi diversi.
Questa esercitazione prevede la presenza di un computer da usare per lo sviluppo. L'esercitazione .NET Hello
World in 10 minuti contiene le istruzioni per configurare l'ambiente di sviluppo locale in Windows, Linux o
MacOS. È anche possibile completare la versione interattiva di questa esercitazione nel browser.

Creare una stringa interpolata


Creare una directory denominata interpolated. Impostarla come directory corrente ed eseguire il comando
seguente da una finestra della console:

dotnet new console

Questo comando crea una nuova applicazione console .NET Core nella directory corrente.
Aprire Program.cs nell'editor preferito e sostituire la riga Console.WriteLine("Hello World!"); con il codice
seguente, sostituendo <name> con il nome:

var name = "<name>";


Console.WriteLine($"Hello, {name}. It's a pleasure to meet you!");

Per provare questo codice, digitare dotnet run nella finestra della console. Quando si esegue il programma
viene visualizzata una singola stringa con una formula di benvenuto che include il nome dell'utente. La stringa
inclusa nella chiamata del metodo WriteLine è un'espressione di stringa interpolata. Si tratta di un tipo di
modello che consente di costruire un'unica stringa detta stringa di risultato da una stringa che include codice
incorporato. Le stringhe interpolate sono particolarmente utili per inserire valori in una stringa o per
concatenare (unire) più stringhe.
Questo semplice esempio contiene i due elementi che devono essere presenti in ogni stringa interpolata:
Un valore letterale stringa che inizia con il carattere $ prima delle virgolette inglesi aperte. Tra il simbolo
$ e le virgolette non devono essere presenti spazi. Se si vuole vedere cosa accade se ne viene incluso
uno, inserire uno spazio dopo il carattere $ , salvare il file ed eseguire nuovamente il programma
digitando dotnet run nella finestra della console. Il compilatore C# visualizza un messaggio di errore
"errore CS1056: Carattere '$'" imprevisto).
Una o più espressioni di interpolazione. Un'espressione di interpolazione è indicata da parentesi graffe di
apertura e chiusura ( { e } ). All'interno delle parentesi graffe è possibile inserire qualsiasi espressione
C# che restituisce un valore, incluso null .
Ora si proveranno altri esempi di interpolazione di stringhe con altri tipi di dati.
Includere tipi di dati diversi
Nella sezione precedente è stata usata l'interpolazione di stringhe per inserire una stringa in un'altra. Il risultato
di un'espressione di interpolazione può avere tuttavia qualsiasi tipo di dati. Includiamo valori di vari tipi di dati in
una stringa interpolata.
Nell'esempio seguente viene definito prima di tutto un tipo di dati class Vegetable con la Name proprietà e un
ToString metodo che esegue l'override del comportamento del metodo Object.ToString(). Il public
modificatore di accesso rende il metodo disponibile a qualsiasi codice client per ottenere la rappresentazione di
Vegetable stringa di un'istanza. Nell' Vegetable.ToString esempio il metodo restituisce il valore Name della
proprietà inizializzata Vegetable nel Costruttore:

public Vegetable(string name) => Name = name;

Viene quindi creata Vegetable un'istanza della classe denominata item usando l' new operatore e
specificando un nome per il costruttore: Vegetable

var item = new Vegetable("eggplant");

Infine, includere la variabile item in una stringa interpolata che contenga anche un valore DateTime, un valore
Decimal e un valore di enumerazione Unit . Sostituire tutto il codice C# in un editor con il codice seguente e
usare il comando dotnet run per l'eseguirlo:

using System;

public class Vegetable


{
public Vegetable(string name) => Name = name;

public string Name { get; }

public override string ToString() => Name;


}

public class Program


{
public enum Unit { item, kilogram, gram, dozen };

public static void Main()


{
var item = new Vegetable("eggplant");
var date = DateTime.Now;
var price = 1.99m;
var unit = Unit.item;
Console.WriteLine($"On {date}, the price of {item} was {price} per {unit}.");
}
}

L'espressione di interpolazione item nella stringa interpolata restituisce il testo "eggplant" nella stringa del
risultato. Questo accade perché, quando il tipo di risultato dell'espressione non è una stringa, il risultato
restituisce una stringa nel modo seguente:
Se l'espressione di interpolazione restituisce null , viene usata una stringa vuota ("" o String.Empty).
Se l'espressione di interpolazione non restituisce null , in genere viene chiamato il metodo ToString del
tipo di risultato. È possibile testare questo processo aggiornando l'implementazione del metodo
Vegetable.ToString . Potrebbe anche non essere necessario implementare il metodo ToString , poiché
ogni tipo prevede un'implementazione di questo metodo. Per il test di questo funzionamento, impostare
come commento la definizione del metodo Vegetable.ToString nell'esempio (facendola precedere dal
simbolo di commento // ). Nell'output la stringa "eggplant" viene sostituita dal nome completo del tipo
("Vegetable" in questo esempio), ovvero il comportamento predefinito del metodo Object.ToString(). Il
comportamento predefinito del metodo ToString per un tipo di enumerazione prevede la restituzione
della rappresentazione stringa di un valore usato nella definizione dell'enumerazione.
Nell'output di questo esempio la data è troppo precisa (il prezzo delle melanzane non varia ogni secondo) e il
valore del prezzo non indica una valuta. Nella sezione successiva si apprenderà come risolvere questi problemi
controllando la formattazione delle rappresentazioni di stringa dei risultati delle espressioni.

Controllare la formattazione delle espressioni di interpolazione


Nella sezione precedente sono state inserite nella stringa di risultato due stringhe con formattazione non valida.
Uno è un valore di data e ora nel quale solo la data risultava corretta. Il secondo è un prezzo che non indica la
valuta. Entrambi i problemi sono di facile risoluzione. L'interpolazione di stringhe consente di specificare
stringhe di formato che controllano la formattazione di determinati tipi. Modificare la chiamata in
Console.WriteLine dell'esempio precedente in modo da includere le stringhe di formato per le espressioni di
data e prezzo, come indicato nella riga seguente:

Console.WriteLine($"On {date:d}, the price of {item} was {price:C2} per {unit}.");

Per specificare una stringa di formato, aggiungere i due punti (":") e la stringa di formato desiderata dopo
l'espressione di interpolazione. "d" è un stringa di formato data e ora standard che rappresenta il formato di
data breve. "C2" è una stringa di formato numerico standard che rappresenta un numero come valore di valuta
con due cifre dopo il separatore decimale.
Molti tipi delle librerie .NET supportano un set predefinito di stringhe di formato. Tali tipi includono tutti i tipi
numerici e i tipi data e ora. Per l'elenco completo dei tipi che supportano le stringhe di formato, vedere Stringhe
di formato e tipi della libreria di classe .NET nell'articolo Formattazione di tipi in .NET.
Provare a modificare le stringhe di formato nell'editor di testo ed eseguire nuovamente il programma ogni volta
che si apporta una modifica, per verificare l'effetto delle modifiche sulla formattazione di data, ora e valore
numerico. Modificare il valore "d" in {date:d} inserendo i valori "t" (per visualizzare il formato ora breve), "y"
(per visualizzare anno e mese) e "yyyy" (per visualizzare l'anno come numero a quattro cifre). Modificare "C2" in
{price:C2} inserendo "e" (per la notazione esponenziale) e "F3" (per un valore numerico con tre cifre dopo il
separatore decimale).
Oltre alla formattazione è possibile controllare la larghezza del campo e l'allineamento delle stringhe formattate
incluse nella stringa di risultato. La sezione successiva spiega come eseguire questa operazione.

Controllare la larghezza del campo e l'allineamento delle espressioni


di interpolazione
In genere quando il risultato di un'espressione di interpolazione è formattato in stringa, questa viene inclusa in
una stringa del risultato senza spazi iniziali o finali. In particolare quando si usa un set di dati, la possibilità di
controllare la larghezza di un campo e l'allineamento del testo consente di generare un output più leggibile. Per
verificare, sostituire tutto il codice nell'editor di testo con il codice seguente e digitare dotnet run per eseguire il
programma:
using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
var titles = new Dictionary<string, string>()
{
["Doyle, Arthur Conan"] = "Hound of the Baskervilles, The",
["London, Jack"] = "Call of the Wild, The",
["Shakespeare, William"] = "Tempest, The"
};

Console.WriteLine("Author and Title List");


Console.WriteLine();
Console.WriteLine($"|{"Author",-25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,-25}|{title.Value,30}|");
}
}

I nomi degli autori vengono allineati a sinistra e i titoli da loro scritti sono allineati a destra. Per specificare
l'allineamento, aggiungere una virgola (",") dopo un'espressione di interpolazione e indicare la larghezza
minima del campo. Se il valore specificato è un numero positivo, il campo è allineato a destra. Se è un numero
negativo, il campo è allineato a sinistra.
Provare a rimuovere il segno negativo dal codice {"Author",-25} e {title.Key,-25} ed eseguire di nuovo
l'esempio, come indicato dal codice seguente:

Console.WriteLine($"|{"Author",25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");

Questa volta le informazioni sull'autore sono allineate a destra.


È possibile combinare un identificatore di allineamento e una stringa di formato per un'unica espressione di
interpolazione. A tale scopo, specificare prima l'allineamento, seguito da due punti e dalla stringa di formato.
Sostituire tutto il codice nel metodo Main con il codice seguente, che consente di visualizzare tre stringhe
formattate con le larghezze di campo definite. A questo punto eseguire il programma immettendo il comando
dotnet run .

Console.WriteLine($"[{DateTime.Now,-20:d}] Hour [{DateTime.Now,-10:HH}] [{1063.342,15:N2}] feet");

L'output è simile al seguente:

[04/14/2018 ] Hour [16 ] [ 1,063.34] feet

È stata completata l'esercitazione sull'interpolazione di stringhe.


Per altre informazioni, vedere l'argomento Interpolazione di stringhe e l'esercitazione Interpolazione di stringhe
in C#.
Interpolazione di stringhe in C#
18/03/2020 • 10 minutes to read • Edit Online

Questa esercitazione illustra come usare l'interpolazione di stringhe per formattare e includere risultati di
espressione in una stringa di risultato. Gli esempi presuppongono una buona familiarità con i concetti di base
del linguaggio C# e con la formattazione dei tipi in .NET. Se non si ha familiarità con l'interpolazione di stringhe
o con la formattazione dei tipi in .NET, vedere prima l'esercitazione interattiva sull'interpolazione di stringhe. Per
altre informazioni sulla formattazione dei tipi in .NET, vedere l'argomento Formattazione di tipi in .NET.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

Introduzione
La funzionalità di interpolazione di stringhe, basata sulla funzionalità di formattazione composita, offre una
sintassi più leggibile e pratica per includere risultati di espressione formattati in una stringa di risultato.
Per identificare un valore letterale stringa come stringa interpolata, anteporre a questa il simbolo $ . È possibile
incorporare in una stringa interpolata qualsiasi espressione C# valida che restituisca un valore. Nell'esempio
seguente, non appena un'espressione viene valutata, il suo risultato viene convertito in una stringa e incluso in
una stringa di risultato:

double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}");
Console.WriteLine($"Length of the hypotenuse of the right triangle with legs of {a} and {b} is
{CalculateHypotenuse(a, b)}");

double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);

// Expected output:
// Area of the right triangle with legs of 3 and 4 is 6
// Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5

Come illustrato nell'esempio, per includere un'espressione in una stringa interpolata è necessario racchiuderla
tra parentesi graffe:

{<interpolationExpression>}

Le stringhe interpolate supportano tutte le caratteristiche della funzionalità di formattazione composita delle
stringhe, che li rende un'alternativa più leggibile all'uso del metodo String.Format.

Come specificare una stringa di formato per un'espressione di


interpolazione
Per specificare una stringa di formato supportata dal tipo del risultato dell'espressione, far seguire
all'espressione di interpolazione i due punti (":") e la stringa di formato da usare:

{<interpolationExpression>:<formatString>}

L'esempio seguente illustra come specificare stringhe di formato standard e personalizzate per espressioni che
producono risultati di tipo data e ora o numerici:

var date = new DateTime(1731, 11, 25);


Console.WriteLine($"On {date:dddd, MMMM dd, yyyy} Leonhard Euler introduced the letter e to denote
{Math.E:F5} in a letter to Christian Goldbach.");

// Expected output:
// On Sunday, November 25, 1731 Leonhard Euler introduced the letter e to denote 2.71828 in a letter to
Christian Goldbach.

Per altre informazioni, vedere la sezione Componente della stringa di formato dell'argomento Formattazione
composita. Questa sezione mette a disposizione i collegamenti agli argomenti che descrivono le stringhe di
formato standard e personalizzate supportate dai tipi di base .NET.

Come controllare la larghezza del campo e l'allineamento delle


espressioni di interpolazione formattate
È possibile specificare la larghezza minima del campo e l'allineamento del risultato dell'espressione formattata
facendo seguire l'espressione di interpolazione da una virgola (",") e dall'espressione costante:

{<interpolationExpression>,<alignment>}

Se il valore alignment è positivo, il risultato dell'espressione formattata è allineato a destra, mentre se è negativo
è allineato a sinistra.
Se è necessario specificare sia l'allineamento che una stringa di formato, iniziare con l'allineamento:

{<interpolationExpression>,<alignment>:<formatString>}

L'esempio seguente illustra come specificare l'allineamento e usa caratteri barra verticale ("|") per delimitare i
campi di testo:

const int NameAlignment = -9;


const int ValueAlignment = 7;

double a = 3;
double b = 4;
Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:");
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|");

// Expected output:
// Three classical Pythagorean means of 3 and 4:
// |Arithmetic| 3.500|
// |Geometric| 3.464|
// |Harmonic | 3.429|

Come illustrato nell'esempio di output, se la lunghezza del risultato dell'espressione formattata supera la
larghezza del campo specificata, il valore alignment viene ignorato.
Per altre informazioni, vedere la sezione Componente di allineamento dell'argomento Formattazione composita.

Come usare sequenze di escape in una stringa interpolata


Le stringhe interpolate supportano tutte le sequenze di escape che è possibile usare all'interno di valori letterali
stringa normali. Per altre informazioni, vedere Sequenze di escape delle stringhe.
Per interpretare le sequenze di escape letteralmente, usare un valore letterale stringa verbatim. Una stringa
verbatim interpolata $ inizia con @ il carattere seguito dal carattere. A partire dalla versione 8.0 $ di @ C, è
possibile $@"..." @$"..." usare i token e in qualsiasi ordine: entrambi e sono stringhe letterali interpolate
valide.
Per includere una parentesi graffa, "{" o "}", in una stringa di risultato, usare due parentesi graffe, "{{" o "}}". Per
altre informazioni, vedere la sezione Sequenze di escape delle parentesi graffe dell'argomento Formattazione
composita.
L'esempio seguente illustra come includere parentesi graffe in una stringa di risultato e costruire una stringa
interpolata verbatim:

var xs = new int[] { 1, 2, 7, 9 };


var ys = new int[] { 7, 9, 12 };
Console.WriteLine($"Find the intersection of the {{{string.Join(", ",xs)}}} and {{{string.Join(", ",ys)}}}
sets.");

var userName = "Jane";


var stringWithEscapes = $"C:\\Users\\{userName}\\Documents";
var verbatimInterpolated = $@"C:\Users\{userName}\Documents";
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);

// Expected output:
// Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets.
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents

Come usare un operatore condizionale ternario ?: in un'espressione


di interpolazione
Dato che i due punti (":") hanno un significato speciale in un elemento con un'espressione di interpolazione, per
usare un operatore condizionale in un'espressione, racchiudere l'espressione tra parentesi, come illustrato
dall'esempio seguente:

var rand = new Random();


for (int i = 0; i < 7; i++)
{
Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" : "tails")}");
}

Come creare una stringa di risultato specifica delle impostazioni


cultura con interpolazione
Per impostazione predefinita, una stringa interpolata usa le impostazioni cultura correnti definite dalla proprietà
CultureInfo.CurrentCulture per tutte le operazioni di formattazione. Usare la conversione implicita di una stringa
interpolata in un'istanza di System.FormattableString e chiamare il relativo metodo ToString(IFormatProvider)
per creare una stringa di risultato specifica delle impostazioni cultura. L'esempio seguente illustra come
eseguire questa operazione:

var cultures = new System.Globalization.CultureInfo[]


{
System.Globalization.CultureInfo.GetCultureInfo("en-US"),
System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
System.Globalization.CultureInfo.InvariantCulture
};

var date = DateTime.Now;


var number = 31_415_926.536;
FormattableString message = $"{date,20}{number,20:N3}";
foreach (var culture in cultures)
{
var cultureSpecificMessage = message.ToString(culture);
Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}

// Expected output is like:


// en-US 5/17/18 3:44:55 PM 31,415,926.536
// en-GB 17/05/2018 15:44:55 31,415,926.536
// nl-NL 17-05-18 15:44:55 31.415.926,536
// 05/17/2018 15:44:55 31,415,926.536

Come illustrato nell'esempio, è possibile usare un'istanza di FormattableString per generare più stringhe di
risultato per diverse impostazioni cultura.

Come creare una stringa di risultato usando le impostazioni cultura


inglese non dipendenti da paese/area geografica
Insieme al metodo FormattableString.ToString(IFormatProvider) è possibile usare il metodo statico
FormattableString.Invariant per risolvere una stringa interpolata in una stringa di risultato per InvariantCulture.
L'esempio seguente illustra come eseguire questa operazione:

string messageInInvariantCulture = FormattableString.Invariant($"Date and time in invariant culture:


{DateTime.Now}");
Console.WriteLine(messageInInvariantCulture);

// Expected output is like:


// Date and time in invariant culture: 05/17/2018 15:46:24

Conclusioni
Questa esercitazione descrive scenari comuni di utilizzo dell'interpolazione di stringhe. Per altre informazioni
sull'interpolazione di stringhe, vedere l'argomento Interpolazione di stringhe. Per altre informazioni sulla
formattazione dei tipi in .NET, vedere gli argomenti Formattazione di tipi in .NET e Formattazione composita.

Vedere anche
String.Format
System.FormattableString
System.IFormattable
Stringhe
Esercitazione: aggiornare le interfacce con i metodi
di interfaccia predefiniti in C# 8,0
02/11/2020 • 11 minutes to read • Edit Online

A partire da C# 8.0 su .NET Core 3.0 è possibile definire un'implementazione quando si dichiara un membro di
un'interfaccia. Lo scenario più comune consiste nell'aggiunta sicura di membri a un'interfaccia già rilasciata e
usata da innumerevoli client.
In questa esercitazione si apprenderà come:
Estendere le interfacce in modo sicuro aggiungendo metodi con implementazioni.
Creare implementazioni con parametri per una maggiore flessibilità.
Abilitare gli implementatori per fornire un'implementazione più specifica sotto forma di override.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET Core, incluso il compilatore C# 8,0. Il compilatore
C# 8,0 è disponibile a partire da Visual Studio 2019 versione 16,3 o .NET Core 3,0 SDK.

Panoramica dello scenario


Questa esercitazione inizia con la versione 1 di una raccolta di relazioni con i clienti. L'applicazione di base è
disponibile nel repository di esempi in GitHub. L'azienda che ha realizzato questa raccolta intende farla adottare
dai clienti con le applicazioni esistenti. Agli utenti della raccolta vengono fornite definizioni di interfaccia minime
da implementare. Ecco la definizione di interfaccia per un cliente:

public interface ICustomer


{
IEnumerable<IOrder> PreviousOrders { get; }

DateTime DateJoined { get; }


DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}

Viene definita una seconda interfaccia che rappresenta un ordine:

public interface IOrder


{
DateTime Purchased { get; }
decimal Cost { get; }
}

Da queste interfacce il team potrebbe realizzare una raccolta per consentire agli utenti di creare un'esperienza
migliore per i clienti. L'obiettivo è consolidare le relazioni con i clienti esistenti e migliorare quelle con i nuovi
clienti.
Ora è il momento di aggiornare la raccolta per la versione successiva. Una delle funzionalità richieste è la
definizione di uno sconto fedeltà per i clienti con molti ordini. Questo nuovo sconto fedeltà viene applicato ogni
volta che un cliente effettua un ordine. Lo sconto specifico è una proprietà di ogni singolo cliente. Ogni
implementazione di ICustomer può impostare regole diverse per lo sconto fedeltà.
Il modo più naturale per aggiungere questa funzionalità consiste nell'ottimizzare l'interfaccia ICustomer con un
metodo per applicare qualsiasi sconto fedeltà. Questo suggerimento di progettazione ha causato problemi tra
gli sviluppatori esperti: "le interfacce non sono modificabili dopo il rilascio. a meno di generare errori. In C# 8.0
sono state aggiunte implementazioni di interfaccia predefinite per l'aggiornamento delle interfacce. Gli autori
della raccolta possono aggiungere nuovi membri all'interfaccia a cui applicare un'implementazione predefinita.
Le implementazioni di interfaccia predefinite consentono agli sviluppatori di aggiornare un'interfaccia, ma
possono comunque essere sottoposte a override da qualsiasi implementatore. Gli utenti della raccolta possono
accettare l'implementazione predefinita come modifica che non causa interruzioni. Se le regole business sono
diverse, possono eseguire l'override.

Aggiornare con i metodi di interfaccia predefiniti


Il team concorda sull'implementazione predefinita più probabile: uno sconto fedeltà per i clienti.
L'aggiornamento dovrà fornire la funzionalità per impostare due proprietà: il numero di ordini necessario per
avere diritto allo sconto e la percentuale dello sconto. Questo lo rende uno scenario perfetto per i metodi di
interfaccia predefiniti. È possibile aggiungere un metodo all' ICustomer interfaccia e fornire l'implementazione
più probabile. Tutte le implementazioni esistenti e quelle nuove possono usare l'implementazione predefinita o
una personalizzata.
Aggiungere prima di tutto il nuovo metodo all'interfaccia, incluso il corpo del metodo:

// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}

L'autore della raccolta scrive un primo test per verificare l'implementazione:

SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))


{
Reminders =
{
{ new DateTime(2010, 08, 12), "childs's birthday" },
{ new DateTime(1012, 11, 15), "anniversary" }
}
};

SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);


c.AddOrder(o);

o = new SampleOrder(new DateTime(2103, 7, 4), 25m);


c.AddOrder(o);

// Check the discount:


ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Si noti la parte seguente del test:


// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Il cast da SampleCustomer a ICustomer è necessario. La classe SampleCustomer non deve necessariamente fornire
un'implementazione per ComputeLoyaltyDiscount , perché viene fornita dall'interfaccia ICustomer . Tuttavia, la
classe SampleCustomer non eredita i membri dalle relative interfacce. Questa regola non è cambiata. Per
chiamare qualsiasi metodo dichiarato e implementato nell'interfaccia, la variabile deve essere il tipo
dell'interfaccia, in questo esempio ICustomer .

Fornire la parametrizzazione
Anche se si tratta di un buon inizio, l'implementazione predefinita è troppo restrittiva. Molti utenti di questo
sistema potrebbero scegliere soglie diverse per il numero di acquisti, una durata diversa dell'iscrizione o una
percentuale di sconto diversa. È possibile fornire un'esperienza di aggiornamento migliore per più clienti
offrendo la possibilità di impostare questi parametri. A questo scopo, aggiungere un metodo statico che imposta
questi tre parametri che controllano l'implementazione predefinita:

// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;

public decimal ComputeLoyaltyDiscount()


{
DateTime start = DateTime.Now - length;

if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))


{
return discountPercent;
}
return 0;
}

Sono disponibili molte nuove funzionalità del linguaggio in questo piccolo frammento di codice. Le interfacce
possono ora includere membri statici, tra cui campi e metodi. Sono inoltre abilitati diversi modificatori di
accesso. I campi aggiuntivi sono privati, mentre il nuovo metodo è pubblico. Per i membri dell'interfaccia è
consentito qualsiasi modificatore.
Per le applicazioni in cui si usa la formula generale per calcolare lo sconto fedeltà, ma con parametri diversi, non
è necessario fornire un'implementazione personalizzata perché è possibile impostare gli argomenti tramite un
metodo statico. Il codice seguente imposta ad esempio un "apprezzamento del cliente" che premia qualsiasi
cliente che si sia iscritto da più di un mese:

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);


Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Estendere l'implementazione predefinita
Il codice aggiunto finora offre un'implementazione utile per gli scenari in cui gli utenti vogliono qualcosa di
simile all'implementazione predefinita o per fornire un set non correlato di regole. Per una funzionalità finale,
verrà eseguito il refactoring del codice per abilitare scenari in cui gli utenti potrebbero scegliere di sviluppare
ulteriormente l'implementazione predefinita.
Si supponga ad esempio che una startup voglia attirare nuovi clienti. Offre uno sconto del 50% sul primo ordine
di un nuovo cliente. Altrimenti, i clienti esistenti ottengono lo sconto standard. L'autore della raccolta deve
trasferire l'implementazione predefinita in un metodo protected static in modo che qualsiasi classe che
implementi questa interfaccia possa riutilizzare il codice nella propria implementazione. L'implementazione
predefinita del membro di interfaccia chiama anche questo metodo predefinito:

public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);


protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
DateTime start = DateTime.Now - length;

if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))


{
return discountPercent;
}
return 0;
}

In un'implementazione di una classe che implementa questa interfaccia, l'override può chiamare il metodo
helper statico ed estendere questa logica per fornire lo sconto "nuovo cliente":

public decimal ComputeLoyaltyDiscount()


{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}

È possibile visualizzare il codice completo nel repository degli esempi su GitHub. L'applicazione di base è
disponibile nel repository di esempi in GitHub.
Queste nuove funzionalità implicano che le interfacce possono essere aggiornate in modo sicuro quando è
disponibile un'implementazione predefinita ragionevole per questi nuovi membri. Progettare con attenzione le
interfacce per esprimere singole idee funzionali che possono essere implementate da più classi. In questo modo
è più facile aggiornare le definizioni di queste interfacce quando vengono individuati nuovi requisiti per la stessa
idea funzionale.
Esercitazione: combinare le funzionalità in durante
la creazione di classi mediante interfacce con
metodi di interfaccia predefiniti
14/05/2020 • 15 minutes to read • Edit Online

A partire da C# 8.0 su .NET Core 3.0 è possibile definire un'implementazione quando si dichiara un membro di
un'interfaccia. Questa funzionalità fornisce nuove funzionalità in cui è possibile definire le implementazioni
predefinite per le funzionalità dichiarate nelle interfacce. Le classi possono scegliere quando eseguire l'override
della funzionalità, quando utilizzare la funzionalità predefinita e quando non dichiarare il supporto per le
funzionalità discrete.
In questa esercitazione si apprenderà come:
Creare interfacce con implementazioni che descrivono funzionalità discrete.
Creare classi che usano le implementazioni predefinite.
Creare classi che eseguono l'override di alcune o di tutte le implementazioni predefinite.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET Core, incluso il compilatore C# 8,0. Il compilatore
C# 8,0 è disponibile a partire da Visual Studio 2019 versione 16,3o .NET Core 3,0 SDK o versione successiva.

Limitazioni dei metodi di estensione


Uno dei modi in cui è possibile implementare il comportamento che viene visualizzato come parte di
un'interfaccia consiste nel definire metodi di estensione che forniscono il comportamento predefinito. Le
interfacce dichiarano un set minimo di membri, fornendo una superficie di attacco maggiore per qualsiasi classe
che implementa tale interfaccia. I metodi di estensione in forniscono, ad esempio, Enumerable
l'implementazione di qualsiasi sequenza come origine di una query LINQ.
I metodi di estensione vengono risolti in fase di compilazione, usando il tipo dichiarato della variabile. Le classi
che implementano l'interfaccia possono fornire un'implementazione migliore per qualsiasi metodo di
estensione. Le dichiarazioni di variabili devono corrispondere al tipo di implementazione per consentire al
compilatore di scegliere l'implementazione. Quando il tipo in fase di compilazione corrisponde all'interfaccia, le
chiamate al metodo vengono risolte nel metodo di estensione. Un altro problema con i metodi di estensione è
che questi metodi sono accessibili laddove la classe che contiene i metodi di estensione è accessibile. Le classi
non possono dichiarare se devono o non devono fornire funzionalità dichiarate nei metodi di estensione.
A partire da C# 8,0, è possibile dichiarare le implementazioni predefinite come metodi di interfaccia. Quindi,
ogni classe utilizza automaticamente l'implementazione predefinita. Qualsiasi classe in grado di fornire
un'implementazione migliore può eseguire l'override della definizione del metodo di interfaccia con un
algoritmo migliore. In un certo senso, questa tecnica è simile a come si possono usare i metodi di estensione.
In questo articolo si apprenderà come le implementazioni dell'interfaccia predefinite consentano nuovi scenari.

Progettare l'applicazione
Si consideri un'applicazione di automazione domestica. Probabilmente si hanno molti tipi diversi di luci e
indicatori che possono essere usati in tutta la casa. Ogni luce deve supportare le API per attivarle e disattivarle e
per segnalare lo stato corrente. Alcune luci e indicatori possono supportare altre funzionalità, ad esempio:
Accendere la luce, quindi spegnerla dopo un timer.
Lampeggiare la luce per un certo periodo di tempo.
Alcune di queste funzionalità estese potrebbero essere emulate in dispositivi che supportano il set minimo. Che
indica che fornisce un'implementazione predefinita. Per i dispositivi con più funzionalità integrate, il software del
dispositivo utilizzerebbe le funzionalità native. Per altre luci, può scegliere di implementare l'interfaccia e di
usare l'implementazione predefinita.
I membri di interfaccia predefiniti sono una soluzione migliore per questo scenario rispetto ai metodi di
estensione. Gli autori delle classi possono controllare le interfacce che scelgono di implementare. Le interfacce
che scelgono sono disponibili come metodi. Inoltre, poiché i metodi di interfaccia predefiniti sono virtuali per
impostazione predefinita, la distribuzione del metodo sceglie sempre l'implementazione nella classe.
Viene ora creato il codice per illustrare queste differenze.

Creare interfacce
Per iniziare, creare l'interfaccia che definisce il comportamento per tutte le luci:

public interface ILight


{
void SwitchOn();
void SwitchOff();
bool IsOn();
}

Una fixture di base di overhead può implementare questa interfaccia come illustrato nel codice seguente:

public class OverheadLight : ILight


{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;

public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

In questa esercitazione il codice non consente di guidare i dispositivi, ma emula le attività scrivendo messaggi
nella console. È possibile esplorare il codice senza automatizzare la propria abitazione.
Definire quindi l'interfaccia per una luce che può essere disattivata automaticamente dopo un timeout:

public interface ITimerLight : ILight


{
Task TurnOnFor(int duration);
}

È possibile aggiungere un'implementazione di base alla luce del sovraccarico, ma una soluzione migliore
consiste nel modificare questa definizione di interfaccia per fornire un' virtual implementazione predefinita:
public interface ITimerLight : ILight
{
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Using the default interface method for the ITimerLight.TurnOnFor.");
SwitchOn();
await Task.Delay(duration);
SwitchOff();
Console.WriteLine("Completed ITimerLight.TurnOnFor sequence.");
}
}

Con l'aggiunta di questa modifica, la OverheadLight classe può implementare la funzione timer dichiarando il
supporto per l'interfaccia:

public class OverheadLight : ITimerLight { }

Un tipo di luce diverso può supportare un protocollo più sofisticato. Può fornire la propria implementazione per
TurnOnFor , come illustrato nel codice seguente:

public class HalogenLight : ITimerLight


{
private enum HalogenLightState
{
Off,
On,
TimerModeOn
}

private HalogenLightState state;


public void SwitchOn() => state = HalogenLightState.On;
public void SwitchOff() => state = HalogenLightState.Off;
public bool IsOn() => state != HalogenLightState.Off;
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Halogen light starting timer function.");
state = HalogenLightState.TimerModeOn;
await Task.Delay(duration);
state = HalogenLightState.Off;
Console.WriteLine("Halogen light finished custom timer function");
}

public override string ToString() => $"The light is {state}";


}

A differenza dei metodi della classe virtuale che eseguono l'override, la dichiarazione di TurnOnFor nella
HalogenLight classe non usa la override parola chiave.

Funzionalità di combinazione e corrispondenza


I vantaggi dei metodi di interfaccia predefiniti diventano più chiari quando si introducono funzionalità più
avanzate. L'uso delle interfacce consente di combinare e confrontare le funzionalità. Consente inoltre a ogni
autore di classi di scegliere tra l'implementazione predefinita e un'implementazione personalizzata. Aggiungere
un'interfaccia con un'implementazione predefinita per una luce lampeggiante:
public interface IBlinkingLight : ILight
{
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Using the default interface method for IBlinkingLight.Blink.");
for (int count = 0; count < repeatCount; count++)
{
SwitchOn();
await Task.Delay(duration);
SwitchOff();
await Task.Delay(duration);
}
Console.WriteLine("Done with the default interface method for IBlinkingLight.Blink.");
}
}

L'implementazione predefinita consente a qualsiasi luce di lampeggiare. La luce del sovraccarico può
aggiungere funzionalità di timer e di lampeggio usando l'implementazione predefinita:

public class OverheadLight : ILight, ITimerLight, IBlinkingLight


{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;

public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

Un nuovo tipo di luce, LEDLight supporta sia la funzione timer che la funzione Blink direttamente. Questo stile
chiaro implementa entrambe le ITimerLight IBlinkingLight interfacce e ed esegue l'override del Blink
Metodo:

public class LEDLight : IBlinkingLight, ITimerLight, ILight


{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("LED Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("LED Light has finished the Blink funtion.");
}

public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

Un ExtraFancyLight potrebbe supportare direttamente le funzioni Blink e timer:


public class ExtraFancyLight : IBlinkingLight, ITimerLight, ILight
{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Extra Fancy Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("Extra Fancy Light has finished the Blink function.");
}
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Extra Fancy light starting timer function.");
await Task.Delay(duration);
Console.WriteLine("Extra Fancy light finished custom timer function");
}

public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

L'oggetto HalogenLight creato in precedenza non supporta il lampeggio. Quindi, non aggiungere all'
IBlinkingLight elenco delle interfacce supportate.

Rilevare i tipi leggeri usando i criteri di ricerca


Scriviamo quindi un codice di test. È possibile usare la funzionalità di ricerca dei criteri di C# per determinare le
funzionalità di una luce esaminando le interfacce supportate. Il metodo seguente consente di esercitare le
funzionalità supportate di ogni luce:

private static async Task TestLightCapabilities(ILight light)


{
// Perform basic tests:
light.SwitchOn();
Console.WriteLine($"\tAfter switching on, the light is {(light.IsOn() ? "on" : "off")}");
light.SwitchOff();
Console.WriteLine($"\tAfter switching off, the light is {(light.IsOn() ? "on" : "off")}");

if (light is ITimerLight timer)


{
Console.WriteLine("\tTesting timer function");
await timer.TurnOnFor(1000);
Console.WriteLine("\tTimer function completed");
}
else
{
Console.WriteLine("\tTimer function not supported.");
}

if (light is IBlinkingLight blinker)


{
Console.WriteLine("\tTesting blinking function");
await blinker.Blink(500, 5);
Console.WriteLine("\tBlink function completed");
}
else
{
Console.WriteLine("\tBlink function not supported.");
}
}
Il codice seguente nel Main metodo crea ogni tipo di luce in sequenza e testa tale luce:

static async Task Main(string[] args)


{
Console.WriteLine("Testing the overhead light");
var overhead = new OverheadLight();
await TestLightCapabilities(overhead);
Console.WriteLine();

Console.WriteLine("Testing the halogen light");


var halogen = new HalogenLight();
await TestLightCapabilities(halogen);
Console.WriteLine();

Console.WriteLine("Testing the LED light");


var led = new LEDLight();
await TestLightCapabilities(led);
Console.WriteLine();

Console.WriteLine("Testing the fancy light");


var fancy = new ExtraFancyLight();
await TestLightCapabilities(fancy);
Console.WriteLine();
}

Come il compilatore determina l'implementazione migliore


In questo scenario viene illustrata un'interfaccia di base senza alcuna implementazione. L'aggiunta di un metodo
nell' ILight interfaccia introduce nuove complessità. Le regole del linguaggio che governano i metodi di
interfaccia predefiniti riducono al minimo l'effetto sulle classi concrete che implementano più interfacce
derivate. Viene ora migliorata l'interfaccia originale con un nuovo metodo che Mostra come cambia l'utilizzo.
Ogni indicatore chiaro può segnalare lo stato di alimentazione come valore enumerato:

public enum PowerStatus


{
NoPower,
ACPower,
FullBattery,
MidBattery,
LowBattery
}

L'implementazione predefinita non presuppone alcun risparmio energia:

public interface ILight


{
void SwitchOn();
void SwitchOff();
bool IsOn();
public PowerStatus Power() => PowerStatus.NoPower;
}

Queste modifiche vengono compilate in modo corretto, anche se ExtraFancyLight dichiara il supporto per l'
ILight interfaccia e le interfacce derivate, ITimerLight e IBlinkingLight . Nell'interfaccia è stata dichiarata
solo un'implementazione "più vicina" ILight . Qualsiasi classe che dichiara una sostituzione diventerà
un'implementazione "più vicina". Sono stati presentati esempi nelle classi precedenti che hanno scavalcato i
membri di altre interfacce derivate.
Evitare di eseguire l'override dello stesso metodo in più interfacce derivate. Questa operazione crea una
chiamata al metodo ambigua ogni volta che una classe implementa entrambe le interfacce derivate. Il
compilatore non può scegliere un metodo migliore, quindi genera un errore. Se, ad esempio, IBlinkingLight in
e è stato ITimerLight implementato un override di PowerStatus , è OverheadLight necessario fornire un
override più specifico. In caso contrario, il compilatore non può scegliere tra le implementazioni nelle due
interfacce derivate. In genere è possibile evitare questa situazione mantenendo le definizioni di interfaccia
piccole e incentrate su una sola funzionalità. In questo scenario, ogni funzionalità di una luce è la propria
interfaccia; più interfacce vengono ereditate solo dalle classi.
Questo esempio illustra uno scenario in cui è possibile definire funzionalità discrete che possono essere
combinate nelle classi. Per dichiarare qualsiasi set di funzionalità supportate, dichiarare le interfacce supportate
da una classe. L'utilizzo di metodi di interfaccia predefiniti virtuali consente alle classi di utilizzare o definire
un'implementazione diversa per uno o tutti i metodi di interfaccia. Questa funzionalità del linguaggio offre nuovi
modi per modellare i sistemi reali che si stanno creando. I metodi di interfaccia predefiniti offrono un modo più
chiaro per esprimere le classi correlate che possono combinare e associare funzionalità diverse utilizzando
implementazioni virtuali di tali funzionalità.
Indici e intervalli
28/01/2021 • 11 minutes to read • Edit Online

Gli intervalli e gli indici forniscono una sintassi concisa per l'accesso a singoli elementi o intervalli in una
sequenza.
In questa esercitazione si apprenderà come:
Usare la sintassi per gli intervalli in una sequenza.
Comprendere le decisioni a livello di progettazione per l'inizio e la fine di ogni sequenza.
Analizzare gli scenari per i tipi Index e Range.

Supporto del linguaggio per indici e intervalli


Questo supporto per il linguaggio si basa su due nuovi tipi e due nuovi operatori:
System.Index rappresenta un indice in una sequenza.
Indice dell'operatore end ^ , che specifica che un indice è relativo alla fine di una sequenza.
System.Range rappresenta un intervallo secondario di una sequenza.
Operatore Range .. , che specifica l'inizio e la fine di un intervallo come operandi.
Per iniziare, ecco come funzionano le regole per gli indici. Prendere in considerazione una matrice sequence .
L'indice 0 è uguale a sequence[0] . L'indice ^0 è uguale a sequence[sequence.Length] . L'espressione
sequence[^0] genera un'eccezione, analogamente a quanto sequence[sequence.Length] accade. Per qualsiasi
numero n , l'indice ^n è uguale a sequence[sequence.Length - n] .

string[] words = new string[]


{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0

È possibile recuperare l'ultima parola con l'indice ^1 . Aggiungere il codice seguente sotto l'inizializzazione:

Console.WriteLine($"The last word is {words[^1]}");

Un intervallo specifica inizio e fine di un intervallo. Gli intervalli sono esclusivi, ovvero la fine non è inclusa
nell'intervallo. L'intervallo [0..^0] rappresenta l'intero intervallo, proprio come [0..sequence.Length]
rappresenta l'intero intervallo.
Il codice seguente crea un intervallo secondario con le parole "quick", "brown" e "fox". Include da words[1] a
words[3] . L'elemento words[4] non è compreso nell'intervallo. Aggiungere il codice seguente allo stesso
metodo. Copiarlo e incollarlo nella parte inferiore della finestra interattiva.
string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();

Il codice seguente restituisce l'intervallo con "Lazy" e "Dog". Include words[^2] e words[^1] . L'indice finale
words[^0] non viene incluso. Aggiungere anche il codice seguente:

string[] lazyDog = words[^2..^0];


foreach (var word in lazyDog)
Console.Write($"< {word} >");
Console.WriteLine();

Gli esempi seguenti creano intervalli aperti alle estremità, per l'inizio, la fine o entrambe:

string[] allWords = words[..]; // contains "The" through "dog".


string[] firstPhrase = words[..4]; // contains "The" through "fox"
string[] lastPhrase = words[6..]; // contains "the, "lazy" and "dog"
foreach (var word in allWords)
Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in firstPhrase)
Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in lastPhrase)
Console.Write($"< {word} >");
Console.WriteLine();

È anche possibile dichiarare gli intervalli o gli indici come variabili. In seguito la variabile può essere usata
all'interno dei caratteri [ e ] :

Index the = ^3;


Console.WriteLine(words[the]);
Range phrase = 1..4;
string[] text = words[phrase];
foreach (var word in text)
Console.Write($"< {word} >");
Console.WriteLine();

L'esempio seguente illustra molti dei motivi per cui sono state fatte tali scelte. Modificare x , y , e z per
provare combinazioni diverse. Durante le varie prove, usare valori per cui x è minore di y e y è minore di z
per le combinazioni valide. Aggiungere il codice seguente in un nuovo metodo. Provare combinazioni diverse:
int[] numbers = Enumerable.Range(0, 100).ToArray();
int x = 12;
int y = 25;
int z = 36;

Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}");


Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");

Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:");


Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through
{y_z[^1]}");

Console.WriteLine("numbers[x..^x] removes x elements at each end:");


Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}");

Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]");


Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");

Supporto dei tipi per gli indici e gli intervalli


Gli indici e gli intervalli forniscono una sintassi chiara e concisa per accedere a un singolo elemento o a un
intervallo di elementi in una sequenza. Un'espressione di indice restituisce in genere il tipo degli elementi di una
sequenza. Un'espressione di intervallo restituisce in genere lo stesso tipo di sequenza della sequenza di origine.
Qualsiasi tipo che fornisce un indicizzatore con un Index Range parametro o supporta in modo esplicito gli
indici o gli intervalli rispettivamente. Un indicizzatore che accetta un solo Range parametro può restituire un
tipo di sequenza diverso, ad esempio System.Span<T> .

IMPORTANT
Le prestazioni del codice con l'operatore Range dipendono dal tipo dell'operando Sequence.
La complessità temporale dell'operatore di intervallo dipende dal tipo di sequenza. Se, ad esempio, la sequenza è un
oggetto string o una matrice, il risultato è una copia della sezione specificata dell'input, quindi la complessità dell'ora è
o (n) , dove N è la lunghezza dell'intervallo. D'altra parte, se si tratta di un oggetto System.Span<T> o
System.Memory<T> , il risultato fa riferimento allo stesso archivio di backup, che significa che non è presente alcuna copia
e l'operazione è o (1).
Oltre alla complessità temporale, in questo modo vengono generate allocazioni e copie aggiuntive, che hanno un effetto
sulle prestazioni. Nel codice sensibile alle prestazioni, prendere in considerazione l'uso di Span<T> o Memory<T> come
tipo di sequenza, perché l'operatore di intervallo non lo alloca.

Un tipo può essere conteggiato se dispone di una proprietà denominata Length o Count con un getter
accessibile e un tipo restituito int . Un tipo conteggiabile che non supporta in modo esplicito gli indici o gli
intervalli può fornire un supporto implicito. Per ulteriori informazioni, vedere le sezioni supporto implicito degli
indici e supporto per intervalli impliciti della Nota relativa alla proposta di funzionalità. Gli intervalli che usano il
supporto di intervalli impliciti restituiscono lo stesso tipo di sequenza della sequenza di origine.
I tipi .NET seguenti, ad esempio, supportano sia gli indici che gli intervalli: String , Span<T> e ReadOnlySpan<T>
. List<T>Supporta gli indici, ma non supporta gli intervalli.
Array ha un comportamento più sfumato. Le matrici a dimensione singola supportano sia gli indici che gli
intervalli. Non esistono matrici multidimensionali. L'indicizzatore per una matrice multidimensionale ha più
parametri, non un singolo parametro. Le matrici irregolari, denominate anche matrici di matrici, supportano sia
gli intervalli che gli indicizzatori. Nell'esempio seguente viene illustrato come eseguire l'iterazione di una
sottosezione rettangolare di una matrice irregolare. Viene eseguita l'iterazione della sezione al centro, esclusa la
prima e le ultime tre righe e la prima e l'ultima due colonne da ogni riga selezionata:

var jagged = new int[10][]


{
new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
new int[10] { 10,11,12,13,14,15,16,17,18,19},
new int[10] { 20,21,22,23,24,25,26,27,28,29},
new int[10] { 30,31,32,33,34,35,36,37,38,39},
new int[10] { 40,41,42,43,44,45,46,47,48,49},
new int[10] { 50,51,52,53,54,55,56,57,58,59},
new int[10] { 60,61,62,63,64,65,66,67,68,69},
new int[10] { 70,71,72,73,74,75,76,77,78,79},
new int[10] { 80,81,82,83,84,85,86,87,88,89},
new int[10] { 90,91,92,93,94,95,96,97,98,99},
};

var selectedRows = jagged[3..^3];

foreach (var row in selectedRows)


{
var selectedColumns = row[2..^2];
foreach (var cell in selectedColumns)
{
Console.Write($"{cell}, ");
}
Console.WriteLine();
}

In tutti i casi, l'operatore di intervallo per Array alloca una matrice per archiviare gli elementi restituiti.

Scenari per gli indici e gli intervalli


Si utilizzeranno spesso intervalli e indici quando si desidera analizzare una parte di una sequenza più ampia. La
nuova sintassi è più chiara nella lettura esatta della parte della sequenza. La funzione locale MovingAverage
accetta un Range come argomento. In seguito il metodo enumera solo quell'intervallo durante il calcolo dei
valori minimo e massimo e della media. Provare a includere il codice seguente nel progetto:
int[] sequence = Sequence(1000);

for(int start = 0; start < sequence.Length; start += 100)


{
Range r = start..(start+10);
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}");
}

for (int start = 0; start < sequence.Length; start += 100)


{
Range r = ^(start + 10)..^start;
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}");
}

(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);

int[] Sequence(int count) =>


Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100)).ToArray();
Esercitazione: Esprimere più chiaramente le finalità
di progettazione con tipi riferimento nullable e non
nullable
02/11/2020 • 19 minutes to read • Edit Online

C# 8,0 introduce i tipi di riferimento Nullable, che completano i tipi di riferimento in modo analogo ai tipi di
valore Nullable. Per dichiarare una variabile come tipo riferimento nullable basta aggiungere un ? alla fine
del tipo. Ad esempio, string? rappresenta un tipo string nullable. È possibile usare questi nuovi tipi per
esprimere più chiaramente le finalità della progettazione: alcune variabili devono avere sempre un valore,
mentre in altre un valore può mancare.
In questa esercitazione si apprenderà come:
Incorporare tipi riferimento nullable e non nullable nelle progettazioni.
Abilitare i controlli dei tipi riferimento nullable in tutto il codice.
Scrivere codice in cui il compilatore impone tali decisioni di progettazione.
Usare la funzionalità dei riferimenti nullable nelle proprie progettazioni.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET Core, incluso il compilatore C# 8,0. Il compilatore
C# 8,0 è disponibile con Visual Studio 2019o .NET Core 3,0.
Per questa esercitazione si presuppone che l'utente abbia familiarità con C# e .NET, inclusa l'interfaccia della riga
di comando di .NET Core o Visual Studio.

Incorporare tipi riferimento nullable nelle progettazioni


In questa esercitazione si compilerà una libreria che modella l'esecuzione di un sondaggio. Il codice usa tipi
riferimento sia nullable che non nullable per rappresentare concetti reali. Le domande del sondaggio non
possono mai essere Null. Un partecipante al sondaggio potrebbe preferire non rispondere a una domanda. Le
risposte potrebbero essere null in questo caso.
Il codice scritto per questo esempio esprime questa intenzione e il compilatore la applica.

Creare l'applicazione e abilitare i tipi riferimento nullable


Creare una nuova applicazione console in Visual Studio oppure dalla riga di comando tramite
dotnet new console . Assegnare all'applicazione il nome NullableIntroduction . Dopo aver creato l'applicazione,
è necessario specificare che l'intero progetto venga compilato in un contesto di annotazione
Nullable abilitato. Aprire il file con estensione csproj e aggiungere un Nullable elemento all' PropertyGroup
elemento. Impostare il relativo valore su enable . È necessario optare per la funzionalità dei tipi di riferimento
Nullable , anche nei progetti C# 8,0. Questo perché dopo che la funzionalità viene attivata, le dichiarazioni di
variabili di riferimento esistenti diventano tipi riferimento non nullable . Sebbene questa decisione consenta
di individuare i problemi per cui il codice esistente potrebbe non avere controlli null appropriati, potrebbe non
riflettere accuratamente la finalità di progettazione originale:

<Nullable>enable</Nullable>
Progettare i tipi per l'applicazione
Per questa applicazione di sondaggio è necessario creare alcune classi:
Una classe che modella l'elenco di domande.
Una classe che modella un elenco di persone contattate per il sondaggio.
Una classe che modella le risposte di una persona che ha partecipato al sondaggio.
Questi tipi useranno tipi riferimento sia nullable sia non nullable per indicare i membri obbligatori e quelli
facoltativi. I tipi riferimento nullable comunicano chiaramente questa finalità della progettazione:
Le domande che costituiscono il sondaggio non possono mai essere Null. Una domanda vuota non ha infatti
alcun senso.
I partecipanti al sondaggio non possono mai essere Null. Si vuole infatti tenere traccia delle persone che
sono state contattate, anche quelle che non hanno accettato di partecipare.
Una risposta a una domanda può essere Null. I partecipanti possono infatti rifiutarsi di rispondere ad alcune
o a tutte le domande.
Se è stata eseguita la programmazione in C#, è possibile che si sia abituati a fare riferimento ai tipi che
consentono null valori che potrebbero aver perso altre opportunità per dichiarare istanze non nullable:
La raccolta delle domande deve essere non nullable.
La raccolta dei partecipanti deve essere non nullable.
Quando si scrive il codice, si noterà che un tipo di riferimento non nullable come il valore predefinito per i
riferimenti evita errori comuni che potrebbero condurre a NullReferenceException . Una lezione di questa
esercitazione consiste nel prendere decisioni sulle variabili che possono essere o meno null . Nelle versioni
precedenti il linguaggio non forniva la sintassi necessaria per esprimere queste decisioni. Ora invece tutto
questo è possibile.
L'app da compilare esegue i passaggi seguenti:
1. Consente di creare un sondaggio e di aggiungere domande.
2. Crea un set pseudo-casuale di intervistati per il sondaggio.
3. Contatta gli intervistati fino a quando la dimensione del sondaggio completata raggiunge il numero
obiettivo.
4. Scrive statistiche importanti sulle risposte del sondaggio.

Compilare il sondaggio con tipi di riferimento nullable e non nullable


Il primo codice scritto crea il sondaggio. Occorre scrivere le classi necessarie per modellare una domanda del
sondaggio e l'esecuzione del sondaggio. Il sondaggio ha tre tipi di domande, distinte dal formato della risposta:
risposte Sì/No, risposte in forma di numero e risposte testuali. Creare una public SurveyQuestion classe:

namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}

Il compilatore interpreta ogni dichiarazione di variabile di tipo riferimento come tipo di riferimento non
nullable per il codice in un contesto di annotazione Nullable abilitato. È possibile vedere il primo avviso
aggiungendo le proprietà del testo della domanda e il tipo di domanda, come illustrato nel codice seguente:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}

public class SurveyQuestion


{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}

Poiché QuestionText non è stato inizializzato, il compilatore genera un avviso che informa che una proprietà
non nullable non è stata inizializzata. Un requisito della progettazione è che il testo della domanda non sia Null,
pertanto occorre aggiunge un costruttore per inizializzare questa proprietà e anche il valore QuestionType . La
definizione finale della classe è simile al codice seguente:

namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}

public class SurveyQuestion


{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }

public SurveyQuestion(QuestionType typeOfQuestion, string text) =>


(TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}
}

Aggiungendo il costruttore, l'avviso viene rimosso. Anche l'argomento del costruttore è un tipo riferimento non
nullable, quindi il compilatore non genera alcun avviso.
Creare quindi una classe public denominata SurveyRun . Questa classe contiene un elenco di metodi e oggetti
SurveyQuestion per aggiungere domande al sondaggio, come illustrato nel codice seguente:

using System.Collections.Generic;

namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();

public void AddQuestion(QuestionType type, string question) =>


AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) => surveyQuestions.Add(surveyQuestion);
}
}

Anche in questo caso occorre inizializzare l'oggetto elenco su un valore non Null per evitare che il compilatore
generi un avviso. Nel secondo overload di AddQuestion non ci sono controlli dei valori Null in quanto non sono
necessari: la variabile è stata infatti dichiarata come non nullable. Il suo valore non può essere null .
Passare a Program.cs nell'editor e sostituire il contenuto di Main con le righe di codice seguenti:

var surveyRun = new SurveyRun();


surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many times (to the nearest 100) has that
happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");

Poiché l'intero progetto si trova in un contesto di annotazione Nullable abilitato, si riceveranno avvisi quando si
passa null a un metodo che prevede un tipo di riferimento non nullable. Provare ad aggiungere la riga
seguente a Main :

surveyRun.AddQuestion(QuestionType.Text, default);

Creare i partecipanti e ottenere le risposte al sondaggio


A questo punto occorre scrivere il codice che genera le risposte al sondaggio. Questo processo include varie
piccole attività:
1. Compilare un metodo che generi gli oggetti partecipante. Questi oggetti rappresentano le persone a cui è
stato chiesto di completare il sondaggio.
2. Creare la logica per simulare la formulazione delle domande a un partecipante e la raccolta delle risposte
oppure di nessun dato, se il partecipante non ha risposto.
3. Ripetere finché non ha risposto al sondaggio un numero sufficiente di partecipanti.
A questo punto occorre aggiungere una classe che rappresenti una risposta al sondaggio. Abilitare il supporto
dei tipi riferimento nullable. Aggiungere una proprietà Id e un costruttore che la inizializza, come illustrato nel
codice seguente:

namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }

public SurveyResponse(int id) => Id = id;


}
}

Quindi aggiungere un metodo static per la creazione di nuovi partecipanti tramite la generazione di un ID
casuale:

private static readonly Random randomGenerator = new Random();


public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

La responsabilità principale di questa classe è generare le risposte di un partecipante alle domande del
sondaggio. A questo scopo è necessario eseguire i passaggi seguenti:
1. Chiedere di partecipare al sondaggio. Se la persona non acconsente, restituire una risposta mancante (o
Null).
2. Porre ogni domanda e registrare la risposta. Ogni risposta può anche essere mancante (o Null).
Aggiungere il codice seguente alla classe SurveyResponse :

private Dictionary<int, string>? surveyResponses;


public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}

private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;

private string? GenerateAnswer(SurveyQuestion question)


{
switch (question.TypeOfQuestion)
{
case QuestionType.YesNo:
int n = randomGenerator.Next(-1, 2);
return (n == -1) ? default : (n == 0) ? "No" : "Yes";
case QuestionType.Number:
n = randomGenerator.Next(-30, 101);
return (n < 0) ? default : n.ToString();
case QuestionType.Text:
default:
switch (randomGenerator.Next(0, 5))
{
case 0:
return default;
case 1:
return "Red";
case 2:
return "Green";
case 3:
return "Blue";
}
return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
}
}

L'archivio per le risposte del sondaggio è un Dictionary<int, string>? , a indicare che può essere Null. Si sta
usando la nuova funzionalità del linguaggio per dichiarare la finalità della progettazione, sia al compilatore che a
chiunque legga il codice successivamente. Se si esegue la dereferenziazione surveyResponses senza prima
verificare il null valore, verrà visualizzato un avviso del compilatore. Non si riceve un avviso nel metodo
AnswerSurvey perché il compilatore è in grado di determinare che la variabile surveyResponses è stata
impostata su un valore non Null.
L'uso di null per le risposte mancanti evidenzia un aspetto essenziale per l'utilizzo dei tipi riferimento nullable,
ovvero l'obiettivo non è rimuovere tutti i valori null dal programma. L'obiettivo è invece quello di assicurarsi
che il codice scritto esprima lo scopo della progettazione. I valori mancanti sono un concetto necessario da
esprimere nel codice. Il valore null rappresenta un modo chiaro per esprimere tali valori mancanti. Il tentativo
di rimuovere tutti i valori null porta solo alla definizione di un altro modo per esprimere i valori mancanti
senza null .
A questo punto occorre scrivere il metodo PerformSurvey nella classe SurveyRun . Aggiungere il codice seguente
nella classe SurveyRun :

private List<SurveyResponse>? respondents;


public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = new List<SurveyResponse>();
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}

Anche in questo caso la scelta di un List<SurveyResponse>? nullable indica che la risposta può essere Null. Indica
che il sondaggio non è ancora stato distribuito ad alcun partecipante. Si noti che i partecipanti continuano a
essere aggiunti finché non acconsente un numero sufficiente.
L'ultimo passaggio dell'esecuzione del sondaggio consiste nell'aggiungere una chiamata per eseguire il
sondaggio alla fine del metodo Main :

surveyRun.PerformSurvey(50);

Esaminare le risposte al sondaggio


L'ultimo passaggio è la visualizzazione dei risultati del sondaggio. Occorre aggiungere codice a molte delle
classi scritte. Questo codice dimostra il valore della distinzione fra i tipi riferimento nullable e non nullable. Per
iniziare, aggiungere i due membri con corpo di espressione seguenti alla classe SurveyResponse :

public bool AnsweredSurvey => surveyResponses != null;


public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

Poiché surveyResponses è un tipo di riferimento Nullable, i controlli null sono necessari prima di dereferenziarli.
Il Answer metodo restituisce una stringa che non ammette i valori null, pertanto è necessario coprire il caso di
una risposta mancante usando l'operatore di Unione null.
Aggiungere quindi questi tre membri con corpo di espressione alla classe SurveyRun :

public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());


public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

Il membro AllParticipants deve tenere conto del fatto che la variabile respondents potrebbe essere Null, ma il
valore restituito non può essere Null. Se si modifica l'espressione rimuovendo ?? e la sequenza vuota che
segue, il compilatore avvisa che il metodo potrebbe restituire null e la sua firma restituita restituisce un tipo
non nullable.
Infine, aggiungere il ciclo seguente alla fine del metodo Main :
foreach (var participant in surveyRun.AllParticipants)
{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
}
}
else
{
Console.WriteLine("\tNo responses");
}
}

Non è necessario eseguire alcun controllo dei valori null in questo codice perché le interfacce sottostanti sono
state progettate in modo che restituiscano tutte tipi di riferimento non nullable.

Ottenere il codice
Il codice dell'esercitazione completata è disponibile nel repository samples nella cartella
csharp/NullableIntroduction.
È possibile fare delle prove cambiando le dichiarazioni di tipo fra tipi riferimento nullable e non nullable e
osservare come vengono generati avvisi diversi per assicurare che non venga dereferenziato accidentalmente
un valore null .

Passaggi successivi
Ottenere altre informazioni eseguendo la migrazione di un'applicazione esistente per usare i tipi riferimento
nullable:
Aggiornare un'app per i tipi riferimento nullable
Informazioni su come usare il tipo di riferimento Nullable quando si usa Entity Framework:

Nozioni fondamentali sull'Entity Framework Core: utilizzo di tipi di riferimento Nullable


Esercitazione: Eseguire la migrazione di codice
esistente con tipi di riferimento nullableTutorial:
Migrate existing code with nullable reference types
18/03/2020 • 25 minutes to read • Edit Online

In C# 8 sono ora disponibili i tipi riferimento nullable , che completano i tipi riferimento allo stesso modo in
cui i tipi valore nullable completano i tipi valore. Per dichiarare una variabile come tipo riferimento nullable
basta aggiungere un ? alla fine del tipo. Ad esempio, string? rappresenta un tipo string nullable. È possibile
usare questi nuovi tipi per esprimere più chiaramente le finalità della progettazione: alcune variabili devono
avere sempre un valore, mentre in altre un valore può mancare. Tutte le variabili esistenti di un tipo riferimento
verrebbero interpretate come un tipo riferimento non nullable.
In questa esercitazione si apprenderà come:
Abilitare i controlli dei riferimenti Null mentre si lavora con il codice.
Diagnosticare e correggere i diversi avvisi correlati ai valori Null.
Gestire l'interfaccia tra contesti abilitati per nullable e contesti disabilitati per nullable.
Controllare i contesti delle annotazioni nullable.

Prerequisites
È necessario configurare il computer per l'esecuzione di .NET Core, incluso il compilatore c'è 8.0. Il compilatore
di C'è 8 è disponibile a partire da Visual Studio 2019 versione 16.3 o .NET Core 3.0 SDK.
Per questa esercitazione si presuppone che l'utente abbia familiarità con C# e .NET, inclusa l'interfaccia della riga
di comando di .NET Core o Visual Studio.

Esplorare l'applicazione di esempio


L'applicazione di esempio di cui verrà eseguita la migrazione è un'app Web lettore di feed RSS. L'app consente
di leggere da un singolo feed RSS e visualizzare riassunti per gli articoli più recenti. È possibile selezionare uno
qualsiasi degli articoli da visitare. L'applicazione è relativamente nuova, ma è stata scritta prima che
diventassero disponibili i tipi riferimento nullable. Pur essendo rappresentative di solidi principi, le decisioni di
progettazione non sfruttano questa importante funzionalità del linguaggio.
L'applicazione di esempio include una libreria di unit test che convalida le funzionalità principali dell'app. Tale
progetto renderà più facile eseguire l'aggiornamento in modo sicuro, se si modifica qualsiasi implementazione
in base agli avvisi generati. È possibile scaricare il codice iniziale dal repository GitHub dotnet/samples.
L'obiettivo di migrazione del progetto dovrebbe essere di sfruttare le nuove funzionalità del linguaggio in modo
da esprimere chiaramente le finalità per il supporto dei valori Null delle variabili e farlo in modo tale che il
compilatore non generi avvisi quando il contesto delle annotazioni nullable e il contesto degli avvisi nullable è
impostato su enabled .

Aggiornare i progetti a C# 8
Un buon primo passo è determinare l'ambito dell'attività di migrazione. Per iniziare, aggiornare il progetto a C#
8.0 (o versione successiva). Aggiungere LangVersion l'elemento a PropertyGroup in entrambi i file csproj per il
progetto Web e il progetto di unit test:
<LangVersion>8.0</LangVersion>

L'aggiornamento della versione del linguaggio seleziona C# 8.0, ma non abilita il contesto delle annotazioni
nullable e il contesto degli avvisi nullable. Ricompilare il progetto per assicurarsi che venga compilato senza
avvisi.
Un buon passo successivo è attivare il contesto delle annotazioni nullable e vedere quanti avvisi vengono
generati. Aggiungere l'elemento seguente sia ai file csproj nella soluzione, che direttamente nell'elemento
LangVersion :

<Nullable>enable</Nullable>

Eseguire una compilazione di prova ed esaminare l'elenco degli avvisi. In questa piccola applicazione, il
compilatore genera cinque avvisi, dunque è probabile che in un caso come questo si lasci abilitato il contesto
delle annotazioni nullable e si inizi a risolvere gli avvisi per l'intero progetto.
Questa strategia funziona solo per i progetti più piccoli. Per qualsiasi progetto di dimensioni più grandi, il
numero di avvisi generati abilitando il contesto delle annotazioni nullable per l'intera base di codice rende più
difficile risolvere gli avvisi in modo sistematico. Per progetti aziendali più grandi, è spesso opportuno eseguire la
migrazione di un progetto alla volta. In ogni progetto, eseguire la migrazione di una classe o un file alla volta.

Gli avvisi sono utili per individuare le finalità di progettazione originali


Esistono due classi che generano più avvisi. Prima di tutto la classe NewsStoryViewModel . Rimuovere l'elemento
Nullable da entrambi i file csproj in modo da poter limitare l'ambito degli avvisi alle sezioni del codice su cui si
sta lavorando. Aprire il file NewsStoryViewModel.cs e aggiungere le direttive seguenti per abilitare il contesto
delle annotazioni nullable per NewsStoryViewModel e ripristinarlo seguendo tale definizione di classe:

#nullable enable
public class NewsStoryViewModel
{
public DateTimeOffset Published { get; set; }
public string Title { get; set; }
public string Uri { get; set; }
}
#nullable restore

Queste due direttive consentono di focalizzare le operazioni di migrazione. Gli avvisi nullable vengono generati
per l'area del codice su cui si sta lavorando attivamente. Verranno lasciati attivi fino a quando non si è pronti per
attivare gli avvisi per l'intero progetto. È consigliabile usare il valore restore invece di disable , in modo da
evitare di disabilitare inavvertitamente il contesto in un secondo momento quando si attivano le annotazioni
nullable per l'intero progetto. Dopo avere attivato il contesto delle annotazioni nullable per l'intero progetto, è
possibile rimuovere tutti i pragma #nullable da tale progetto.
La classe NewsStoryViewModel è un oggetto di trasferimento dei dati (DTO) e due delle proprietà sono stringhe di
lettura/scrittura:

public class NewsStoryViewModel


{
public DateTimeOffset Published { get; set; }
public string Title { get; set; }
public string Uri { get; set; }
}
Queste due proprietà causano l'avviso CS8618 "Non-nullable property is uninitialized" (Proprietà non nullable
non inizializzata). Il messaggio è chiaro: entrambe le proprietà string hanno il valore predefinito null quando
viene costruito un NewsStoryViewModel . Quello che è importante scoprire è come vengono costruiti gli oggetti
NewsStoryViewModel . Esaminando questa classe non è possibile stabilire se il valore null fa parte della
progettazione o se questi oggetti sono impostati su valori non Null ogni volta che ne viene creato uno. Le storie
delle notizie vengono create nel metodo GetNews della classe NewsService :

ISyndicationItem item = await feedReader.ReadItem();


var newsStory = _mapper.Map<NewsStoryViewModel>(item);
news.Add(newsStory);

Sono tanti gli aspetti interessanti nel blocco di codice precedente. Questa applicazione usa il pacchetto NuGet
AutoMapper per costruire un elemento di notizie da un ISyndicationItem . Si è scoperto che gli elementi della
storia delle notizie vengono costruiti e le proprietà vengono impostate in tale singola istruzione. Questo significa
che la progettazione per NewsStoryViewModel indica che queste proprietà non devono mai avere il valore null .
Queste proprietà devono essere tipi riferimento non nullable . Questa è la migliore espressione della finalità
di progettazione originale. In effetti, per qualsiasi NewsStoryViewModel viene correttamente creata un'istanza con
valori non Null. Il codice di inizializzazione seguente rappresenta quindi una correzione valida:

public class NewsStoryViewModel


{
public DateTimeOffset Published { get; set; }
public string Title { get; set; } = default!;
public string Uri { get; set; } = default!;
}

L'assegnazione di Title e Uri a default , ovvero null per il tipo string , non cambia il comportamento di
runtime del programma. NewsStoryViewModel viene ancora costruito con valori Null, ma ora il compilatore non
segnala alcun avviso. L'operatore per la tolleranza per i valori Null , ovvero il carattere ! dopo
l'espressione default , indica al compilatore che l'espressione precedente non è Null. Questa tecnica può essere
utile quando altre modifiche forzano modifiche molto più grandi a una base di codice, ma in questa applicazione
esiste una soluzione relativamente rapida e migliore: rendere il NewsStoryViewModel tipo non modificabile in cui
tutte le proprietà sono impostate nel costruttore. Modificare NewsStoryViewModel nel modo seguente:

#nullable enable
public class NewsStoryViewModel
{
public NewsStoryViewModel(DateTimeOffset published, string title, string uri) =>
(Published, Title, Uri) = (published, title, uri);

public DateTimeOffset Published { get; }


public string Title { get; }
public string Uri { get; }
}
#nullable restore

Al termine, è necessario aggiornare il codice che configura AutoMapper in modo che usi il costruttore anziché
impostare proprietà. Aprire NewsService.cs e cercare il codice seguente nella parte inferiore del file:
public class NewsStoryProfile : Profile
{
public NewsStoryProfile()
{
// Create the AutoMapper mapping profile between the 2 objects.
// ISyndicationItem.Id maps to NewsStoryViewModel.Uri.
CreateMap<ISyndicationItem, NewsStoryViewModel>()
.ForMember(dest => dest.Uri, opts => opts.MapFrom(src => src.Id));
}
}

Tale codice esegue il mapping delle proprietà dell'oggetto ISyndicationItem con le proprietà
NewsStoryViewModel . Si vuole invece che AutoMapper fornisca il mapping usando un costruttore. Sostituire il
codice precedente con la configurazione di AutoMapper seguente:

#nullable enable
public class NewsStoryProfile : Profile
{
public NewsStoryProfile()
{
// Create the AutoMapper mapping profile between the 2 objects.
// ISyndicationItem.Id maps to NewsStoryViewModel.Uri.
CreateMap<ISyndicationItem, NewsStoryViewModel>()
.ForCtorParam("uri", opt => opt.MapFrom(src => src.Id));
}

Si noti che poiché questa classe è piccola e il codice è stato esaminato con attenzione, è necessario attivare la
direttiva #nullable enable sopra questa dichiarazione di classe. La modifica al costruttore potrebbe aver
introdotto errori, quindi è opportuno eseguire tutti i test e verificare l'applicazione prima di procedere.
Il primo set di modifiche ha illustrato come scoprire quando il progetto originale indica che le variabili non
devono essere impostate su null . Questa tecnica è nota come corretta per costruzione . Si dichiara che un
oggetto e le relative proprietà non possono essere null quando vengono costruiti. L'analisi di flusso del
compilatore garantisce che tali proprietà non vengano impostate su null dopo la costruzione. Si noti che
questo costruttore viene chiamato da codice esterno e che tale codice è incurante dei valori Null . La nuova
sintassi non prevede controlli di runtime. Il codice esterno potrebbe aggirare l'analisi di flusso del compilatore.
In altri casi, la struttura di una classe offre diverse indicazioni della finalità. Aprire il file Error.cshtml.cs nella
cartella Pages. ErrorViewModel contiene il codice seguente:

public class ErrorModel : PageModel


{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public void OnGet()


{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

Aggiungere la direttiva #nullable enable prima della dichiarazione di classe e una direttiva #nullable restore
dopo la dichiarazione. Si riceverà un avviso che RequestId non è inizializzato. Osservando la classe, è necessario
decidere che la proprietà RequestId deve essere Null in alcuni casi. L'esistenza della proprietà ShowRequestId
indica che sono possibili valori mancanti. Dato che null è valido, aggiungere ? al tipo string per indicare
che la proprietà RequestId è un tipo riferimento nullable. La classe finale è simile all'esempio seguente:
#nullable enable
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public void OnGet()


{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
#nullable restore

Controllando gli usi della proprietà si noterà che nella pagina associata è previsto il controllo del valore Null per
la proprietà prima di eseguirne il rendering nel markup. Questo è un uso sicuro di un tipo riferimento nullable,
quindi è tutto per questa classe.

La correzione dei valori Null causa modifiche


Spesso, la correzione per un set di avvisi crea nuovi avvisi nel codice correlato. È possibile visualizzare gli avvisi
in azione correggendo la classe index.cshtml.cs . Aprire il file index.cshtml.cs ed esaminare il codice. Questo
file contiene il code-behind per la pagina di indice:
public class IndexModel : PageModel
{
private readonly NewsService _newsService;

public IndexModel(NewsService newsService)


{
_newsService = newsService;
}

public string ErrorText { get; private set; }

public List<NewsStoryViewModel> NewsItems { get; private set; }

public async Task OnGet()


{
string feedUrl = Request.Query["feedurl"];

if (!string.IsNullOrEmpty(feedUrl))
{
try
{
NewsItems = await _newsService.GetNews(feedUrl);
}
catch (UriFormatException)
{
ErrorText = "There was a problem parsing the URL.";
return;
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
ErrorText = "Unknown host name.";
return;
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.ProtocolError)
{
ErrorText = "Syndication feed not found.";
return;
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is XmlException)
{
ErrorText = "There was a problem parsing the feed. Are you sure that URL is a
syndication feed?";
return true;
}
return false;
});
}
}
}
}

Aggiungere la direttiva #nullable enable . Verranno visualizzati due avvisi. Entrambe le proprietà ErrorText e
NewsItems sono inizializzate. Un esame di questa classe porterebbe a credere che entrambe le proprietà devono
essere tipi di riferimento nullable: entrambi hanno setter privati. Una sola viene assegnata nel metodo OnGet .
Prima di apportare modifiche, esaminare i consumer di entrambe le proprietà. Nella pagina stessa, viene
eseguito un controllo del valore Null per ErrorText prima della generazione del markup per eventuali errori.
Viene eseguito un controllo di null per la raccolta NewsItems , che viene anche controllata per assicurarsi che
contenga elementi. Una correzione rapida potrebbe consistere nell'impostare entrambe le proprietà come tipi
riferimento nullable. Una correzione migliore sarebbe impostare la raccolta come tipo riferimento non nullable e
aggiungere elementi alla raccolta esistente durante il recupero delle notizie. La prima correzione prevede
l'aggiunta di ? al tipo string per ErrorText :

public string? ErrorText { get; private set; }

Tale modifica non verrà propagata in altro codice, perché qualsiasi accesso alla proprietà ErrorText è già
protetto dai controlli Null. Inizializzare quindi l'elenco NewsItems e rimuovere il setter della proprietà,
rendendola una proprietà di sola lettura:

public List<NewsStoryViewModel> NewsItems { get; } = new List<NewsStoryViewModel>();

In questo modo è stato risolto l'avviso, ma è stato introdotto un errore. L' NewsItems elenco è ora corretto per
costruzione , ma il codice che imposta l'elenco in OnGet deve essere modificato in modo che corrisponda alla
nuova API. Invece di usare un'assegnazione, chiamare AddRange per aggiungere gli elementi delle notizie
all'elenco esistente:

NewsItems.AddRange(await _newsService.GetNews(feedUrl));

L'uso di AddRange al posto di un'assegnazione significa che il metodo GetNews può restituire IEnumerable
invece di List . Si risparmia così un'allocazione. Modificare la firma del metodo e rimuovere la chiamata
ToList , come illustrato nell'esempio di codice seguente:

public async Task<IEnumerable<NewsStoryViewModel>> GetNews(string feedUrl)


{
var news = new List<NewsStoryViewModel>();
var feedUri = new Uri(feedUrl);

using (var xmlReader = XmlReader.Create(feedUri.ToString(),


new XmlReaderSettings { Async = true }))
{
try
{
var feedReader = new RssFeedReader(xmlReader);

while (await feedReader.Read())


{
switch (feedReader.ElementType)
{
// RSS Item
case SyndicationElementType.Item:
ISyndicationItem item = await feedReader.ReadItem();
var newsStory = _mapper.Map<NewsStoryViewModel>(item);
news.Add(newsStory);
break;

// Something else
default:
break;
}
}
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}

return news.OrderByDescending(story => story.Published);


}
Anche la modifica della firma causa errori per uno dei test. Aprire il file NewsServiceTests.cs nella cartella
Services del progetto SimpleFeedReader.Tests . Passare al test Returns_News_Stories_Given_Valid_Uri e
modificare il tipo della variabile result in IEnumerable<NewsItem> . La modifica del tipo significa che la proprietà
Count non è più disponibile, quindi sostituire la proprietà Count in Assert con una chiamata a Any() :

// Act
IEnumerable<NewsStoryViewModel> result =
await _newsService.GetNews(feedUrl);

// Assert
Assert.True(result.Any());

Sarà anche necessario aggiungere un'istruzione using System.Linq all'inizio del file.
Questo set di modifiche evidenzia quanto sia necessaria un'attenzione speciale quando si aggiorna codice che
include creazioni di istanze generiche. Sia l'elenco che gli elementi nell'elenco di tipi non nullable. Uno o
entrambi potrebbero essere tipi nullable. Tutte le dichiarazioni seguenti sono consentite:
List<NewsStoryViewModel> : elenco non nullable di modelli di visualizzazione non nullable.
List<NewsStoryViewModel?> : elenco non nullable di modelli di visualizzazione nullable.
List<NewsStoryViewModel>? : elenco nullable di modelli di visualizzazione non nullable.
List<NewsStoryViewModel?>? : elenco nullable di modelli di visualizzazione nullable.

Interfacce con codice esterno


Sono state apportate modifiche alla classe NewsService , quindi attivare l'annotazione #nullable enable per tale
classe. Non verranno generati nuovi avvisi. Tuttavia, un attento esame della classe è utile per illustrare alcune
delle limitazioni dell'analisi di flusso del compilatore. Esaminare il costruttore:

public NewsService(IMapper mapper)


{
_mapper = mapper;
}

Il parametro IMapper è tipizzato come riferimento nullable. Viene chiamato dal codice dell'infrastruttura di
ASP.NET Core, quindi il compilatore non sa che IMapper non sarà mai Null. Il contenitore di inserimento delle
dipendenze di ASP.NET Core predefinito genera un'eccezione se non riesce a risolvere un servizio necessario,
quindi il codice è corretto. Il compilatore non può convalidare tutte le chiamate alle API pubbliche, anche se il
codice viene compilato con i contesti delle annotazioni nullable abilitati. Inoltre, le librerie potrebbero essere
utilizzate da progetti per i quali non è ancora stato il consenso esplicito all'uso dei tipi riferimento nullable.
Convalidare gli input per le API pubbliche, anche se sono stati dichiarati come tipi nullable.

Ottenere il codice
Gli avvisi identificati nella compilazione di prova iniziale sono stati corretti, quindi è ora possibile attivare il
contesto delle annotazioni nullable per entrambi i progetti. Ricompilare i progetti. Il compilatore non segnala
alcun avviso. È possibile ottenere il codice per il progetto completato nel repository GitHub dotnet/samples.
Le nuove funzionalità che supportano i tipi riferimento nullable consentono di trovare e correggere eventuali
errori nel modo in cui si gestiscono i valori null nel codice. L'abilitazione del contesto delle annotazioni
nullable consente di esprimere le finalità di progettazione: alcune variabili non devono mai essere Null e altre
variabili possono contenere valori Null. Queste funzionalità rendono più semplice dichiarare le finalità della
progettazione. Analogamente, il contesto degli avvisi nullable indica al compilatore di generare avvisi quando
vengono violate le finalità. Questi avvisi sono utili per segnalare gli aggiornamenti che rendono il codice più
resiliente e meno soggetto a generare eccezioni di tipo NullReferenceException durante l'esecuzione. È possibile
controllare l'ambito di questi contesti in modo da potersi concentrare sulle aree locali del codice di cui eseguire
la migrazione, mentre la base di codice rimanente rimane invariata. In pratica, è possibile eseguire questa attività
di migrazione come parte delle normali attività di manutenzione delle classi. Questa esercitazione ha illustrato il
processo per eseguire la migrazione di un'applicazione per l'uso dei tipi riferimento nullable. È possibile
esplorare un esempio più esaustivo reale di questo processo, esaminando la richiesta pull effettuata da Jon
Skeet per incorporare i tipi riferimento nullable in NodaTime. In alternativa, è possibile apprendere tecniche per
l'utilizzo di tipi di riferimento nullable con Entity Framework Core in Entity Framework Core - Utilizzo di tipi di
riferimento nullable.
Esercitazione: generare e utilizzare flussi asincroni
con C# 8,0 e .NET Core 3,0
20/05/2020 • 16 minutes to read • Edit Online

C# 8,0 introduce flussi asincroni , che modellano un'origine di flusso di dati. I flussi di dati spesso recuperano o
generano elementi in modo asincrono. I flussi asincroni si basano sulle nuove interfacce introdotte in .NET
Standard 2,1. Queste interfacce sono supportate in .NET Core 3,0 e versioni successive. Forniscono un modello
di programmazione naturale per le origini dati del flusso asincrono.
In questa esercitazione si apprenderà come:
Creare un'origine dati che genera una sequenza di elementi di dati in modo asincrono.
Utilizzare tale origine dati in modo asincrono.
Supporta l'annullamento e i contesti acquisiti per i flussi asincroni.
Riconoscere quando la nuova interfaccia e l'origine dati sono da preferire rispetto alle sequenze di dati
sincrone precedenti.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET Core, incluso il compilatore C# 8,0. Il compilatore
C# 8 è disponibile a partire da Visual Studio 2019 versione 16,3 o .NET Core 3,0 SDK.
È necessario creare un token di accesso di GitHub per poter accedere all'endpoint GraphQL di GitHub.
Selezionare le autorizzazioni seguenti per il token di accesso di GitHub:
repo:status
public_repo
Salvare il token di accesso in un luogo sicuro in modo da poterlo usare per ottenere l'accesso all'endpoint
dell'API GitHub.

WARNING
Mantenere protetto il token di accesso personale. Qualsiasi software con il token di accesso personale può effettuare
chiamate API GitHub tramite i diritti di accesso.

Per questa esercitazione si presuppone che l'utente abbia familiarità con C# e .NET, inclusa l'interfaccia della riga
di comando di .NET Core o Visual Studio.

Eseguire l'applicazione iniziale


È possibile ottenere il codice per l'applicazione iniziale usata in questa esercitazione dal repository DotNet/docs
nella cartella CSharp/Tutorials/AsyncStreams .
L'applicazione iniziale è un'applicazione console che usa l'interfaccia GraphQL di GitHub per recuperare i
problemi recenti scritti nel repository dotnet/docs. Per iniziare, esaminare il codice seguente per il metodo Main
dell'app iniziale:
static async Task Main(string[] args)
{
//Follow these steps to create a GitHub Access Token
// https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-
token
//Select the following permissions for your GitHub Access Token:
// - repo:status
// - public_repo
// Replace the 3rd parameter to the following code with your GitHub access token.
var key = GetEnvVariable("GitHubKey",
"You must store your GitHub key in the 'GitHubKey' environment variable",
"");

var client = new GitHubClient(new Octokit.ProductHeaderValue("IssueQueryDemo"))


{
Credentials = new Octokit.Credentials(key)
};

var progressReporter = new progressStatus((num) =>


{
Console.WriteLine($"Received {num} issues in total");
});
CancellationTokenSource cancellationSource = new CancellationTokenSource();

try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
}

È possibile impostare una variabile di ambiente GitHubKey per il token di accesso personale oppure è possibile
sostituire l'ultimo argomento nella chiamata a GenEnvVariable con il token di accesso personale. Non inserire il
codice di accesso nel codice sorgente se si condividerà l'origine con altri utenti. Non caricare mai i codici di
accesso in un repository di origine condivisa.
Dopo aver creato il client di GitHub, il codice in Main crea un oggetto di segnalazione dello stato e un token di
annullamento. Dopo aver creato questi oggetti, Main chiama runPagedQueryAsync per recuperare i 250
problemi creati più di recente. Al termine di tale attività, vengono visualizzati i risultati.
Quando si esegue l'applicazione iniziale, è possibile notare alcuni aspetti importanti della modalità di esecuzione
di questa applicazione. Lo stato viene segnalato per ogni pagina restituita da GitHub. È possibile notare una
notevole pausa prima che GitHub restituisca ogni nuova pagina di problemi. Infine, i problemi vengono
visualizzati solo dopo aver recuperato tutte e 10 le pagine da GitHub.

Esaminare l'implementazione
L'implementazione spiega il comportamento evidenziato nella sezione precedente. Esaminare il codice per
runPagedQueryAsync :
private static async Task<JArray> runPagedQueryAsync(GitHubClient client, string queryText, string repoName,
CancellationToken cancel, IProgress<int> progress)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;

JArray finalResults = new JArray();


bool hasMorePages = true;
int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results = JObject.Parse(response.HttpResponse.Body.ToString());

int totalCount = (int)issues(results)["totalCount"];


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"];
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)["startCursor"].ToString();
issuesReturned += issues(results)["nodes"].Count();
finalResults.Merge(issues(results)["nodes"]);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
}
return finalResults;

JObject issues(JObject result) => (JObject)result["data"]["repository"]["issues"];


JObject pageInfo(JObject result) => (JObject)issues(result)["pageInfo"];
}

Concentrarsi sull'algoritmo per la suddivisione in pagine e sulla struttura asincrona del codice precedente. È
possibile consultare la documentazione di GitHub GraphQL per informazioni dettagliate sull'API GraphQL di
GitHub. Il runPagedQueryAsync metodo enumera i problemi dalla più recente alla meno recente. Richiede 25
problemi per ogni pagina ed esamina la struttura pageInfo della risposta per continuare con la pagina
precedente. Viene rispettato il supporto della suddivisione in pagine standard di GraphQL per le risposte a più
pagine. La risposta include un oggetto pageInfo che include un valore hasPreviousPages e un valore
startCursor usato per richiedere la pagina precedente. I problemi sono nella matrice nodes . Il metodo
runPagedQueryAsync aggiunge questi nodi in una matrice che contiene tutti i risultati da tutte le pagine.

Dopo il recupero e il ripristino di una pagina di risultati, runPagedQueryAsync segnala lo stato e verifica se è
presente una richiesta di annullamento. In caso affermativo, runPagedQueryAsync genera un'eccezione
OperationCanceledException.
Esistono diversi elementi in questo codice che possono essere migliorati. Soprattutto, runPagedQueryAsync deve
allocare spazio di archiviazione per tutti i problemi restituiti. Questo esempio si arresta dopo 250 problemi,
perché il recupero di tutti i problemi richiede molta più memoria per archiviare tutti i problemi recuperati. I
protocolli per supportare i report sullo stato di avanzamento e l'annullamento rendono più difficile la
comprensione dell'algoritmo durante la prima lettura. Sono necessari più tipi e API. È necessario tenere traccia
delle comunicazioni tramite CancellationTokenSource e il relativo oggetto associato CancellationToken per
comprendere dove viene richiesto l'annullamento e dove viene concesso.

I flussi asincroni sono più efficaci


I flussi asincroni e il supporto del linguaggio associato offrono una risposta a tutte queste problematiche. Il
codice che genera la sequenza può ora usare yield return per restituire gli elementi in un metodo dichiarato
con il modificatore async . È possibile utilizzare un flusso asincrono con un ciclo await foreach , proprio come si
utilizza qualsiasi sequenza con un ciclo foreach .
Queste nuove funzionalità del linguaggio dipendono da tre nuove interfacce aggiunte a .NET 2.1 Standard e
implementate in .NET Core 3.0:
System.Collections.Generic.IAsyncEnumerable<T>
System.Collections.Generic.IAsyncEnumerator<T>
System.IAsyncDisposable
Queste tre interfacce dovrebbero risultare familiari alla maggior parte degli sviluppatori C#. Il comportamento è
simile a quello delle relative controparti sincrone:
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
System.IDisposable
Un tipo che potrebbe essere poco noto è System.Threading.Tasks.ValueTask. Lo struct ValueTask fornisce un'API
simile alla classe System.Threading.Tasks.Task. ValueTask viene usato in queste interfacce per motivi di
prestazioni.

Convertire in flussi asincroni


A questo punto, il metodo runPagedQueryAsync verrà convertito per generare un flusso asincrono. In primo
luogo, modificare la firma di runPagedQueryAsync per restituire IAsyncEnumerable<JToken> e rimuovere il token di
annullamento e gli oggetti di stato dall'elenco di parametri, come illustrato nel codice seguente:

private static async IAsyncEnumerable<JToken> runPagedQueryAsync(GitHubClient client,


string queryText, string repoName)

Il codice iniziale elabora ogni pagina quando viene recuperata, come illustrato nel codice seguente:

finalResults.Merge(issues(results)["nodes"]);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();

Sostituire queste tre righe con il codice seguente:

foreach (JObject issue in issues(results)["nodes"])


yield return issue;

È anche possibile rimuovere la dichiarazione di finalResults più indietro in questo metodo e l'istruzione
return che segue il ciclo modificato.

Sono state completate le modifiche per generare un flusso asincrono. Il metodo finito dovrebbe essere simile al
codice seguente:
private static async IAsyncEnumerable<JToken> runPagedQueryAsync(GitHubClient client,
string queryText, string repoName)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;

bool hasMorePages = true;


int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results = JObject.Parse(response.HttpResponse.Body.ToString());

int totalCount = (int)issues(results)["totalCount"];


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"];
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)["startCursor"].ToString();
issuesReturned += issues(results)["nodes"].Count();

foreach (JObject issue in issues(results)["nodes"])


yield return issue;
}

JObject issues(JObject result) => (JObject)result["data"]["repository"]["issues"];


JObject pageInfo(JObject result) => (JObject)issues(result)["pageInfo"];
}

Verrà ora modificato il codice che utilizza la raccolta per utilizzare il flusso asincrono. Trovare il codice seguente
in Main che elabora la raccolta dei problemi:

var progressReporter = new progressStatus((num) =>


{
Console.WriteLine($"Received {num} issues in total");
});
CancellationTokenSource cancellationSource = new CancellationTokenSource();

try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}

Sostituire il codice con il ciclo await foreach seguente:


int num = 0;
await foreach (var issue in runPagedQueryAsync(client, PagedIssueQuery, "docs"))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}

La nuova interfaccia IAsyncEnumerator<T> deriva da IAsyncDisposable . Ciò significa che il ciclo precedente
eliminerà in modo asincrono il flusso al termine del ciclo. Si supponga che il ciclo appaia come il codice
seguente:

int num = 0;
var enumerator = runPagedQueryAsync(client, PagedIssueQuery, "docs").GetEnumeratorAsync();
try
{
while (await enumerator.MoveNextAsync())
{
var issue = enumerator.Current;
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
} finally
{
if (enumerator != null)
await enumerator.DisposeAsync();
}

Per impostazione predefinita, gli elementi del flusso vengono elaborati nel contesto acquisito. Se si desidera
disabilitare l'acquisizione del contesto, utilizzare il TaskAsyncEnumerableExtensions.ConfigureAwait metodo di
estensione. Per ulteriori informazioni sui contesti di sincronizzazione e sull'acquisizione del contesto corrente,
vedere l'articolo sull' utilizzo del modello asincrono basato su attività.
I flussi asincroni supportano l'annullamento utilizzando lo stesso protocollo di altri async metodi. Per
supportare l'annullamento, modificare la firma per il metodo iteratore asincrono come indicato di seguito:
private static async IAsyncEnumerable<JToken> runPagedQueryAsync(GitHubClient client,
string queryText, string repoName, [EnumeratorCancellation] CancellationToken cancellationToken =
default)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;

bool hasMorePages = true;


int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results = JObject.Parse(response.HttpResponse.Body.ToString());

int totalCount = (int)issues(results)["totalCount"];


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"];
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)["startCursor"].ToString();
issuesReturned += issues(results)["nodes"].Count();

foreach (JObject issue in issues(results)["nodes"])


yield return issue;
}

JObject issues(JObject result) => (JObject)result["data"]["repository"]["issues"];


JObject pageInfo(JObject result) => (JObject)issues(result)["pageInfo"];
}

L' EnumeratorCancellationAttribute attributo induce il compilatore a generare il codice per il


IAsyncEnumerator<T> che rende il token passato a GetAsyncEnumerator Visible al corpo dell'iteratore asincrono
come tale argomento. All'interno runQueryAsync di, è possibile esaminare lo stato del token e annullare ulteriori
operazioni, se richiesto.
Usare un altro metodo di estensione, WithCancellation , per passare il token di annullamento al flusso asincrono.
Modificare il ciclo enumerando i problemi come indicato di seguito:

private static async Task EnumerateWithCancellation(GitHubClient client)


{
int num = 0;
var cancellation = new CancellationTokenSource();
await foreach (var issue in runPagedQueryAsync(client, PagedIssueQuery, "docs")
.WithCancellation(cancellation.Token))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
}

È possibile ottenere il codice per l'esercitazione completata dal repository DotNet/docs nella cartella
CSharp/Tutorials/AsyncStreams .

Eseguire l'applicazione completata


Eseguire di nuovo l'applicazione. Confrontare il comportamento con il comportamento dell'applicazione iniziale.
La prima pagina di risultati viene enumerata non appena è disponibile. Esiste una pausa osservabile quando
viene richiesta e recuperata ogni nuova pagina, poi i risultati della pagina successiva vengono enumerati
rapidamente. Il blocco try / catch non è necessario per gestire l'annullamento: il chiamante può interrompere
l'enumerazione della raccolta. Lo stato viene segnalato in modo chiaro perché il flusso asincrono genera i
risultati quando viene scaricata ogni pagina. Lo stato di ogni problema restituito è facilmente incluso nel
await foreach ciclo. Non è necessario un oggetto callback per tenere traccia dello stato di avanzamento.

È possibile notare miglioramenti per l'uso della memoria esaminando il codice. Non è più necessario allocare
una raccolta per archiviare tutti i risultati prima che vengano enumerati. Il chiamante può determinare come
utilizzare i risultati e se è necessaria una raccolta di archiviazione.
Eseguire sia l'applicazione iniziale che quella finita per osservare in autonomia le differenze tra le
implementazioni. Al termine, è possibile eliminare il token di accesso di GitHub creato all'inizio di questa
esercitazione. Se un utente malintenzionato riesce ad accedere al token, potrebbe ottenere l'accesso alle a API di
GitHub usando le credenziali.
Esercitazione: usare i criteri di ricerca per compilare
algoritmi basati sui tipi e basati sui dati.
28/01/2021 • 27 minutes to read • Edit Online

In C# 7 sono state introdotte le funzionalità dei criteri di ricerca di base. Queste funzionalità sono estese in C# 8
e C# 9 con le nuove espressioni e i modelli. È possibile scrivere funzionalità che si comportano come se si
estendessero tipi che potrebbero essere in altre librerie. I criteri possono essere usati anche per creare
funzionalità necessarie per l'applicazione che non sono funzioni fondamentali del tipo da estendere.
In questa esercitazione si apprenderà come:
Riconoscere le situazioni in cui usare i criteri di ricerca.
Usare le espressioni dei criteri di ricerca per implementare il comportamento in base ai tipi e ai valori delle
proprietà.
Combinare i criteri di ricerca con altre tecniche per creare algoritmi completi.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET 5, che include il compilatore C# 9. Il compilatore
C# 9 è disponibile a partire da Visual Studio 2019 versione 16,9 Preview 1 o .NET 5,0 SDK.
Per questa esercitazione si presuppone che l'utente abbia familiarità con C# e .NET, inclusa l'interfaccia della riga
di comando di .NET Core o Visual Studio.

Scenari per i criteri di ricerca


Lo sviluppo moderno spesso include l'integrazione dei dati da più origini e la presentazione di informazioni e
approfondimenti da tali dati in una singola applicazione coerente. L'utente e il team non avranno il controllo o
l'accesso per tutti i tipi che rappresentano i dati in ingresso.
La classica progettazione orientata agli oggetti eseguirebbe una chiamata per la creazione di tipi
nell'applicazione che rappresentano ogni tipo di dati dalle diverse origini dati. L'applicazione userebbe quindi i
nuovi tipi, compilerebbe gerarchie di ereditarietà, creerebbe metodi virtuali e implementerebbe astrazioni.
Queste tecniche funzionano e in alcuni casi sono i migliori strumenti. In altri casi è possibile scrivere meno
codice. È possibile scrivere codice più chiaro usando tecniche che separano i dati dalle operazioni che
modificano i dati.
In questa esercitazione si creerà e si esplorerà un'applicazione che accetta i dati in ingresso da più origini esterne
per un singolo scenario. Verrà spiegato come i criteri di ricerca offrano un modo efficiente per utilizzare ed
elaborare tali dati in modi non contemplati nel sistema originale.
Si prenda come esempio un'importante area metropolitana che gestisce il traffico applicando pedaggi e tariffe
per le ore di punta. Si scrive un'applicazione che calcola i pedaggi in base al tipo di veicolo. I miglioramenti
successivi incorporano i prezzi in base al numero di passeggeri del veicolo. Ulteriori miglioramenti aggiungono i
prezzi in base all'ora e al giorno della settimana.
In base a questa breve descrizione, potrebbe essere stata rapidamente delineata una gerarchia di oggetti per
modellare questo sistema. I dati provengono tuttavia da più origini, ad esempio altri sistemi di gestione di
registrazione dei veicoli. Questi sistemi forniscono classi differenti per modellare i dati e non esiste un unico
modello a oggetti che è possibile usare. Nell'esercitazione si useranno queste classi semplificate per la
modellazione dei dati dei veicoli dai sistemi esterni, come illustrato nel codice seguente:
namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}

namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}

namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}

public class Bus


{
public int Capacity { get; set; }
public int Riders { get; set; }
}
}

È possibile scaricare il codice iniziale dal repository GitHub dotnet/samples. È possibile osservare che le classi
dei veicoli provengono da sistemi diversi e sono in spazi dei nomi diversi. Non è possibile sfruttare classi di base
comuni, tranne System.Object .

Progettazioni di criteri di ricerca


Lo scenario usato in questa esercitazione evidenzia i tipi di problemi che i criteri di ricerca sono particolarmente
adatti per la risoluzione:
Gli oggetti da usare non sono in una gerarchia di oggetti che corrisponde ai propri obiettivi. È possibile che si
stiano usando classi che fanno parte di sistemi non correlati.
Le funzionalità che si stanno aggiungendo non fanno parte dell'astrazione fondamentale per queste classi. Il
pedaggio pagato da un veicolo cambia per i diversi tipi di veicoli, ma il pedaggio non è una funzione
fondamentale del veicolo.
Quando la forma dei dati e le operazioni su tali dati non sono descritte insieme, le funzionalità dei criteri di
ricerca in C# ne semplificano l'uso.

Implementare i calcoli di base per i pedaggi


Il calcolo dei pedaggi più semplice si basa solo sul tipo di veicolo:
Un veicolo Car paga $ 2,00.
Un veicolo Taxi paga $ 3,50.
Un veicolo Bus paga $ 5,00.
Un veicolo DeliveryTruck paga $ 10,00

Creare una nuova classe TollCalculator e implementare i criteri di ricerca per il tipo di veicolo per ottenere
l'ammontare dei pedaggi. Nel codice seguente viene illustrata l'implementazione di TollCalculator .
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace toll_calculator
{
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName:
nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
}

Il codice precedente usa un'espressione switch (diversa da un'istruzione switch ) che testa il criterio del
tipo . Un'espressione switch inizia con la variabile, vehicle nel codice precedente, seguita dalla parola chiave
switch . Seguono quindi tutti gli elementi switch tra parentesi graffe. L'espressione switch perfeziona
ulteriormente la sintassi che racchiude l'istruzione switch . La parola chiave case viene omessa e il risultato di
ogni elemento è un'espressione. Gli ultimi due elementi mostrano una nuova funzionalità del linguaggio. Il case
{ } corrisponde a eventuali oggetti non Null che non corrispondevano a un elemento precedente. Questo
elemento rileva eventuali tipi non corretti passati a questo metodo. Il case { } deve seguire i case per ogni tipo
di veicolo. Se l'ordine è stato invertito, il case { } deve avere la precedenza. Il criterio null infine rileva
quando null viene passato a questo metodo. Il criterio null può essere l'ultimo perché gli altri criteri dei tipi
corrispondono solo a un oggetto non Null del tipo corretto.
Per testare questo codice, usare il codice seguente in Program.cs :
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace toll_calculator
{
class Program
{
static void Main(string[] args)
{
var tollCalc = new TollCalculator();

var car = new Car();


var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();

Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");


Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}");

try
{
tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
Console.WriteLine("Caught an argument exception when using the wrong type");
}
try
{
tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
Console.WriteLine("Caught an argument exception when using null");
}
}
}
}

Il codice è incluso nel progetto iniziale, ma è impostato come commento. Rimuovere i commenti ed è possibile
testare quello che è stato scritto.
Si può già osservare come i criteri consentano di creare algoritmi in cui il codice e i dati sono separati.
L'espressione switch testa il tipo e genera valori diversi in base ai risultati. Si tratta solo dell'inizio.

Aggiungere i prezzi in base al numero degli occupanti


L'autorità di regolazione dei pedaggi vuole incoraggiare i conducenti a viaggiare al massimo della capacità. Si è
deciso di far pagare di più i veicoli con un minor numero di passeggeri e di agevolare i veicoli che viaggiano al
completo, offrendo prezzi più bassi:
Automobili e taxi senza passeggeri pagano un extra di $ 0,50.
Automobili e taxi con due passeggeri usufruiscono di uno sconto di $ 0,50.
Automobili e taxi con tre o più passeggeri usufruiscono di uno sconto di $ 1,00.
Gli autobus con meno del 50% dei posti occupati pagano un extra di $ 2,00.
Gli autobus con più del 90% dei posti occupati usufruiscono di uno sconto di $ 1,00.
Queste regole possono essere implementate usando il criterio di proprietà nella stessa espressione switch.
Un modello di proprietà è una when clausola che confronta un valore della proprietà con un valore costante.
Dopo aver determinato il tipo, il criterio di proprietà esamina le proprietà dell'oggetto. Il case singolo di Car si
espande a quattro case diversi:

vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car c => 2.00m - 1.0m,

// ...
};

I primi tre case testano il tipo come Car , quindi controllano il valore della proprietà Passengers . Se entrambi
corrispondono, l'espressione viene valutata e restituita.
Si espanderanno in modo analogo anche i case dei taxi:

vehicle switch
{
// ...

Taxi {Fares: 0} => 3.50m + 1.00m,


Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi t => 3.50m - 1.00m,

// ...
};

Nell'esempio precedente la clausola when è stata omessa nel case finale.


Successivamente, implementare le regole per il numero di occupanti espandendo i case per gli autobus, come
illustrato nell'esempio seguente:

vehicle switch
{
// ...

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,


Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus b => 5.00m,

// ...
};

L'autorità di regolazione dei pedaggi non considera il numero di passeggeri dei furgoni, L'ammontare dei
pedaggi viene invece calcolato sulla base della classe di peso dei furgoni, come indicato di seguito:
I furgoni oltre le 5000 libbre (2268 kg) pagano un extra di $ 5,00.
I furgoni leggeri, sotto le 3000 libbre (1360 kg), usufruiscono di uno sconto di $ 2,00.
Tale regola viene implementata con il codice seguente:
vehicle switch
{
// ...

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,
};

Il codice precedente illustra la clausola when di un elemento switch. La clausola when viene usata per testare
condizioni diverse dall'uguaglianza per una proprietà. Al termine, sarà disponibile un metodo simile al codice
seguente:

vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car c => 2.00m - 1.0m,

Taxi {Fares: 0} => 3.50m + 1.00m,


Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi t => 3.50m - 1.00m,

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,


Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus b => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};

Molti di questi elementi switch sono esempi di criteri ricorsivi . Car { Passengers: 1} , ad esempio, mostra un
criterio costante in un criterio di proprietà.
È possibile rendere meno ripetitivo questo codice usando switch annidate. Negli esempi precedenti sia Car che
Taxi hanno quattro diversi elementi. In entrambi i casi, è possibile creare un criterio di tipo che viene inserito in
un criterio di proprietà. Questa tecnica è illustrata nel codice seguente:
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},

Taxi t => t.Fares switch


{
0 => 3.50m + 1.00m,
1 => 3.50m,
2 => 3.50m - 0.50m,
_ => 3.50m - 1.00m
},

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,


Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus b => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName:


nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};

Nell'esempio precedente l'uso di un'espressione ricorsiva indica che non si ripetono gli elementi Car e Taxi
che contengono elementi figlio che testano il valore della proprietà. Questa tecnica non viene usata per gli
elementi Bus e DeliveryTruck perché tali elementi testano gli intervalli della proprietà, non i valori discreti.

Aggiungi i prezzi per le ore di punta


Come funzionalità finale, l'autorità di regolazione dei pedaggi vuole aggiungere i prezzi per le ore di punta.
Durante le ore di punta del mattino e della sera, i pedaggi sono raddoppiati. Tale regola viene applicata al traffico
in una sola direzione: in entrata in città durante le ore di punta del mattino e in uscita durante quelle serali. Negli
altri orari della giornata lavorativa, i pedaggi aumentano del 50%. La sera tardi e la mattina presto, i pedaggi
sono ridotti del 25%. Durante il fine settimana, si paga la normale tariffa, a qualsiasi ora. È possibile utilizzare
una serie se if else le istruzioni e per esprimere questa situazione utilizzando il codice seguente:
public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
{
return 1.0m;
}
else
{
int hour = timeOfToll.Hour;
if (hour < 6)
{
return 0.75m;
}
else if (hour < 10)
{
if (inbound)
{
return 2.0m;
}
else
{
return 1.0m;
}
}
else if (hour < 16)
{
return 1.5m;
}
else if (hour < 20)
{
if (inbound)
{
return 1.0m;
}
else
{
return 2.0m;
}
}
else // Overnight
{
return 0.75m;
}
}
}

Il codice precedente funziona correttamente, ma non è leggibile. È necessario concatenare tutti i case di input e
le if istruzioni nidificate per ragionare sul codice. Al contrario, si useranno i criteri di ricerca per questa
funzionalità, ma si integreranno con altre tecniche. È possibile creare una singola espressione con
corrispondenza dei criteri che tenga in considerazione tutte le combinazioni di direzione, giorno della settimana
e ora. Il risultato sarà un'espressione complessa, difficile da leggere e da comprendere, di cui sarebbe difficile
garantire la correttezza. In alternativa, combinare questi metodi per compilare una tupla di valori che descrive in
modo conciso tutti gli stati. Usare quindi i criteri di ricerca per calcolare un moltiplicatore per il pedaggio. La
tupla contiene tre condizioni distinte:
Il giorno, che è un giorno feriale o il fine settimana.
La fascia oraria in cui il pedaggio viene riscosso.
La direzione, in entrata in città o in uscita dalla città
La tabella seguente mostra le combinazioni dei valori di input e il moltiplicatore dei prezzi per le ore di punta:
GIO RN O O RA DIREZ IO N E P REM IUM

Giorno della settimana ore di punta del mattino in entrata x 2,00

Giorno della settimana ore di punta del mattino in uscita x 1,00

Giorno della settimana giorno in entrata x 1,50

Giorno della settimana giorno in uscita x 1,50

Giorno della settimana ore di punta serali in entrata x 1,00

Giorno della settimana ore di punta serali in uscita x 2,00

Giorno della settimana notte in entrata x 0,75

Giorno della settimana notte in uscita x 0,75

fine settimana ore di punta del mattino in entrata x 1,00

fine settimana ore di punta del mattino in uscita x 1,00

fine settimana giorno in entrata x 1,00

fine settimana giorno in uscita x 1,00

fine settimana ore di punta serali in entrata x 1,00

fine settimana ore di punta serali in uscita x 1,00

fine settimana notte in entrata x 1,00

fine settimana notte in uscita x 1,00

Sono presenti 16 combinazioni diverse delle tre variabili. Combinando alcune delle condizioni, si semplificherà
l'espressione switch finale.
Il sistema che raccoglie i pedaggi usa una struttura DateTime per l'ora in cui il pedaggio è stato riscosso.
Compilare metodi membro che creano le variabili dalla tabella precedente. La funzione seguente usa come
criterio di ricerca l'espressione switch per esprimere se DateTime rappresenta il fine settimana o un giorno
feriale:

private static bool IsWeekDay(DateTime timeOfToll) =>


timeOfToll.DayOfWeek switch
{
DayOfWeek.Monday => true,
DayOfWeek.Tuesday => true,
DayOfWeek.Wednesday => true,
DayOfWeek.Thursday => true,
DayOfWeek.Friday => true,
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false
};

Questo metodo è corretto, ma è ripetitivo. È possibile semplificarlo, come illustrato nel codice seguente:
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
{
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};

Aggiungere quindi una funzione simile per classificare l'ora in blocchi di:

private enum TimeBand


{
MorningRush,
Daytime,
EveningRush,
Overnight
}

private static TimeBand GetTimeBand(DateTime timeOfToll) =>


timeOfToll.Hour switch
{
< 6 or > 19 => TimeBand.Overnight,
< 10 => TimeBand.MorningRush,
< 16 => TimeBand.Daytime,
_ => TimeBand.EveningRush,
};

Si aggiunge un privato enum per convertire ogni intervallo di tempo in un valore discreto. Quindi, il
GetTimeBand metodo usa modelli relazionali, congiuntiva o Patterns, entrambi aggiunti in C# 9,0. Il modello
relazionale consente di testare un valore numerico usando < , > , <= o >= . Il or criterio verifica se
un'espressione corrisponde a uno o più modelli. È anche possibile usare un and modello per verificare che
un'espressione corrisponda a due modelli distinti e un not modello per verificare che un'espressione non
corrisponda a un modello.
Dopo aver creato tali metodi, è possibile usare un'altra espressione switch con il criterio di tupla per
calcolare il sovrapprezzo. È possibile creare un'espressione switch con tutti i 16 elementi:

public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>


(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, true) => 1.50m,
(true, TimeBand.Daytime, false) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, true) => 0.75m,
(true, TimeBand.Overnight, false) => 0.75m,
(false, TimeBand.MorningRush, true) => 1.00m,
(false, TimeBand.MorningRush, false) => 1.00m,
(false, TimeBand.Daytime, true) => 1.00m,
(false, TimeBand.Daytime, false) => 1.00m,
(false, TimeBand.EveningRush, true) => 1.00m,
(false, TimeBand.EveningRush, false) => 1.00m,
(false, TimeBand.Overnight, true) => 1.00m,
(false, TimeBand.Overnight, false) => 1.00m,
};

Il codice precedente funziona, ma può essere semplificato. Tutte le otto combinazioni per il fine settimana hanno
lo stesso pedaggio. È possibile sostituire tutte le otto combinazioni con la sola riga seguente:
(false, _, _) => 1.0m,

Sia traffico in entrata che quello in uscita hanno lo stesso moltiplicatore durante le ore diurne e notturne dei
giorni feriali. Questi quattro elementi possono essere sostituiti con le due righe seguenti:

(true, TimeBand.Overnight, _) => 0.75m,


(true, TimeBand.Daytime, _) => 1.5m,

Dopo le due modifiche, il codice dovrebbe essere simile al seguente:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>


(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
(false, _, _) => 1.00m,
};

È infine possibile rimuovere le due fasce orarie di punta che pagano il prezzo normale. Dopo aver rimosso tali
elementi, è possibile sostituire false con un discard ( _ ) nell'elemento switch finale. Il metodo finale sarà il
seguente:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>


(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
(true, TimeBand.MorningRush, true) => 2.0m,
(true, TimeBand.EveningRush, false) => 2.0m,
_ => 1.0m,
};

Questo esempio illustra uno dei vantaggi dei criteri di ricerca. I rami dei criteri vengono infatti valutati in ordine.
Se si modifica l'ordine in modo che un ramo precedente gestisce uno dei case successivi, il compilatore avvisa
l'utente perché il codice non è raggiungibile. Grazie alle regole del linguaggio, è stato più facile eseguire le
semplificazioni precedenti con la certezza che il codice non fosse modificato.
I criteri di ricerca rendono alcuni tipi di codice più leggibili e costituiscono un'alternativa a tecniche orientate a
oggetti quando non è possibile aggiungere codice alle classi. Nel cloud i dati e le funzionalità sono separati. La
forma dei dati e le operazioni su di essi non sono necessariamente descritte insieme. In questa esercitazione i
dati esistenti sono stati utilizzati in modi completamente diversi dalla funzione originale. I criteri di ricerca hanno
consentito di scrivere funzionalità che hanno eseguito l'override di tali tipi, anche se non è stato possibile
estenderli.

Passaggi successivi
È possibile scaricare il codice completo dal repository GitHub dotnet/samples. Esplorare i criteri in autonomia e
aggiungere questa tecnica alle normali attività di codifica. L'apprendimento di queste tecniche offre un altro
modo per affrontare i problemi e creare nuove funzionalità.
App console
02/11/2020 • 22 minutes to read • Edit Online

Questa esercitazione illustra alcune funzionalità disponibili in .NET Core e nel linguaggio C#. Si apprenderà:
Nozioni di base sul interfaccia della riga di comando di .NET Core
Struttura di un'applicazione console in C#
Input/output della console
Nozioni di base sulle API di I/O dei file in .NET
Nozioni di base sul modello di programmazione asincrona delle attività in .NET
Verrà compilata un'applicazione che legge un file di testo e restituisce il contenuto del file di testo alla console.
La velocità di riproduzione dell'output della console verrà quindi configurata in modo da consentirne la lettura
ad alta voce. È possibile velocizzare o rallentare il passo premendo le chiavi ' <' (minore di) o ' >' (maggiore di).
In questa esercitazione verranno create anche È necessario crearli uno alla volta.

Prerequisiti
Configurare il computer per l'esecuzione di .NET Core. È possibile trovare le istruzioni di installazione
nella pagina di download di .NET Core . È possibile eseguire questa applicazione in Windows, Linux,
macOS o in un contenitore docker.
Installare l'editor di codice preferito.

Creare l'app
Il primo passaggio consiste nel creare una nuova applicazione. Aprire un prompt dei comandi e creare una
nuova directory per l'applicazione, impostandola come directory corrente. Digitare il comando
dotnet new console al prompt dei comandi Questa operazione crea i file iniziali per un'applicazione "Hello
World" di base.
Prima di iniziare a apportare modifiche, seguire i passaggi per eseguire la semplice applicazione Hello World.
Dopo aver creato l'applicazione, digitare dotnet restore al prompt dei comandi. Questo comando esegue il
processo di ripristino dei pacchetti NuGet. Lo strumento NuGet consente di gestire pacchetti .NET. Questo
comando scarica eventuali dipendenze mancanti per il progetto. Poiché si tratta di un nuovo progetto, non è
ancora presente alcuna dipendenza e con la prima esecuzione verrà quindi scaricato .NET Core Framework.
Dopo questo passaggio iniziale, sarà sufficiente eseguire dotnet restore quando si aggiungono nuovi pacchetti
dipendenti o si aggiorna la versione di una delle dipendenze.
Non è necessario eseguire dotnet restore perché viene eseguito in modo implicito da tutti i comandi che
richiedono un ripristino, ad esempio dotnet new ,, dotnet build dotnet run , dotnet test , dotnet publish e
dotnet pack . Per disabilitare il ripristino implicito, usare l' --no-restore opzione.

Il dotnet restore comando è ancora utile in alcuni scenari in cui il ripristino esplicito è significativo, ad esempio
le compilazioni di integrazione continua in Azure DevOps Services o nei sistemi di compilazione che devono
controllare in modo esplicito quando si verifica il ripristino.
Per informazioni su come gestire i feed NuGet, vedere la dotnet restore documentazionedi.
Dopo aver ripristinato i pacchetti, eseguire dotnet build per avviare il motore di compilazione e creare il file
eseguibile dell'applicazione. Eseguire infine dotnet run per avviare l'applicazione.
Il codice dell'applicazione Hello World semplice è tutto contenuto in Program.cs. Aprire il file con un editor di
testo Stiamo per apportare le prime modifiche. Nella parte iniziale del file è presente un'istruzione using:

using System;

Questa istruzione indica al compilatore che qualsiasi tipo dello spazio dei nomi System rientra nell'ambito.
Come altri linguaggi orientati agli oggetti, anche C# ricorre agli spazi dei nomi per organizzare i tipi. Il
programma Hello World non è diverso. È possibile vedere che il programma è incluso nello spazio dei nomi con
il nome basato sul nome della directory corrente. Per questa esercitazione, è necessario modificare il nome dello
spazio dei nomi per TeleprompterConsole :

namespace TeleprompterConsole

Lettura e restituzione del file


La prima funzionalità da aggiungere è la capacità di leggere un file di testo e visualizzare tutto il testo nella
console. Prima di tutto, aggiungere un file di testo. Copiare il file sampleQuotes.txt dal repository GitHub di
questo esempio alla directory del progetto. Questo file verrà usato come script per l'applicazione. Per
informazioni su come scaricare l'app di esempio per questo argomento, vedere le istruzioni nell'argomento
Esempi ed esercitazioni.
Aggiungere quindi il metodo seguente alla classe Program (immediatamente sotto il metodo Main ):

static IEnumerable<string> ReadFrom(string file)


{
string line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}

Questo metodo usa tipi provenienti da due nuovi spazi dei nomi. Per eseguire questa operazione, è necessario
aggiungere le due righe seguenti all'inizio del file:

using System.Collections.Generic;
using System.IO;

L'interfaccia IEnumerable<T> è definita nello spazio dei nomi System.Collections.Generic. La classe File è
definita nello spazio dei nomi System.IO.
Questo metodo è un tipo speciale di metodo C# denominato metodo iteratore. I metodi enumeratore
restituiscono sequenze che vengono valutate in modo differito. In altre parole, ogni elemento della sequenza
viene generato nel momento in cui viene richiesto dal codice che utilizza la sequenza. I metodi Enumerator sono
metodi che contengono una o più yield return istruzioni. L'oggetto restituito dal metodo ReadFrom contiene il
codice per generare ogni elemento della sequenza. In questo esempio, ciò consiste nella lettura della riga di
testo successiva dal file di origine e nella restituzione della stringa. Ogni volta che il codice chiamante richiede
l'elemento successivo della sequenza, il codice legge la riga di testo successiva dal file e la restituisce. Quando il
file è stato letto completamente, la sequenza indica che non sono presenti altri elementi.
Altri due elementi della sintassi C# possono risultare nuovi all'utente. L' using istruzione in questo metodo
gestisce la pulizia delle risorse. La variabile inizializzata nell'istruzione using ( reader , in questo esempio) deve
implementare l'interfaccia IDisposable. Tale interfaccia definisce un singolo metodo, Dispose , che deve essere
chiamato quando deve essere rilasciata la risorsa. Il compilatore genera la chiamata quando l'esecuzione
raggiunge la parentesi graffa di chiusura dell'istruzione using . Il codice generato dal compilatore assicura che
la risorsa venga rilasciata anche se viene generata un'eccezione dal codice nel blocco definito tramite l'istruzione
using.
La variabile reader viene definita tramite la parola chiave var . var definisce una variabile locale tipizzata in
modo implicito. ovvero il tipo della variabile è determinato dal tipo in fase di compilazione dell'oggetto
assegnato alla variabile. In questo caso corrisponde al valore restituito dal metodo OpenText(String), ovvero a un
oggetto StreamReader.
A questo punto, inserire il codice per leggere il file nel Main Metodo:

var lines = ReadFrom("sampleQuotes.txt");


foreach (var line in lines)
{
Console.WriteLine(line);
}

Eseguire il programma, usando dotnet run in modo da poter visualizzare ogni riga visualizzata nella console.

Aggiunta di ritardi e formattazione dell'output


Il testo restituito viene visualizzato troppo velocemente per potere essere letto a voce alta. È quindi necessario
aggiungere ritardi nell'output. Quando si inizia, verrà compilato parte del codice di base che consente
l'elaborazione asincrona. Questi primi passaggi dovranno tuttavia seguire alcuni anti-pattern, evidenziati nei
commenti mentre si aggiunge il codice, e il codice verrà aggiornato nei passaggi successivi.
Questa sezione è articolata in due fasi. Prima di tutto, è necessario aggiornare il metodo iteratore per restituire
singole parole anziché intere righe. Questa operazione viene eseguita con queste modifiche. Sostituire la
funzione yield return line; con il codice seguente:

var words = line.Split(' ');


foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;

Sarà quindi necessario modificare il modo in cui vengono usate le righe del file e aggiungere un ritardo dopo la
scrittura di ogni parola. Sostituire l'istruzione Console.WriteLine(line) nel metodo Main con il blocco seguente:

Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}

La classe Task si trova nello spazio dei nomi System.Threading.Tasks ed è quindi necessario aggiungere
l'istruzione using all'inizio del file:
using System.Threading.Tasks;

Eseguire l'esempio e verificare l'output. Ogni singola parola viene ora visualizzata, seguita da un ritardo di 200
ms. L'output visualizzato mostra tuttavia alcuni problemi perché il file di testo di origine presenta più righe con
oltre 80 caratteri senza interruzione di riga, che possono risultare difficili da leggere durante lo scorrimento.
Questa operazione è facile da risolvere. Si tiene traccia solo della lunghezza di ogni riga e si genera una nuova
riga ogni volta che la lunghezza della linea raggiunge una determinata soglia. Dichiarare una variabile locale
dopo la dichiarazione di words nel metodo ReadFrom che contiene la lunghezza di riga:

var lineLength = 0;

Aggiungere quindi il codice seguente dopo l'istruzione yield return word + " "; (prima della parentesi graffa
di chiusura):

lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}

Eseguire l'esempio e si sarà in grado di leggere a voce alta al ritmo preconfigurato.

Attività asincrone
In questo passaggio finale verrà aggiunto il codice per scrivere l'output in modo asincrono in un'unica attività,
eseguendo anche un'altra attività per leggere l'input dell'utente se desiderano velocizzare o rallentare la
visualizzazione del testo o arrestare completamente la visualizzazione del testo. Questa operazione include
alcuni passaggi e, alla fine, tutti gli aggiornamenti necessari. Il primo passaggio consiste nel creare un Task
metodo di restituzione asincrono che rappresenti il codice creato finora per la lettura e la visualizzazione del file.
Aggiungere questo metodo alla Program classe (viene ricavato dal corpo del Main metodo):

private static async Task ShowTeleprompter()


{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(200);
}
}
}

Si noteranno due modifiche. Nel corpo del metodo, anziché chiamare Wait() per attendere in modo sincrono il
completamento di un'attività, questa versione usa la parola chiave await . A questo scopo, è necessario
aggiungere il modificatore async alla firma del metodo. Il metodo restituisce Task . Osservare inoltre come
non siano presenti istruzioni return che restituiscono un oggetto Task . L'oggetto Task , infatti, viene creato dal
codice che il compilatore genera quando si usa l'operatore await . È possibile immaginare quindi che il metodo
restituisca l'oggetto quando raggiunge la parola chiave await . L'oggetto Task restituito indica che il lavoro
non è stato completato. Il metodo riprende l'esecuzione quando viene completata l'attività attesa. Al termine
dell'esecuzione, l'oggetto Task restituito indica che il lavoro è stato completato. Il codice chiamante può
monitorare l'oggetto Task restituito per determinare quando è stato completato.
È possibile chiamare questo nuovo metodo nel metodo Main :

ShowTeleprompter().Wait();

Di seguito, nel metodo Main , il codice attende in modo sincrono. Quando possibile, è consigliabile usare
l'operatore await anziché attendere in modo sincrono. Tuttavia, nel metodo di un'applicazione console Main
non è possibile usare l' await operatore. Questo comporterebbe infatti la chiusura dell'applicazione prima che
siano state completate tutte le attività.

NOTE
Se si usa C# 7.1 o versione successiva è possibile creare applicazioni console con il metodo async Main .

Successivamente, è necessario scrivere il secondo metodo asincrono per leggere dalla console e controllare le
chiavi ' <' (minore di),' >' (maggiore di) è X ' o ' x '. Ecco il metodo che si aggiunge per l'attività:

private static async Task GetInput()


{
var delay = 200;
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
{
delay -= 10;
}
else if (key.KeyChar == '<')
{
delay += 10;
}
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
{
break;
}
} while (true);
};
await Task.Run(work);
}

In questo modo viene creata un'espressione lambda per rappresentare un Action delegato che legge una chiave
dalla console e modifica una variabile locale che rappresenta il ritardo quando l'utente preme le chiavi ' <'
(minore di) o ' >' (maggiore di). Il metodo delegato termina quando l'utente preme i tasti ' X ' o ' x ', che
consentono all'utente di arrestare la visualizzazione del testo in qualsiasi momento. Questo metodo usa
ReadKey() per bloccare l'operazione e attendere che l'utente prema un tasto.
Per completare questa funzionalità, è necessario creare un nuovo metodo di restituzione async Task in grado di
avviare entrambe le attività ( GetInput e ShowTeleprompter ) e gestire i dati condivisi tra di esse.
È il momento di creare una classe in grado di gestire i dati condivisi tra queste due attività. Questa classe
contiene due proprietà pubbliche: il ritardo e un flag Done per indicare che il file è stato letto interamente:
namespace TeleprompterConsole
{
internal class TelePrompterConfig
{
public int DelayInMilliseconds { get; private set; } = 200;

public void UpdateDelay(int increment) // negative to speed up


{
var newDelay = Min(DelayInMilliseconds + increment, 1000);
newDelay = Max(newDelay, 20);
DelayInMilliseconds = newDelay;
}

public bool Done { get; private set; }

public void SetDone()


{
Done = true;
}
}
}

Inserire la classe in un nuovo file e includerla nello spazio dei nomi TeleprompterConsole , come illustrato in
precedenza. Sarà inoltre necessario aggiungere un'istruzione in using static modo che sia possibile fare
riferimento ai Min Max metodi e senza i nomi di classe o spazio dei nomi che lo racchiudono. Un'
using static istruzione importa i metodi da una classe. Questo comportamento è in contrasto con le istruzioni
using usate finora che hanno importato tutte le classi da uno spazio dei nomi.

using static System.Math;

Sarà quindi necessario aggiornare i metodi ShowTeleprompter e GetInput affinché usino il nuovo oggetto
config . Scrivere un ultimo metodo Task di restituzione dell'oggetto async , in grado di avviare entrambe le
attività e chiudere la prima attività nel momento in cui viene completata:

private static async Task RunTeleprompter()


{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);

var speedTask = GetInput(config);


await Task.WhenAny(displayTask, speedTask);
}

Il nuovo metodo qui è la chiamata WhenAny(Task[]). In questo modo si crea un oggetto Task che termina non
appena viene completata un'attività inclusa nel relativo elenco di argomenti.
È ora necessario aggiornare i metodi ShowTeleprompter e GetInput affinché usino l'oggetto config per il
ritardo:
private static async Task ShowTeleprompter(TelePrompterConfig config)
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(config.DelayInMilliseconds);
}
}
config.SetDone();
}

private static async Task GetInput(TelePrompterConfig config)


{
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
config.UpdateDelay(-10);
else if (key.KeyChar == '<')
config.UpdateDelay(10);
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
config.SetDone();
} while (!config.Done);
};
await Task.Run(work);
}

Questa nuova versione di ShowTeleprompter chiama un nuovo metodo nella classe TeleprompterConfig . A
questo punto, è necessario aggiornare Main affinché chiami RunTeleprompter anziché ShowTeleprompter :

RunTeleprompter().Wait();

Conclusioni
In questa esercitazione sono state illustrate diverse funzionalità del linguaggio C# e presentate le librerie .NET
Core correlate all'uso di applicazioni console. A partire da queste informazioni è possibile approfondire la
conoscenza del linguaggio e delle classi presentate nell'esercitazione. Sono state illustrate le nozioni di base
dell'I/O di file e console, il blocco e l'utilizzo non bloccanti della programmazione asincrona basata su attività,
una panoramica del linguaggio C# e la modalità di organizzazione dei programmi C# e il interfaccia della riga di
comando di .NET Core.
Per altre informazioni su I/O di file, consultare l'argomento I/O di file e di flussi. Per altre informazioni sul
modello di programmazione asincrono usato in questa esercitazione, consultare l'argomento Programmazione
asincrona basata su attività e l'argomento Programmazione asincrona.
Client REST
28/01/2021 • 22 minutes to read • Edit Online

Questa esercitazione illustra alcune funzionalità disponibili in .NET Core e nel linguaggio C#. Si apprenderà:
Nozioni di base del interfaccia della riga di comando di .NET Core.
Panoramica delle funzionalità del linguaggio C#
Gestione delle dipendenze con NuGet
Comunicazioni HTTP
Elaborazione delle informazioni JSON
Gestione della configurazione con attributi
Verrà compilata un'applicazione che rilascia richieste HTTP a un servizio REST su GitHub. si leggeranno
informazioni in formato JSON e si convertirà il pacchetto JSON in oggetti C#. Si imparerà infine a usare e
gestire oggetti C#.
Questa esercitazione include molte funzionalità. È necessario crearli uno alla volta.
Se si preferisce proseguire con l' esempio finale per questo articolo, è possibile scaricarlo. Per istruzioni sul
download, vedere Esempi ed esercitazioni.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET Core. Per le istruzioni per l'installazione, vedere la
pagina di download di .NET Core. È possibile eseguire questa applicazione in Windows, Linux o macOS o in un
contenitore docker. È necessario installare l'editor di codice preferito. Nelle descrizioni seguenti viene usato
Visual Studio Code, un editor open source multipiattaforma, ma è possibile usare gli strumenti con cui si ha
maggiore familiarità.

Creare l'applicazione
Il primo passaggio consiste nel creare una nuova applicazione. Aprire un prompt dei comandi e creare una
nuova directory per l'applicazione, impostandola come directory corrente. Immettere il comando seguente in
una finestra della console:

dotnet new console --name WebAPIClient

Questa operazione crea i file iniziali per un'applicazione "Hello World" di base. Il nome del progetto è
"WebAPIClient". Poiché si tratta di un nuovo progetto, non è presente alcuna dipendenza. La prima esecuzione
consente di scaricare il Framework .NET Core, installare un certificato di sviluppo ed eseguire Gestione pacchetti
NuGet per ripristinare le dipendenze mancanti.
Prima di iniziare a apportare modifiche, cd nella directory "WebAPIClient" e digitare dotnet run (vedere la
nota) al prompt dei comandi per eseguire l'applicazione. dotnet run esegue automaticamente dotnet restore
se nell'ambiente mancano dipendenze. Esegue anche dotnet build se l'applicazione deve essere ricompilata.
Dopo l'installazione iniziale, sarà necessario solo eseguire dotnet restore o dotnet build quando ha senso per
il progetto.

Aggiunta di nuove dipendenze


Uno degli obiettivi di progettazione principali di .NET Core è ridurre al minimo le dimensioni dell'installazione di
.NET. Se per alcune delle funzionalità di un'applicazione sono necessarie altre librerie, è possibile aggiungere tali
dipendenze al file di progetto C# (*.csproj). Per questo esempio, è necessario aggiungere il
System.Runtime.Serialization.Json pacchetto, in modo che l'applicazione possa elaborare le risposte JSON.

È necessario il System.Runtime.Serialization.Json pacchetto per questa applicazione. Aggiungerlo al progetto


eseguendo il comando seguente dell' interfaccia della riga di comando di .NET:

dotnet add package System.Text.Json

Esecuzione di richieste Web


Si è ora pronti per iniziare a recuperare dati dal Web. In questa applicazione si leggeranno informazioni dall'API
GitHub. In particolare, si leggeranno informazioni sui progetti nell'ambito di .NET Foundation. Si inizierà
inviando all'API GitHub la richiesta di recuperare informazioni sui progetti. L'endpoint che verrà usato è:
https://api.github.com/orgs/dotnet/repos. Poiché si vuole recuperare tutte le informazioni su questi progetti, si
userà una richiesta HTTP GET. Anche il browser usa richieste HTTP GET ed è quindi possibile incollare l'URL nel
browser per visualizzare le informazioni che si riceveranno ed elaboreranno.
Per eseguire richieste Web, usare la classe HttpClient. Come tutte le API .NET moderne, HttpClient supporta solo
metodi asincroni per le API a esecuzione prolungata. È quindi necessario iniziare creando un metodo asincrono,
che verrà inserito nell'implementazione mentre si compila la funzionalità dell'applicazione. Iniziare aprendo il
file program.cs nella directory del progetto e aggiungendo il metodo seguente alla classe Program :

private static async Task ProcessRepositories()


{
}

È necessario aggiungere una using direttiva all'inizio del Main metodo, in modo che il compilatore C#
riconosca il Task tipo:

using System.Threading.Tasks;

Se si compila il progetto in questo momento, viene generato un avviso per indicare che il metodo non contiene
alcun operatore await e verrà quindi eseguito in modo sincrono. Ignorare temporaneamente il messaggio. Si
aggiungeranno operatori await durante la compilazione del metodo.
Aggiornare quindi il Main metodo per chiamare il ProcessRepositories metodo. Il ProcessRepositories
metodo restituisce un'attività e non è necessario uscire dal programma prima del completamento dell'attività.
Pertanto, è necessario modificare la firma di Main . Aggiungere il async modificatore e modificare il tipo
restituito in Task . Quindi, nel corpo del metodo, aggiungere una chiamata a ProcessRepositories . Aggiungere
la await parola chiave alla chiamata al metodo:

static async Task Main(string[] args)


{
await ProcessRepositories();
}

Si dispone a questo punto di un programma che, pur non eseguendo alcuna operazione, opera in modo
asincrono. È ora possibile migliorarlo.
È necessario innanzitutto un oggetto in grado di recuperare i dati dal Web; a tale scopo è possibile usare
HttpClient. per gestire la richiesta e le risposte. Creare un'istanza di una singola istanza di quel tipo nella
Program classe all'interno del file Program.cs .

namespace WebAPIClient
{
class Program
{
private static readonly HttpClient client = new HttpClient();

static async Task Main(string[] args)


{
//...
}
}
}

Tornare quindi al metodo ProcessRepositories e compilarne una prima versione:

private static async Task ProcessRepositories()


{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");

var stringTask = client.GetStringAsync("https://api.github.com/orgs/dotnet/repos");

var msg = await stringTask;


Console.Write(msg);
}

È necessario aggiungere anche due nuove using direttive all'inizio del file affinché questo venga compilato:

using System.Net.Http;
using System.Net.Http.Headers;

Questa prima versione esegue una richiesta Web per leggere l'elenco di tutti i repository presenti
nell'organizzazione DotNet Foundation. (L'ID GitHub per .NET Foundation è dotnet .) Le prime righe
configurano HttpClient per questa richiesta. prima viene configurato per accettare le risposte JSON di GitHub.
Questo formato è semplicemente JSON. Nella riga successiva viene aggiunta un'intestazione Agente utente a
tutte le richieste provenienti da questo oggetto. Queste due intestazioni vengono controllate dal codice server di
GitHub e sono necessarie per recuperare informazioni da GitHub.
Dopo aver configurato l'oggetto HttpClient, si eseguirà una richiesta Web e si recupererà la risposta. In questa
prima versione viene usato il metodo pratico HttpClient.GetStringAsync(String). Questo metodo avvia un'attività
che esegue la richiesta Web e, quando la richiesta viene restituita, legge il flusso di risposta e ne estrae il
contenuto. Il corpo della risposta viene restituito come String. La stringa è disponibile quando l'attività viene
completata.
Le ultime due righe di questo metodo attendono che l'attività sia completata e visualizzano la risposta nella
console. Compilare l'app ed eseguirla. Il messaggio di avviso non viene più visualizzato perché
ProcessRepositories contiene ora un operatore await . Verrà visualizzata una lunga schermata di testo JSON
formattato.

Elaborazione del risultato JSON


A questo punto è stato scritto il codice per recuperare una risposta da un server Web e visualizzare il testo
contenuto nella risposta. Si convertirà ora la risposta JSON in oggetti C#.
La System.Text.Json.JsonSerializer classe serializza gli oggetti in JSON e DESERIALIZZA JSON in oggetti. Per
iniziare, definire una classe per rappresentare l' repo oggetto JSON restituito dall'API GitHub:

using System;

namespace WebAPIClient
{
public class Repository
{
public string name { get; set; }
}
}

Inserire il codice precedente in un nuovo file denominato 'repo.cs'. Questa versione della classe rappresenta il
percorso più semplice per elaborare dati JSON. Il nome della classe e il nome del membro corrispondono ai
nomi usati nel pacchetto JSON, anziché alle convenzioni C# seguenti. Questo errore verrà risolto in un secondo
momento specificando alcuni attributi di configurazione. Questa classe dimostra un'altra caratteristica
importante delle funzioni di serializzazione e deserializzazione JSON: non tutti i campi del pacchetto JSON fanno
parte della classe. Il serializzatore JSON ignorerà le informazioni non incluse nel tipo di classe in uso. In questo
modo sarà più semplice creare tipi compatibili con un solo subset dei campi presenti nel pacchetto JSON.
Dopo aver creato il tipo, è possibile deserializzarlo.
Si userà infine il serializzatore per convertire i dati JSON in oggetti C#. Sostituire la chiamata a
GetStringAsync(String) nel ProcessRepositories metodo con le righe seguenti:

var streamTask = client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");


var repositories = await JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);

Si stanno usando nuovi spazi dei nomi, quindi è necessario aggiungerli anche all'inizio del file:

using System.Collections.Generic;
using System.Text.Json;

Si noti che ora viene usato GetStreamAsync(String) anziché GetStringAsync(String). Il serializzatore usa un flusso
anziché una stringa come origine. Verranno ora illustrate alcune funzionalità del linguaggio C# utilizzate nella
seconda riga del frammento di codice precedente. Il primo argomento per
JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) è un' await
espressione. Gli altri due parametri sono facoltativi e vengono omessi nel frammento di codice. Le espressioni
await possono apparire praticamente in qualsiasi punto del codice, anche se fino a questo momento sono state
viste solo come parte di un'istruzione di assegnazione. Il Deserialize metodo è generico, pertanto è necessario
fornire gli argomenti di tipo per il tipo di oggetti che devono essere creati dal testo JSON. In questo esempio si
esegue la deserializzazione in un List<Repository> oggetto, ovvero un altro oggetto generico, il
System.Collections.Generic.List<T> . La List<> classe archivia una raccolta di oggetti. L'argomento tipo dichiara
il tipo di oggetti archiviati in List<> . Il testo JSON rappresenta una raccolta di oggetti repository, quindi
l'argomento di tipo è Repository .
Questa sezione è quasi completata. Ora che i dati JSON sono stati convertiti in oggetti C#, verrà visualizzato il
nome di ogni repository. Sostituire le righe seguenti:

var msg = await stringTask; //**Deleted this


Console.Write(msg);

con il codice seguente:


foreach (var repo in repositories)
Console.WriteLine(repo.name);

Compilare l'applicazione ed eseguirla. Verranno stampati i nomi dei repository che fanno parte di .NET
Foundation.

Controllo della serializzazione


Prima di aggiungere altre funzionalità, è possibile indirizzare la name proprietà tramite l' [JsonPropertyName]
attributo. A questo scopo, eseguire le modifiche seguenti alla dichiarazione del campo name in repo.cs:

[JsonPropertyName("name")]
public string Name { get; set; }

Per utilizzare l' [JsonPropertyName] attributo, sarà necessario aggiungere lo System.Text.Json.Serialization spazio
dei nomi alle using direttive:

using System.Text.Json.Serialization;

Con questa operazione si modifica il codice che scrive il nome di ogni repository in program.cs:

Console.WriteLine(repo.Name);

Eseguire dotnet run per assicurarsi che i mapping siano corretti. L'output ottenuto dovrebbe essere uguale a
quello precedente.
Si apporterà di seguito un'ultima modifica prima di aggiungere nuove funzionalità. Il metodo
ProcessRepositories può operare in modo asincrono e restituire una raccolta di repository. È ora necessario
fare in modo che il metodo restituisca List<Repository> e spostare il codice che scrive le informazioni nel
metodo Main .
Modificare la firma di ProcessRepositories in modo da restituire un'attività il cui risultato sia un elenco di
oggetti Repository :

private static async Task<List<Repository>> ProcessRepositories()

Restituire quindi i repository dopo aver elaborato la risposta JSON:

var streamTask = client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");


var repositories = await JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);
return repositories;

Il compilatore genera l'oggetto Task<T> come elemento restituito perché il metodo è stato contrassegnato
come async . Si modificherà ora il metodo Main in modo che acquisisca i risultati e scriva ogni nome di
repository nella console. Il metodo Main avrà ora un aspetto simile al seguente:
public static async Task Main(string[] args)
{
var repositories = await ProcessRepositories();

foreach (var repo in repositories)


Console.WriteLine(repo.Name);
}

Lettura di altre informazioni


Per completare l'esercitazione si elaboreranno ora altre proprietà del pacchetto JSON inviato dall'API di GitHub.
Non si intende acquisire tutte le informazioni possibili ma, aggiungendo alcune proprietà, sarà possibile
illustrare qualche altra funzionalità del linguaggio C#.
Si inizierà aggiungendo altri tipi semplici alla definizione di classe Repository . Aggiungere quindi alla classe le
proprietà seguenti:

[JsonPropertyName("description")]
public string Description { get; set; }

[JsonPropertyName("html_url")]
public Uri GitHubHomeUrl { get; set; }

[JsonPropertyName("homepage")]
public Uri Homepage { get; set; }

[JsonPropertyName("watchers")]
public int Watchers { get; set; }

In queste proprietà sono incorporate conversioni dal tipo stringa (il contenuto dei pacchetti JSON) al tipo di
destinazione. Il tipo Uri, che per alcuni utenti può essere nuovo, rappresenta un URI o, come in questo caso, un
URL. Nel caso dei tipi Uri e int , se il pacchetto JSON contiene dati che non possono essere convertiti nel tipo
di destinazione, l'azione di serializzazione genererà un'eccezione.
Dopo aver aggiunto queste proprietà, aggiornare il metodo Main per visualizzare gli elementi seguenti:

foreach (var repo in repositories)


{
Console.WriteLine(repo.Name);
Console.WriteLine(repo.Description);
Console.WriteLine(repo.GitHubHomeUrl);
Console.WriteLine(repo.Homepage);
Console.WriteLine(repo.Watchers);
Console.WriteLine();
}

Come passaggio conclusivo si aggiungeranno le informazioni necessarie per l'ultima operazione push,
formattate nella risposta JSON come illustrato di seguito:

2016-02-08T21:27:00Z

Tale formato è nell'ora UTC (Coordinated Universal Time), in modo da ottenere un DateTime valore la cui Kind
proprietà è Utc . Se si preferisce una data rappresentata nel fuso orario, è necessario scrivere un metodo di
conversione personalizzato. Definire innanzitutto una public proprietà che conterrà la rappresentazione UTC
della data e dell'ora nella Repository classe e una LastPush readonly proprietà che restituisca la data
convertita nell'ora locale:
[JsonPropertyName("pushed_at")]
public DateTime LastPushUtc { get; set; }

public DateTime LastPush => LastPushUtc.ToLocalTime();

Verranno ora esaminati i nuovi costrutti appena definiti. La LastPush proprietà viene definita utilizzando un
membro con corpo di espressione per la get funzione di accesso. Non è presente alcuna funzione di accesso
set . L'omissione della set funzione di accesso è la modalità di definizione di una proprietà di sola lettura in
C#. È possibile creare anche proprietà di sola scrittura in C#, ma con un valore limitato.
Dopo aver aggiunto un'altra istruzione di output alla console, sarà possibile compilare ed eseguire nuovamente
l'app:

Console.WriteLine(repo.LastPush);

La versione dell'app dovrebbe ora corrispondere all'esempio completo.

Conclusione
In questa esercitazione sono state descritte le procedure necessarie per eseguire richieste Web, analizzare i
risultati e visualizzare le proprietà dei risultati. Sono stati inoltre aggiunti nuovi pacchetti come dipendenze del
progetto e sono state illustrate alcune delle funzionalità del linguaggio C# che supportano tecniche orientate
agli oggetti.
Non è necessario eseguire dotnet restore perché viene eseguito in modo implicito da tutti i comandi che
richiedono un ripristino, ad esempio dotnet new ,, dotnet build dotnet run , dotnet test , dotnet publish e
dotnet pack . Per disabilitare il ripristino implicito, usare l' --no-restore opzione.

Il dotnet restore comando è ancora utile in alcuni scenari in cui il ripristino esplicito è significativo, ad esempio
le compilazioni di integrazione continua in Azure DevOps Services o nei sistemi di compilazione che devono
controllare in modo esplicito quando si verifica il ripristino.
Per informazioni su come gestire i feed NuGet, vedere la dotnet restore documentazionedi.
Ereditarietà in C# e .NET
02/11/2020 • 45 minutes to read • Edit Online

Questa esercitazione presenta l'ereditarietà in C#. L'ereditarietà è una caratteristica dei linguaggi di
programmazione orientati a oggetti che consente di definire una classe di base con funzionalità specifiche
(relative a dati e comportamento) e classi derivate che ereditano o eseguono l'override di tali funzionalità.

Prerequisiti
In questa esercitazione si presuppone che sia stato installato il .NET Core SDK. Visitare la pagina di download di
.NET Core per scaricarlo. È necessario anche un editor di codice. In questa esercitazione viene usato Visual Studio
Code, ma è possibile usare qualsiasi editor di codice desiderato.

Esecuzione degli esempi


Per creare ed eseguire gli esempi in questa esercitazione, viene usata l'utilità dotnet dalla riga di comando.
Eseguire questi passaggi per ogni esempio:
1. Creare una directory per archiviare l'esempio.
2. Per creare un nuovo progetto.NET Core, immettere il comando dotnet new console al prompt dei
comandi.
3. Copiare e incollare il codice dell'esempio nell'editor di codice.
4. Per caricare o ripristinare le dipendenze del progetto, eseguire il comando dotnet restore dalla riga di
comando.
Non è necessario eseguire dotnet restore perché viene eseguito in modo implicito da tutti i comandi
che richiedono un ripristino, ad esempio dotnet new ,, dotnet build dotnet run , dotnet test ,
dotnet publish e dotnet pack . Per disabilitare il ripristino implicito, usare l' --no-restore opzione.

Il dotnet restore comando è ancora utile in alcuni scenari in cui il ripristino esplicito è significativo, ad
esempio le compilazioni di integrazione continua in Azure DevOps Services o nei sistemi di compilazione
che devono controllare in modo esplicito quando si verifica il ripristino.
Per informazioni su come gestire i feed NuGet, vedere la dotnet restore documentazionedi.
5. Per compilare ed eseguire l'esempio, immettere il comando dotnet run.

Informazioni generali: che cos'è l'ereditarietà?


Il concetto di ereditarietà è uno degli attributi fondamentali della programmazione orientata a oggetti.
L'ereditarietà consente di definire una classe figlio che riutilizza (eredita), estende o modifica il comportamento
di una classe padre. La classe i cui membri vengono ereditati è denominata classe di base. Quella che eredita i
membri della classe di base è denominata classe derivata.
C# e .NET supportano solo l'ereditarietà singola. Ciò significa che una classe può solo ereditare da una singola
classe. L'ereditarietà tuttavia è transitiva, pertanto è possibile definire una gerarchia di ereditarietà per un set di
tipi. In altre parole, il tipo D può ereditare dal tipo C , che eredita dal tipo B , il quale eredita a sua volta dal tipo
della classe di base A . Poiché l'ereditarietà è transitiva, i membri del tipo A sono disponibili per il tipo D .
Non tutti i membri di una classe di base vengono ereditati dalle classi derivate. I membri seguenti non vengono
ereditati:
Costruttori statici, che inizializzano i dati statici di una classe.
Costruttori di istanze, che vengono chiamati per creare una nuova istanza della classe. Ogni classe deve
definire propri costruttori.
Finalizzatori, che vengono chiamati dal Garbage Collector di runtime per distruggere le istanze di una
classe.
Tutti gli altri membri di una classe di base vengono ereditati dalle classi derivate, ma la loro visibilità dipende
dall'accessibilità. L'accessibilità di un membro ne determina la visibilità per le classi derivate, come indicato di
seguito:
I membri privati sono visibili solo nelle classi derivate che sono annidate nella relativa classe di base. In
caso contrario, non sono visibili nelle classi derivate. Nell'esempio seguente A.B è una classe annidata
che deriva da A e C deriva da A . Il campo privato A.value è visibile in A.B. Se tuttavia si rimuovono i
commenti dal metodo C.GetValue e si tenta di compilare l'esempio, verrà generato l'errore del
compilatore CS0122: "'A.value' non è accessibile a causa del livello di protezione impostato".

using System;

public class A
{
private int value = 10;

public class B : A
{
public int GetValue()
{
return this.value;
}
}
}

public class C : A
{
// public int GetValue()
// {
// return this.value;
// }
}

public class Example


{
public static void Main(string[] args)
{
var b = new A.B();
Console.WriteLine(b.GetValue());
}
}
// The example displays the following output:
// 10

I membri protetti sono visibili solo nelle classi derivate.


I membri interni sono visibili solo nelle classi derivate che si trovano nello stesso assembly della classe di
base. Non sono visibili nelle classi derivate che si trovano in un assembly diverso dalla classe di base.
I membri pubblici sono visibili nelle classi derivate e fanno parte dell'interfaccia pubblica della classe
derivata. I membri pubblici ereditati possono essere chiamati come se fossero definiti nella classe
derivata. Nell'esempio seguente la classe A definisce un metodo denominato Method1 e la classe B
eredita dalla classe A . Nell'esempio viene quindi chiamato Method1 come se fosse un metodo di istanza
in B .

public class A
{
public void Method1()
{
// Method implementation.
}
}

public class B : A
{ }

public class Example


{
public static void Main()
{
B b = new B();
b.Method1();
}
}

Le classi derivate possono anche eseguire l'override dei membri ereditati fornendo un'implementazione
alternativa. Per poter eseguire l'override di un membro, il membro nella classe di base deve essere
contrassegnato con la parola chiave virtual. Per impostazione predefinita, i membri della classe di base non sono
contrassegnati come virtual e non possono essere sottoposti a override. Se si prova a eseguire l'override di
un membro non virtuale, viene generato l'errore del compilatore CS0506: "<member> non può eseguire
l'override del membro ereditato <member> perché non è contrassegnato come virtual, abstract o override".

public class A
{
public void Method1()
{
// Do something.
}
}

public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}

In alcuni casi una classe derivata deve eseguire l'override dell'implementazione della classe di base. I membri
della classe di base contrassegnati con la parola chiave abstract richiedono di essere sottoposti a override dalle
classi derivate. Se si prova a compilare l'esempio seguente, verrà generato l'errore del compilatore CS0534, "
<classe> non implementa il membro astratto <membro> ereditato", perché la classe B non fornisce alcuna
implementazione per A.Method1 .
public abstract class A
{
public abstract void Method1();
}

public class B : A // Generates CS0534.


{
public void Method3()
{
// Do something.
}
}

L'ereditarietà si applica solo alle classi e alle interfacce. Le altre categorie di tipi (struct, delegati ed
enumerazioni) non supportano l'ereditarietà. Per queste regole, se si prova a compilare il codice come
nell'esempio seguente, verrà generato l'errore del compilatore CS0527: "Il tipo 'ValueType' nell'elenco delle
interfacce non è un'interfaccia". Il messaggio di errore indica che, sebbene sia possibile definire le interfacce
implementate da un tipo struct, l'ereditarietà non è supportata.

using System;

public struct ValueStructure : ValueType // Generates CS0527.


{
}

Ereditarietà implicita
Oltre ai tipi da cui possono ereditare tramite l'ereditarietà singola, tutti i tipi nel sistema di tipi .NET ereditano in
modo implicito da Object o da un tipo derivato. Le funzionalità comuni di Object saranno disponibili per
qualsiasi tipo.
Per comprendere il significato dell'ereditarietà implicita, si definirà una nuova classe, SimpleClass , che è
semplicemente una definizione di classe vuota:

public class SimpleClass


{ }

È quindi possibile usare la reflection, che consente di esaminare i metadati del tipo per ottenere informazioni su
di esso, per generare un elenco dei membri che appartengono al tipo SimpleClass . Anche se non è stato
definito alcun membro nella classe SimpleClass , l'output dell'esempio indica che ha effettivamente nove
membri. Uno di questi è un costruttore senza parametri (o predefinito) fornito automaticamente per il tipo
SimpleClass dal compilatore C#. I rimanenti otto sono membri di Object, il tipo da cui ereditano in modo
implicito tutte le classi e le interfacce nel sistema di tipi .NET.
using System;
using System.Reflection;

public class Example


{
public static void Main()
{
Type t = typeof(SimpleClass);
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
MemberInfo[] members = t.GetMembers(flags);
Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
foreach (var member in members)
{
string access = "";
string stat = "";
var method = member as MethodBase;
if (method != null)
{
if (method.IsPublic)
access = " Public";
else if (method.IsPrivate)
access = " Private";
else if (method.IsFamily)
access = " Protected";
else if (method.IsAssembly)
access = " Internal";
else if (method.IsFamilyOrAssembly)
access = " Protected Internal ";
if (method.IsStatic)
stat = " Static";
}
var output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by
{member.DeclaringType}";
Console.WriteLine(output);
}
}
}
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass

L'ereditarietà implicita dalla classe Object rende disponibili questi metodi per la classe SimpleClass :
Il metodo pubblico ToString , che converte un oggetto SimpleClass nella relativa rappresentazione
stringa, restituisce il nome di tipo completo. In questo caso il metodo ToString restituisce la stringa
"SimpleClass".
Tre metodi che verificano l'uguaglianza di due oggetti: il metodo pubblico di istanza Equals(Object) , il
metodo statico pubblico Equals(Object, Object) e il metodo statico pubblico
ReferenceEquals(Object, Object) . Per impostazione predefinita, questi metodi verificano l'uguaglianza
dei riferimenti. Ciò significa che, per essere uguali, due variabili di oggetto devono fare riferimento allo
stesso oggetto.
Il metodo pubblico GetHashCode , che calcola un valore che consente di usare un'istanza del tipo nelle
raccolte con hash.
Il metodo pubblico GetType , che restituisce un oggetto Type che rappresenta il tipo SimpleClass .
Il metodo protetto Finalize, che è progettato per rilasciare le risorse non gestite prima che la memoria di
un oggetto venga recuperata dal Garbage Collector.
Il metodo protetto MemberwiseClone, che crea un clone superficiale dell'oggetto corrente.
Grazie all'ereditarietà implicita, è possibile chiamare qualsiasi membro ereditato da un oggetto SimpleClass
come se fosse effettivamente un membro definito nella classe SimpleClass . Nell'esempio seguente viene
chiamato il metodo SimpleClass.ToString che SimpleClass eredita da Object.

using System;

public class SimpleClass


{}

public class Example


{
public static void Main()
{
SimpleClass sc = new SimpleClass();
Console.WriteLine(sc.ToString());
}
}
// The example displays the following output:
// SimpleClass

Nella tabella seguente sono elencate le categorie di tipi che è possibile creare in C# e i tipi da cui ereditano in
modo implicito. Tramite l'ereditarietà ciascun tipo di base rende disponibile un set di membri diverso per i tipi
derivati in modo implicito.

C AT EGO RIA DI T IP I EREDITA IN M O DO IM P L IC ITO DA

classe Object

struct ValueType, Object

enum Enum, ValueType, Object

delegato MulticastDelegate, Delegate, Object

Ereditarietà e relazione "è un"


In genere l'ereditarietà consente di esprimere una relazione "è un" tra una classe di base e una o più classi
derivate, in cui le classi derivate sono versioni specializzate della classe di base. La classe derivata è un tipo della
classe di base. La classe Publication rappresenta ad esempio una pubblicazione di qualsiasi tipo e le classi
Book e Magazine rappresentano tipi specifici di pubblicazioni.

NOTE
Una classe o uno struct può implementare una o più interfacce. Anche se l'implementazione dell'interfaccia è spesso
presentata come una soluzione alternativa all'ereditarietà singola o come modo per usare l'ereditarietà con struct, è stata
ideata per esprimere una relazione diversa (una relazione "può fare") tra un'interfaccia e il relativo tipo di implementazione
rispetto all'ereditarietà. Un'interfaccia definisce un subset di funzionalità che rende disponibili per i tipi di implementazione,
ad esempio le funzionalità per verificare l'uguaglianza, confrontare o ordinare gli oggetti, nonché supportare la
formattazione e l'analisi in base alle impostazioni cultura.
Si noti che "è un" esprime anche la relazione tra un tipo e un'istanza specifica di quel tipo. Nell'esempio
seguente Automobile è una classe che ha tre proprietà univoche di sola lettura: Make , il produttore
dell'automobile, Model , il tipo di automobile e Year , l'anno di produzione. La classe Automobile include anche
un costruttore i cui argomenti vengono assegnati ai valori delle proprietà ed esegue l'override del metodo
Object.ToString per generare una stringa che identifica in modo univoco l'istanza Automobile anziché la classe
Automobile .

using System;

public class Automobile


{
public Automobile(string make, string model, int year)
{
if (make == null)
throw new ArgumentNullException("The make cannot be null.");
else if (String.IsNullOrWhiteSpace(make))
throw new ArgumentException("make cannot be an empty string or have space characters only.");
Make = make;

if (model == null)
throw new ArgumentNullException("The model cannot be null.");
else if (String.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or have space characters only.");
Model = model;

if (year < 1857 || year > DateTime.Now.Year + 2)


throw new ArgumentException("The year is out of range.");
Year = year;
}

public string Make { get; }

public string Model { get; }

public int Year { get; }

public override string ToString() => $"{Year} {Make} {Model}";


}

In questo caso è opportuno non fare affidamento sull'ereditarietà per rappresentare marche e modelli specifici
di automobili. Non è ad esempio necessario definire un tipo Packard che rappresenta automobili prodotte dalla
casa automobilistica Packard. È invece possibile rappresentarle creando un oggetto Automobile con i valori
appropriati passati al costruttore della classe, come illustrato nell'esempio seguente.

using System;

public class Example


{
public static void Main()
{
var packard = new Automobile("Packard", "Custom Eight", 1948);
Console.WriteLine(packard);
}
}
// The example displays the following output:
// 1948 Packard Custom Eight

È preferibile applicare una relazione "è un" basata sull'ereditarietà a una classe di base e a classi derivate che
aggiungono altri membri alla classe di base o che richiedono funzionalità aggiuntive non presenti nella classe di
base.
Progettazione della classe di base e delle classi derivate
Si esaminerà ora il processo di progettazione della classe di base e delle relative classi derivate. In questa
sezione si definirà una classe di base, Publication , che rappresenta una pubblicazione di qualsiasi tipo, ad
esempio un libro, una rivista, un giornale, un journal, un articolo e così via. Si definirà anche una Book classe
che deriva da Publication . L'esempio può essere facilmente esteso alla definizione di altre classi derivate, ad
esempio Magazine , Journal , Newspaper e Article .
Classe di base Publication
Per progettare la classe Publication , è necessario prendere alcune decisioni di progettazione:
Quali membri includere nella classe di base Publication e se i membri Publication forniscono le
implementazioni del metodo o se Publication è una classe di base astratta che funge da modello per le
relative classi derivate.
In questo caso la classe Publication fornirà le implementazioni del metodo. La sezione Progettazione di
classi di base astratte e delle relative classi derivate contiene un esempio in cui viene usata una classe
base astratta per definire i metodi di cui le classi derivate devono eseguire l'override. Le classi derivate
possono fornire qualsiasi implementazione adatta al tipo derivato.
La possibilità di riutilizzare il codice, ovvero il fatto che più classi derivate condividano la dichiarazione e
l'implementazione dei metodi della classe di base e non ne richiedano l'override, è un vantaggio delle
classi di base non astratte. È quindi necessario aggiungere membri a Publication se è probabile che il
relativo codice venga condiviso da alcuni o dalla maggior parte dei tipi specializzati Publication . Se le
implementazioni delle classi di base non vengono eseguite in modo efficiente, sarà necessario fornire
implementazioni di membri pressoché identiche nelle classi derivate anziché una singola
implementazione nella classe di base. La necessità di mantenere il codice duplicato in più posizioni è una
potenziale fonte di bug.
Per ottimizzare il riutilizzo del codice e per creare una gerarchia di ereditarietà logica e intuitiva, è
opportuno assicurarsi che nella classe Publication vengano inclusi solo i dati e le funzionalità comuni a
tutte le pubblicazioni o alla maggior parte di esse. Le classi derivate implementano quindi i membri che
sono univoci per i tipi di pubblicazione specifici che rappresentano.
Fino a che punto estendere la gerarchia di classi. È necessario decidere se si vuole sviluppare una
gerarchia di tre o più classi, anziché semplicemente una classe di base e una o più classi derivate.
Publication può ad esempio essere una classe di base di Periodical , che a sua volta è una classe di
base di Magazine , Journal e Newspaper .
Per questo esempio si userà la piccola gerarchia di una classe Publication e di una singola classe
derivata Book . È possibile estendere facilmente l'esempio per creare una serie di classi aggiuntive che
derivano da Publication , ad esempio Magazine e Article .
Se è opportuno creare un'istanza della classe di base. In caso contrario, è necessario applicare alla classe
la parola chiave abstract. Altrimenti è possibile creare un'istanza della classe Publication chiamando il
relativo costruttore di classe. Se si prova a creare un'istanza di una classe contrassegnata con la parola
chiave abstract da una chiamata diretta al costruttore della classe, il compilatore C# genera l'errore
CS0144, "Non è possibile creare un'istanza della classe o dell'interfaccia astratta". Se si prova a creare
un'istanza della classe usando la reflection, il metodo di reflection genera un'eccezione
MemberAccessException.
Per impostazione predefinita, è possibile creare un'istanza della classe di base chiamando il relativo
costruttore di classe. Non è necessario definire in modo esplicito un costruttore di classe. Se non è
presente un costruttore nel codice sorgente della classe di base, il compilatore C# ne fornisce
automaticamente uno predefinito (senza parametri).
Per questo esempio, la classe Publication verrà contrassegnata come abstract in modo che non sia
possibile crearne un'istanza. Una classe abstract senza metodi abstract indica che questa classe
rappresenta un concetto astratto condiviso tra diverse classi concrete (ad esempio Book , Journal ).
Se le classi derivate devono ereditare l'implementazione della classe di base di un membro specifico, o se
possono eseguire l'override dell'implementazione della classe di base, o ancora se devono fornire
un'implementazione. La parola chiave abstract si usa per forzare le classi derivate a fornire
un'implementazione. Usare la parola chiave virtual per consentire alle classi derivate di eseguire
l'override di un metodo della classe di base. Per impostazione predefinita, non è possibile eseguire
l'override dei metodi definiti nella classe di base.
La classe Publication non ha metodi abstract , ma la classe stessa è abstract .
Se una classe derivata rappresenta la classe finale nella gerarchia di ereditarietà e non può essere usata
come classe di base per altre classi derivate. Per impostazione predefinita, qualsiasi classe può essere
usata come classe di base. È possibile applicare la parola chiave sealed per indicare che una classe non
può essere usata come classe di base per altre classi. Se si prova a stabilire una relazione di derivazione
da una classe sealed, viene generato l'errore del compilatore CS0509, "non può derivare dal tipo sealed
<typeName>".
Per esempio, contrassegnare la classe derivata come sealed .
L'esempio seguente illustra il codice sorgente della classe Publication , nonché un'enumerazione
PublicationType restituita dalla proprietà Publication.PublicationType . Oltre ai membri che eredita da Object,
la classe Publication definisce i membri univoci e gli override dei membri seguenti:

using System;

public enum PublicationType { Misc, Book, Magazine, Article };

public abstract class Publication


{
private bool published = false;
private DateTime datePublished;
private int totalPages;

public Publication(string title, string publisher, PublicationType type)


{
if (String.IsNullOrWhiteSpace(publisher))
throw new ArgumentException("The publisher is required.");
Publisher = publisher;

if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;

Type = type;
}

public string Publisher { get; }

public string Title { get; }

public PublicationType Type { get; }

public string CopyrightName { get; private set; }

public int CopyrightDate { get; private set; }

public int Pages


{
get { return totalPages; }
set
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("The number of pages cannot be zero or negative.");
totalPages = value;
}
}

public string GetPublicationDate()


{
if (!published)
return "NYP";
else
return datePublished.ToString("d");
}

public void Publish(DateTime datePublished)


{
published = true;
this.datePublished = datePublished;
}

public void Copyright(string copyrightName, int copyrightDate)


{
if (String.IsNullOrWhiteSpace(copyrightName))
throw new ArgumentException("The name of the copyright holder is required.");
CopyrightName = copyrightName;

int currentYear = DateTime.Now.Year;


if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
throw new ArgumentOutOfRangeException($"The copyright year must be between {currentYear - 10} and
{currentYear + 1}");
CopyrightDate = copyrightDate;
}

public override string ToString() => Title;


}

Un costruttore
Poiché la classe Publication è abstract , non è possibile crearne un'istanza direttamente dal codice,
come nell'esempio seguente:

var publication = new Publication("Tiddlywinks for Experts", "Fun and Games",


PublicationType.Book);

Il relativo costruttore di istanze può essere tuttavia chiamato direttamente dai costruttori delle classi
derivate, come illustrato dal codice sorgente della classe Book .
Due proprietà relative alla pubblicazione
Title è una proprietà String di sola lettura il cui valore viene fornito chiamando il costruttore
Publication .

Pages è una proprietà Int32 di lettura/scrittura che indica il numero totale di pagine contenute nella
pubblicazione. Il valore viene archiviato in un campo privato denominato totalPages . Deve essere un
numero positivo, altrimenti viene generata un'eccezione ArgumentOutOfRangeException.
Membri relativi all'editore
Due proprietà di sola lettura, Publisher e Type . I valori sono forniti in origine tramite la chiamata al
costruttore di classe Publication .
Membri relativi alla pubblicazione
Due metodi, Publish e GetPublicationDate , impostano e restituiscono la data di pubblicazione. Il
metodo Publish imposta un flag privato published su true quando viene chiamato e assegna la data
passata come argomento al campo privato datePublished . Il metodo GetPublicationDate restituisce la
stringa "NYP" se il flag published è false e il valore del campo datePublished se è true .
Membri relativi al copyright
Il metodo Copyright accetta come argomenti il nome del titolare del copyright e l'anno del copyright e li
assegna alle proprietà CopyrightName e CopyrightDate .
Override del metodo ToString

Se un tipo non esegue l'override del metodo Object.ToString, restituisce il nome completo del tipo, che è
pressoché inutile per distinguere un'istanza da un'altra. La classe Publication esegue l'override di
Object.ToString per restituire il valore della proprietà Title .

La figura seguente illustra la relazione tra la classe di base Publication e la relativa classe Object ereditata in
modo implicito.

Classe Book

La classe Book rappresenta un libro come tipo specializzato di pubblicazione. L'esempio seguente illustra il
codice sorgente della classe Book .
using System;

public sealed class Book : Publication


{
public Book(string title, string author, string publisher) :
this(title, String.Empty, author, publisher)
{ }

public Book(string title, string isbn, string author, string publisher) : base(title, publisher,
PublicationType.Book)
{
// isbn argument must be a 10- or 13-character numeric string without "-" characters.
// We could also determine whether the ISBN is valid by comparing its checksum digit
// with a computed checksum.
//
if (! String.IsNullOrEmpty(isbn)) {
// Determine if ISBN length is correct.
if (! (isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
ulong nISBN = 0;
if (! UInt64.TryParse(isbn, out nISBN))
throw new ArgumentException("The ISBN can consist of numeric characters only.");
}
ISBN = isbn;

Author = author;
}

public string ISBN { get; }

public string Author { get; }

public Decimal Price { get; private set; }

// A three-digit ISO currency symbol.


public string Currency { get; private set; }

// Returns the old price, and sets a new price.


public Decimal SetPrice(Decimal price, string currency)
{
if (price < 0)
throw new ArgumentOutOfRangeException("The price cannot be negative.");
Decimal oldValue = Price;
Price = price;

if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-character string.");
Currency = currency;

return oldValue;
}

public override bool Equals(object obj)


{
Book book = obj as Book;
if (book == null)
return false;
else
return ISBN == book.ISBN;
}

public override int GetHashCode() => ISBN.GetHashCode();

public override string ToString() => $"{(String.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}
Oltre ai membri che eredita da Publication , la classe Book definisce i membri univoci e gli override dei
membri seguenti:
Due costruttori
I due costruttori Book condividono tre parametri comuni. Due, title e publisher, corrispondono ai
parametri del costruttore Publication . Il terzo è author, che viene archiviato in una proprietà Author
pubblica non modificabile. Un costruttore include un parametro isbn, che viene archiviato nella proprietà
automatica ISBN .
Il primo costruttore usa la parola chiave this per chiamare l'altro costruttore. Il concatenamento di
costruttori è un modello comune nella definizione dei costruttori. I costruttori con meno parametri
forniscono i valori predefiniti quando chiamano il costruttore con il maggior numero di parametri.
Il secondo costruttore usa la parola chiave base per passare il titolo e il nome dell'editore al costruttore
della classe di base. Se non si esegue una chiamata esplicita a un costruttore della classe di base nel
codice sorgente, il compilatore C# effettua automaticamente una chiamata al costruttore della classe di
base predefinito o senza parametri.
Una proprietà ISBN di sola lettura, che restituisce il numero ISBN (International Standard Book Number)
dell'oggetto Book , un numero univoco a 10 o 13 cifre. Il numero ISBN viene fornito come argomento a
uno dei costruttori Book . Il numero ISBN viene archiviato in un campo sottostante privato, che viene
generato automaticamente dal compilatore.
Una proprietà Author di sola lettura. Il nome dell'autore viene fornito come argomento a entrambi i
costruttori Book e viene archiviato nella proprietà.
Due proprietà di sola lettura relative ai prezzi, Price e Currency . I relativi valori vengono forniti come
argomenti in una chiamata al metodo SetPrice . La proprietà Currency è il simbolo di valuta ISO a tre
cifre, ad esempio USD per il dollaro statunitense. I simboli di valuta ISO possono essere recuperati dalla
proprietà ISOCurrencySymbol. Entrambe queste proprietà sono esternamente di sola lettura, ma possono
essere entrambe impostate dal codice nella classe Book .
Un metodo SetPrice , che imposta i valori delle proprietà Price e Currency . Questi valori vengono
restituiti dalle stesse proprietà.
Esegue l'override del metodo ToString ereditato da Publication e dei metodi Object.Equals(Object) e
GetHashCode (ereditati da Object).
A meno che non venga sottoposto a override, il metodo Object.Equals(Object) verifica l'uguaglianza dei
riferimenti. Ciò significa che due variabili di oggetto sono considerate uguali se fanno riferimento allo
stesso oggetto. Nella classe Book , d'altra parte, due oggetti Book sono considerati uguali se hanno lo
stesso ISBN.
Quando si esegue l'override del metodo Object.Equals(Object), è necessario eseguire l'override anche del
metodo GetHashCode, che restituisce un valore che verrà usato dal runtime per archiviare elementi in
raccolte con hash e facilitarne così il recupero. Il codice hash deve restituire un valore coerente con il test
di uguaglianza. Poiché Object.Equals(Object) è stato sottoposto a override per restituire true se le
proprietà ISBN di due oggetti Book sono uguali, si restituisce il codice hash calcolato chiamando il
metodo GetHashCode della stringa restituita dalla proprietà ISBN .

La figura seguente illustra la relazione tra la classe Book e la relativa classe di base Publication .
È ora possibile creare un'istanza di un oggetto Book , richiamarne i membri univoci ed ereditati e passarla come
argomento a un metodo che prevede un parametro di tipo Publication o Book , come illustrato nell'esempio
seguente.
using System;
using static System.Console;

public class Example


{
public static void Main()
{
var book = new Book("The Tempest", "0971655819", "Shakespeare, William",
"Public Domain Press");
ShowPublicationInfo(book);
book.Publish(new DateTime(2016, 8, 18));
ShowPublicationInfo(book);

var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
Write($"{book.Title} and {book2.Title} are the same publication: " +
$"{((Publication) book).Equals(book2)}");
}

public static void ShowPublicationInfo(Publication pub)


{
string pubDate = pub.GetPublicationDate();
WriteLine($"{pub.Title}, " +
$"{(pubDate == "NYP" ? "Not Yet Published" : "published on " + pubDate):d} by
{pub.Publisher}");
}
}
// The example displays the following output:
// The Tempest, Not Yet Published by Public Domain Press
// The Tempest, published on 8/18/2016 by Public Domain Press
// The Tempest and The Tempest are the same publication: False

Progettazione di classi di base astratte e delle relative classi derivate


Nell'esempio precedente si è definita una classe di base che fornisce un'implementazione per una serie di
metodi per consentire alle classi derivate di condividere il codice. In molti casi, tuttavia, la classe di base non
deve fornire un'implementazione. Al contrario, è una classe astratta che dichiara dei metodi astratti e funge da
modello che definisce i membri che ogni classe derivata deve implementare. Per una classe di base astratta
l'implementazione di ogni tipo derivato è in genere univoca per quel tipo. La classe è stata contrassegnata con la
parola chiave abstract perché non si è ritenuto logico creare un'istanza di un oggetto Publication , anche se la
classe ha fornito implementazioni di funzionalità comuni alle pubblicazioni.
Ogni forma geometrica bidimensionale chiusa include ad esempio due proprietà: l'area, l'estensione interna
della forma e il perimetro, ovvero la lunghezza totale dei bordi della forma. La modalità di calcolo di queste
proprietà dipende tuttavia completamente dalla forma specifica. La formula per calcolare il perimetro (o
circonferenza) di un cerchio è ad esempio diversa da quella usata per un triangolo. La classe Shape è una classe
abstract con i metodi abstract . Ciò indica che le classi derivate condividono la stessa funzionalità, ma
implementano questa funzionalità in modo diverso.
L'esempio seguente definisce una classe di base astratta denominata Shape che definisce due proprietà: Area e
Perimeter . Oltre a contrassegnare la classe con la parola chiave abstract, si contrassegna con la parola chiave
abstract anche ogni membro dell'istanza. In questo caso Shape esegue anche l'override del metodo
Object.ToString per restituire il nome del tipo, anziché il nome completo. Definisce inoltre due membri statici,
GetArea e GetPerimeter , che consentono ai chiamanti di recuperare facilmente l'area e il perimetro di
un'istanza di qualsiasi classe derivata. Quando si passa un'istanza di una classe derivata a uno di questi metodi, il
runtime chiama l'override del metodo della classe derivata.
using System;

public abstract class Shape


{
public abstract double Area { get; }

public abstract double Perimeter { get; }

public override string ToString() => GetType().Name;

public static double GetArea(Shape shape) => shape.Area;

public static double GetPerimeter(Shape shape) => shape.Perimeter;


}

È quindi possibile derivare da Shape alcune classi che rappresentano forme specifiche. Nell'esempio seguente
vengono definite tre classi, Triangle , Rectangle e Circle . Ogni classe usa una formula univoca per calcolare
l'area e il perimetro della forma specifica. Alcune classi derivate definiscono anche le proprietà, ad esempio
Rectangle.Diagonal e Circle.Diameter , che sono univoche per la forma che rappresentano.
using System;

public class Square : Shape


{
public Square(double length)
{
Side = length;
}

public double Side { get; }

public override double Area => Math.Pow(Side, 2);

public override double Perimeter => Side * 4;

public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);


}

public class Rectangle : Shape


{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}

public double Length { get; }

public double Width { get; }

public override double Area => Length * Width;

public override double Perimeter => 2 * Length + 2 * Width;

public bool IsSquare() => Length == Width;

public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);


}

public class Circle : Shape


{
public Circle(double radius)
{
Radius = radius;
}

public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);

public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);

// Define a circumference, since it's the more familiar term.


public double Circumference => Perimeter;

public double Radius { get; }

public double Diameter => Radius * 2;


}

Nell'esempio seguente vengono usati gli oggetti derivati da Shape . Viene creata un'istanza di una matrice di
oggetti derivati da Shape e vengono chiamati i metodi statici della classe Shape , che esegue il wrapping dei
valori restituiti della proprietà Shape . Il runtime recupera i valori dalle proprietà dei tipi derivati sottoposte a
override. Nell'esempio viene anche eseguito il cast di ogni oggetto Shape nella matrice al relativo tipo derivato
e, se il cast ha esito positivo, vengono recuperate le proprietà di quella sottoclasse specifica di Shape .
using System;

public class Example


{
public static void Main()
{
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (var shape in shapes) {
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
var rect = shape as Rectangle;
if (rect != null) {
Console.WriteLine($" Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
continue;
}
var sq = shape as Square;
if (sq != null) {
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
}
}
}
}
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85

Vedere anche
Ereditarietà (Guida per programmatori C#)
Usare LINQ (Language-Integrated Query)
02/11/2020 • 28 minutes to read • Edit Online

Introduzione
Questa esercitazione illustra le funzionalità disponibili in .NET Core e nel linguaggio C#. Verrà descritto come:
Generare sequenze con LINQ.
Scrivere metodi che possono essere usati facilmente nelle query LINQ.
Distinguere tra valutazione eager e Lazy.
Si apprenderanno queste tecniche creando un'applicazione che illustra una delle abilità di base di un
prestigiatore: il miscuglio faro. In breve, il miscuglio faro è una tecnica che consiste nel tagliare un mazzo di
carte esattamente a metà e quindi nel sovrapporre alternativamente le carte delle due metà per ricostruire il
mazzo originale.
I prestigiatori adottano questa tecnica perché, dopo ogni miscuglio, ciascuna carta si trova in una posizione nota
e le carte vengono ordinate in base a uno schema ripetitivo.
Ai fini dell'esercitazione, questa tecnica offre un modo scherzoso per illustrare la manipolazione di sequenze di
dati. L'applicazione da compilare costruisce un mazzo di schede e quindi esegue una sequenza di shuffle,
scrivendo ogni volta la sequenza. Si confronterà inoltre l'ordine aggiornato con quello originale.
Questa esercitazione prevede diversi passaggi. Dopo ogni passaggio, è possibile eseguire l'applicazione e
verificare lo stato di avanzamento. È anche possibile vedere l'esempio completo disponibile nel repository
dotnet/samples su GitHub. Per istruzioni sul download, vedere Esempi ed esercitazioni.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET core. È possibile trovare le istruzioni di
installazione nella pagina di download di .NET Core . È possibile eseguire questa applicazione in Windows,
Ubuntu Linux o OS X oppure in un contenitore docker. È necessario installare l'editor di codice preferito. Le
descrizioni seguenti usano Visual Studio Code un editor multipiattaforma open source. ma è possibile usare gli
strumenti con cui si ha maggiore familiarità.

Creare l'applicazione
Il primo passaggio consiste nel creare una nuova applicazione. Aprire un prompt dei comandi e creare una
nuova directory per l'applicazione, impostandola come directory corrente. Digitare il comando
dotnet new console al prompt dei comandi Questa operazione crea i file iniziali per un'applicazione "Hello
World" di base.
Se non si è mai usato C#, questa esercitazione illustra la struttura di un programma C#. È possibile leggerla e
tornare qui per ottenere altre informazioni su LINQ.

Creare il set di dati


Prima di iniziare, verificare che le righe seguenti si trovino all'inizio del file Program.cs generato da
dotnet new console :
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;

Se queste tre righe (istruzioni using ) non sono all'inizio del file, il programma non viene compilato.
Ora che si dispone di tutti i riferimenti necessari, considerare quali elementi costituiscono un mazzo di carte.
Generalmente un mazzo di carte da gioco ha quattro semi, ognuno dei quali ha tredici valori. Normalmente si
prenderebbe in considerazione l'idea di creare subito una classe Card e popolare una raccolta di oggetti Card
manualmente. Ma con LINQ è possibile creare un mazzo di carte in maniera più concisa. Invece di creare una
classe Card , è possibile creare due sequenze che rappresentino rispettivamente i semi e i valori. Si creerà una
coppia molto semplice di metodi Iterator che genereranno i valori e i semi come interfacce IEnumerable<T> di
stringhe:

// Program.cs
// The Main() method

static IEnumerable<string> Suits()


{
yield return "clubs";
yield return "diamonds";
yield return "hearts";
yield return "spades";
}

static IEnumerable<string> Ranks()


{
yield return "two";
yield return "three";
yield return "four";
yield return "five";
yield return "six";
yield return "seven";
yield return "eight";
yield return "nine";
yield return "ten";
yield return "jack";
yield return "queen";
yield return "king";
yield return "ace";
}

Inserire questi metodi sotto il metodo Main nel file Program.cs . Questi due metodi usano entrambi la sintassi
yield return per generare una sequenza durante l'esecuzione. Il compilatore crea un oggetto che implementa
IEnumerable<T> e genera la sequenza di stringhe a mano a mano che vengono richieste.
Usare ora questi metodi Iterator per creare il mazzo di carte. Si inserirà la query LINQ nel metodo Main . Ecco
come appare:
// Program.cs
static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

// Display each card that we've generated and placed in startingDeck in the console
foreach (var card in startingDeck)
{
Console.WriteLine(card);
}
}

Le clausole from multiple generano un SelectMany che crea una singola sequenza tramite la combinazione di
ogni elemento nella prima sequenza con ogni elemento nella seconda. L'ordine è importante ai fini di questa
esercitazione. Il primo elemento nella prima sequenza di origine (semi) viene combinato con ogni elemento
della seconda sequenza (valori). Si ottengono così le 13 carte appartenenti al primo seme. Il processo viene
ripetuto con ogni elemento della prima sequenza, ovvero i semi. Il risultato finale è un mazzo di carte ordinato
in base ai semi e quindi in base ai valori.
È importante tenere presente che, sia che si scelga di scrivere la query LINQ nella sintassi di query usata sopra o
di usare invece la sintassi del metodo, è sempre possibile passare da un formato di sintassi all'altro. La query
riportata sopra, scritta nella sintassi di query, può essere scritta nella sintassi del metodo nel modo seguente:

var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new { Suit = suit, Rank = rank }));

Il compilatore converte le istruzioni LINQ scritte con la sintassi di query nella sintassi del metodo equivalente.
Pertanto, indipendentemente dalla sintassi scelta, le due versioni della query producono lo stesso risultato.
Scegliere la sintassi più adatta per la propria situazione: ad esempio, se si lavora in un team in cui alcuni membri
non hanno dimestichezza con la sintassi del metodo, preferire la sintassi di query.
Andare avanti ed eseguire l'esempio che si è creato finora. Verranno visualizzate le 52 carte del mazzo. Può
essere molto utile eseguire questo esempio in un debugger per osservare come vengono eseguiti i metodi
Suits() e Ranks() . È possibile vedere chiaramente che in ogni sequenza ciascuna stringa viene generata solo
quando è necessario.

Modificare l'ordine
A questo punto occorre concentrarsi sul modo in cui si mischieranno le carte nel mazzo. Il primo passaggio
consiste nel tagliare il mazzo in due. I metodi Take e Skip inclusi nelle API LINQ offrono questa funzionalità.
Inserirli sotto il ciclo foreach :

// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

// 52 cards in a deck, so 52 / 2 = 26
var top = startingDeck.Take(26);
var bottom = startingDeck.Skip(26);
}

Non esiste tuttavia un metodo per mischiare le carte nella libreria standard, quindi è necessario scriverne uno
personalizzato. Il metodo che verrà creato illustra diverse tecniche che verranno usate con programmi basati su
LINQ, quindi ogni parte di questo processo verrà spiegata in passaggi.
Per aggiungere alcune funzionalità per l'interazione con l'interfaccia IEnumerable<T> che verrà restituita dalle
query LINQ,è necessario scrivere dei tipi speciali di metodi detti metodi di estensione. In breve, un metodo di
estensione è uno speciale metodo statico che aggiunge nuove funzionalità a un tipo già esistente senza bisogno
di modificare il tipo originale a cui si vogliono aggiungere funzionalità.
Assegnare ai metodi di estensione una nuova posizione aggiungendo al programma un nuovo file di classe
statica denominato Extensions.cs , quindi iniziare a compilare il primo metodo di estensione:

// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqFaroShuffle
{
public static class Extensions
{
public static IEnumerable<T> InterleaveSequenceWith<T>(this IEnumerable<T> first, IEnumerable<T>
second)
{
// Your implementation will go here soon enough
}
}
}

Osservare per un momento la firma del metodo, in particolare i parametri:

public static IEnumerable<T> InterleaveSequenceWith<T> (this IEnumerable<T> first, IEnumerable<T> second)

È possibile notare l'aggiunta del modificatore this nel primo argomento del metodo. Ciò significa che il
metodo viene chiamato come se fosse un membro del tipo del primo argomento. Questa dichiarazione di
metodo segue anche un termine standard in cui i tipi di input e output sono IEnumerable<T> . Ciò consente la
concatenazione dei metodi LINQ per l'esecuzione di query più complesse.
Naturalmente, dato che il mazzo è stato diviso in due, occorrerà unire queste due metà. Nel codice, ciò significa
che verranno enumerate entrambe le sequenze acquisite in Take e Skip contemporaneamente, interleaving gli
elementi e la creazione di una sequenza: il mazzo di schede attualmente casuale. Per scrivere un metodo LINQ
utilizzabile con le due sequenze è necessario comprendere il funzionamento di IEnumerable<T>.
L'interfaccia IEnumerable<T> ha un unico metodo: GetEnumerator. L'oggetto restituito da GetEnumerator ha un
metodo per passare all'elemento successivo e una proprietà che recupera l'elemento corrente nella sequenza. Si
useranno questi due membri per enumerare la raccolta e restituire gli elementi. Questo metodo Interleave sarà
un metodo iteratore. Di conseguenza, anziché creare una raccolta e restituirla, si userà la sintassi yield return
mostrata in precedenza.
Questa è l'implementazione del metodo:

public static IEnumerable<T> InterleaveSequenceWith<T>


(this IEnumerable<T> first, IEnumerable<T> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();

while (firstIter.MoveNext() && secondIter.MoveNext())


{
yield return firstIter.Current;
yield return secondIter.Current;
}
}

Dopo avere scritto questo metodo, tornare al metodo Main e mischiare una volta il mazzo:

// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

var top = startingDeck.Take(26);


var bottom = startingDeck.Skip(26);
var shuffle = top.InterleaveSequenceWith(bottom);

foreach (var c in shuffle)


{
Console.WriteLine(c);
}
}

Confronti
Quante volte è necessario mischiare il mazzo per ripristinare l'ordine originale? Per scoprirlo è necessario
scrivere un metodo che determina se due sequenze sono uguali. Una volta creato tale metodo, sarà necessario
inserire in un ciclo il codice per mischiare il mazzo e verificare quando viene ripristinato l'ordine originale.
Scrivere un metodo per determinare se due sequenze sono uguali è un'operazione piuttosto intuitiva. La
struttura è simile a quella del metodo usato per mischiare il mazzo. In questo caso, però, anziché eseguire
un'istruzione yield return in ogni elemento, si confronteranno gli elementi corrispondenti di ogni sequenza. Al
termine dell'enumerazione dell'intera sequenza, se ogni elemento corrisponde, le sequenze sono identiche:

public static bool SequenceEquals<T>


(this IEnumerable<T> first, IEnumerable<T> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();

while (firstIter.MoveNext() && secondIter.MoveNext())


{
if (!firstIter.Current.Equals(secondIter.Current))
{
return false;
}
}

return true;
}

Questo esempio illustra un secondo termine del linguaggio LINQ: i metodi terminali. Questi metodi accettano
una sequenza come input (o, in questo caso, due sequenze) e restituiscono un singolo valore scalare. Quando si
usa un metodo terminale, questo è sempre il metodo finale in una catena di metodi per una query LINQ, da qui
il nome "terminale".
È possibile notare questo comportamento nella pratica quando si usa il metodo per determinare quando viene
ripristinato l'ordine originale del mazzo. Inserire in un ciclo il codice per mischiare il mazzo e arrestare
l'esecuzione quando viene ripristinato l'ordine originale della sequenza applicando il metodo SequenceEquals() .
È possibile notare che questo sarebbe sempre il metodo finale in qualsiasi query poiché restituisce un singolo
valore anziché una sequenza:

// Program.cs
static void Main(string[] args)
{
// Query for building the deck

// Shuffling using InterleaveSequenceWith<T>();

var times = 0;
// We can re-use the shuffle variable from earlier, or you can make a new one
shuffle = startingDeck;
do
{
shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));

foreach (var card in shuffle)


{
Console.WriteLine(card);
}
Console.WriteLine();
times++;

} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Eseguire il codice ottenuto fino a questo momento e prendere nota del modo in cui il mazzo viene riordinato
ogni volta che viene mischiato. Dopo 8 volte (iterazioni del ciclo do-while), il mazzo torna alla configurazione
originale che aveva quando è stato creato dalla query LINQ iniziale.
Ottimizzazioni
L'esempio creato finora mischia le carte esterne, lasciando le carte in cima e in fondo al mazzo sempre nella
stessa posizione, ma è possibile introdurre una variazione e mischiare anche le carte interne, cambiando la
posizione di tutte e 52 le carte. Per mischiare il mazzo in questo modo, si alternano le carte in modo che la
prima carta della metà inferiore diventi la prima carta del mazzo. Di conseguenza, l'ultima carta della metà
superiore diventerà l'ultima carta del mazzo. Si tratta di una semplice modifica a una singola riga di codice.
Aggiornare la query corrente scambiando le posizioni di Take e Skip. In questo modo si cambia l'ordine della
metà superiore e di quella inferiore del mazzo:

shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));

Eseguire nuovamente il programma. Si noterà che il ripristino dell'ordine del mazzo richiede 52 iterazioni. Nel
corso dell'esecuzione del programma si inizierà a notare anche un calo significativo delle prestazioni.
Questo problema può essere dovuto a vari motivi. Una delle cause principali di questo calo delle prestazioni
consiste nell'uso inefficiente della valutazione lazy.
In breve, la valutazione lazy indica che la valutazione di un'istruzione non viene eseguita finché il suo valore non
è necessario. Le query LINQ sono istruzioni che vengono valutate in modalità lazy. Le sequenze vengono
generate solo quando vengono richiesti gli elementi. Questo è in genere uno dei principali vantaggi di LINQ, ma
in un programma di questo tipo può tradursi in una crescita esponenziale del tempo di esecuzione.
Tenere presente che il mazzo originale è stato generato con una query LINQ. e, ogni volta che si mischiano le
carte, il mazzo viene generato eseguendo tre query LINQ sul mazzo precedente. Tutte queste operazioni sono
eseguite in modalità lazy e quindi vengono ripetute ogni volta che è richiesta la sequenza. Quando si giunge alla
cinquantaduesima iterazione, il mazzo originale è stato rigenerato un numero di volte molto elevato. Per
comprendere più facilmente questo comportamento è possibile scrivere un log. Si potrà così correggere il
problema.
Nel file Extensions.cs digitare o copiare il metodo riportato di seguito. Questo metodo di estensione crea un
nuovo file denominato debug.log nella directory del progetto e registra la query attualmente in esecuzione nel
file di log. Questo metodo di estensione può essere aggiunto a qualsiasi query per indicare che la query è stata
eseguita.

public static IEnumerable<T> LogQuery<T>


(this IEnumerable<T> sequence, string tag)
{
// File.AppendText creates a new file if the file doesn't exist.
using (var writer = File.AppendText("debug.log"))
{
writer.WriteLine($"Executing Query {tag}");
}

return sequence;
}

Si noterà una sottolineatura ondulata rossa sotto File , per indicare che non esiste. Non viene compilato,
perché il compilatore non riconosce File . Per risolvere questo problema, assicurarsi di aggiungere la seguente
riga di codice sotto la prima riga in Extensions.cs :

using System.IO;

Questo dovrebbe risolvere il problema e far scomparire l'indicatore di errore rosso.


Instrumentare quindi la definizione di ogni query con un messaggio di log:

// Program.cs
public static void Main(string[] args)
{
var startingDeck = (from s in Suits().LogQuery("Suit Generation")
from r in Ranks().LogQuery("Rank Generation")
select new { Suit = s, Rank = r }).LogQuery("Starting Deck");

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

Console.WriteLine();
var times = 0;
var shuffle = startingDeck;

do
{
// Out shuffle
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26)
.LogQuery("Bottom Half"))
.LogQuery("Shuffle");
*/

// In shuffle
shuffle = shuffle.Skip(26).LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle");

foreach (var c in shuffle)


{
Console.WriteLine(c);
}

times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Si noti che la registrazione non viene eseguita ogni volta che si accede a una query, ma solo quando si crea la
query originale. L'esecuzione del programma richiede ancora molto tempo, ma ora si è individuato il motivo del
problema. Se il tempo necessario per mischiare anche le carte interne con la registrazione attivata è eccessivo,
limitarsi a mischiare quelle esterne. Gli effetti della valutazione lazy saranno ancora visibili. In un'unica
esecuzione del programma verranno eseguite 2592 query, inclusa la generazione di tutti i semi e valori.
È possibile migliorare le prestazioni del codice per ridurre il numero di esecuzioni eseguite. Una semplice
correzione consiste nel memorizzare nella cache i risultati della query LINQ originale che costruisce il mazzo di
carte. Attualmente si rieseguono le query ad ogni iterazione del ciclo do-while, ricostruendo il mazzo di carte e
rimescolandolo ogni volta. Per memorizzare il mazzo di carte nella cache, è possibile sfruttare i metodi LINQ
ToArray e ToList. Accodandoli alle query, eseguiranno le stesse azioni per cui sono stati creati, ma ora
archivieranno i risultati in una matrice o un elenco, a seconda del metodo che si è scelto di chiamare. Accodare il
metodo LINQ ToArray a entrambe le query ed eseguire di nuovo il programma:
public static void Main(string[] args)
{
var startingDeck = (from s in Suits().LogQuery("Suit Generation")
from r in Ranks().LogQuery("Value Generation")
select new { Suit = s, Rank = r })
.LogQuery("Starting Deck")
.ToArray();

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

Console.WriteLine();

var times = 0;
var shuffle = startingDeck;

do
{
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom Half"))
.LogQuery("Shuffle")
.ToArray();
*/

shuffle = shuffle.Skip(26)
.LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle")
.ToArray();

foreach (var c in shuffle)


{
Console.WriteLine(c);
}

times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Ora il numero di query per mischiare le carte esterne è ridotto a 30. Eseguire nuovamente il programma per
mischiare anche le carte interne e si noteranno miglioramenti analoghi: ora vengono eseguite 162 query.
Questo esempio è stato progettato per mettere in evidenza i casi d'uso in cui la valutazione lazy può causare
problemi di prestazioni. Sebbene sia importante capire dove la valutazione lazy può influire sulle prestazioni del
codice, è altrettanto importante comprendere che non tutte le query devono essere eseguite in modalità eager.
La riduzione delle prestazioni che si verifica senza usare ToArray avviene perché ogni nuova configurazione del
mazzo di carte è basata sulla configurazione precedente. Quando si usa la valutazione lazy, ogni nuova
configurazione del mazzo è basata sul mazzo originale, anche eseguendo il codice che ha creato startingDeck .
Questo comportamento determina una grande quantità di operazioni aggiuntive.
In pratica, per alcuni algoritmi è più efficiente la valutazione eager, mentre per altri è preferibile la valutazione
lazy. Per l'uso quotidiano, quest'ultima rappresenta in genere la scelta migliore quando l'origine dati è costituita
da un processo separato, ad esempio un motore di database. Per i database, la valutazione lazy consente alle
query più complesse di eseguire un solo round trip al processo di database e di tornare al resto del codice. LINQ
offre la stessa flessibilità sia che si scelga di usare la valutazione lazy o eager. Misurare pertanto i processi e
scegliere il tipo di valutazione che offre le prestazioni migliori.

Conclusioni
In questo progetto sono stati illustrati gli argomenti seguenti:
Uso di query LINQ per aggregare i dati in una sequenza significativa
Scrittura di metodi di estensione per aggiungere funzionalità personalizzate alle query LINQ
Individuazione delle aree del codice in cui le query LINQ potrebbero riscontrare problemi di prestazioni, ad
esempio una riduzione della velocità
Valutazione lazy e valutazione eager relativamente alle query LINQ e implicazioni che potrebbero avere sulle
prestazioni delle query
Oltre a LINQ, è stata illustrata una tecnica usata dai prestigiatori per i trucchi con le carte. I prestigiatori usano il
miscuglio Faro perché possono controllare lo spostamento di ogni carta nel mazzo. È una tecnica che, per
mantenere la sua magia, dovrebbe restare nota a pochi.
Per altre informazioni su LINQ, vedere:
LINQ (Language-Integrated Query)
Introduzione a LINQ
Operazioni di query LINQ di base (C#)
Trasformazioni di dati con LINQ (C#)
Sintassi di query e sintassi di metodi in LINQ (C#)
Funzionalità di C# che supportano LINQ
Usare gli attributi in C#
02/11/2020 • 14 minutes to read • Edit Online

Gli attributi consentono di associare informazioni al codice in modo dichiarativo. Offrono anche un elemento
riutilizzabile che può essere applicato a vari tipi di destinazioni.
L'attributo [Obsolete] , ad esempio, può essere applicato a classi, struct, metodi, costruttori e altri elementi.
Dichiara che l'elemento è obsoleto. Spetta quindi al compilatore C# cercare l'attributo ed eseguire di
conseguenza le azioni appropriate.
In questa esercitazione si illustreranno le procedure necessarie per aggiungere attributi al codice, creare e usare
attributi personalizzati e usare alcuni attributi incorporati in .NET Core.

Prerequisiti
È necessario configurare il computer per l'esecuzione di .NET core. È possibile trovare le istruzioni di
installazione nella pagina di download di .NET Core . Questa applicazione può essere eseguita in Windows,
Ubuntu Linux, macOS o in un contenitore Docker. È necessario installare l'editor di codice preferito. Le
descrizioni riportate di seguito usano Visual Studio Code un editor multipiattaforma open source. ma è possibile
usare gli strumenti con cui si ha maggiore familiarità.

Creare l'applicazione
Dopo avere installato tutti gli strumenti, creare una nuova applicazione .NET Core. Per usare il generatore da riga
di comando, eseguire il comando seguente nella shell preferita:
dotnet new console

Questo comando creerà file di progetto .NET Core Bare Bones. Sarà necessario eseguire dotnet restore per
ripristinare le dipendenze richieste per la compilazione del progetto.
Non è necessario eseguire dotnet restore perché viene eseguito in modo implicito da tutti i comandi che
richiedono un ripristino, ad esempio dotnet new ,, dotnet build dotnet run , dotnet test , dotnet publish e
dotnet pack . Per disabilitare il ripristino implicito, usare l' --no-restore opzione.

Il dotnet restore comando è ancora utile in alcuni scenari in cui il ripristino esplicito è significativo, ad esempio
le compilazioni di integrazione continua in Azure DevOps Services o nei sistemi di compilazione che devono
controllare in modo esplicito quando si verifica il ripristino.
Per informazioni su come gestire i feed NuGet, vedere la dotnet restore documentazionedi.
Per eseguire il programma, usare dotnet run . Nella console viene visualizzato "Hello, World".

Come aggiungere attributi al codice


In C# gli attributi sono classi che ereditano dalla classe di base Attribute . Qualsiasi classe che eredita da
Attribute può essere usata come una sorta di "tag" in altre parti del codice. Si consideri, ad esempio, l'attributo
denominato ObsoleteAttribute , usato per segnalare che il codice è obsoleto e non deve più essere usato. È
possibile inserire questo attributo in una classe usando, ad esempio, parentesi quadre.
[Obsolete]
public class MyClass
{
}

Anche se la classe è chiamata ObsoleteAttribute , nel codice è necessario usare solo [Obsolete] . Questa è una
convenzione seguita da C#. Se si preferisce, è possibile usare il nome completo [ObsoleteAttribute] .
Quando si contrassegna una classe come obsoleta, è opportuno fornire alcune informazioni sul motivo per cui è
obsoleta e/o sull'oggetto da usare in alternativa. A questo scopo, passare un parametro stringa all'attributo
Obsolete.

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]


public class ThisClass
{
}

La stringa viene trasmessa come argomento a un costruttore ObsoleteAttribute , come se si scrivesse


var attr = new ObsoleteAttribute("some string") .

I parametri che è possibile passare a un costruttore di attributo sono limitati a tipi o valori letterali semplici:
bool, int, double, string, Type, enums, etc e matrici di questi tipi. Non è possibile usare un'espressione o una
variabile, ma è possibile usare parametri posizionali o denominati.

Come creare un attributo personalizzato


Creare un attributo è semplice come ereditarlo dalla classe di base Attribute .

public class MySpecialAttribute : Attribute


{
}

Con quanto creato in precedenza, è possibile ora usare [MySpecial] (o [MySpecialAttribute] ) come attributo in
un'altra posizione della codebase.

[MySpecial]
public class SomeOtherClass
{
}

Alcuni attributi presenti nella libreria di classi base .NET, come ObsoleteAttribute , attivano determinati
comportamenti nel compilatore. Qualsiasi attributo creato, tuttavia, svolge solo la funzione di metadato e non
genera alcun codice nella classe di attributo in esecuzione. È compito dell'utente intervenire sui metadati in
un'altra parte del codice. Informazioni più dettagliate sono disponibili più avanti nell'esercitazione.
A questo punto è necessario prestare attenzione a un piccolo problema. Come specificato in precedenza, è
possibile passare come argomenti solo determinati tipi di attributi. Quando si crea un tipo di attributo, tuttavia, il
compilatore C# non impedisce la creazione di questi parametri. Nell'esempio seguente è stato creato un
attributo con un costruttore compilato correttamente.
public class GotchaAttribute : Attribute
{
public GotchaAttribute(Foo myClass, string str) {
}
}

Non sarà tuttavia possibile usare questo costruttore con la sintassi di attributo.

[Gotcha(new Foo(), "test")] // does not compile


public class AttributeFail
{
}

Il codice precedente genererà un errore del compilatore come


Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type

Come limitare l'uso degli attributi


Gli attributi possono essere usati su diversi tipi di "destinatari". Gli esempi precedenti ne illustrano l'uso
all'interno di classi, ma possono anche essere usati in:
Assembly
Classe
Costruttore
Delegato
Enumerazione
Evento
Campo
GenericParameter
Interfaccia
Metodo
Modulo
Parametro
Proprietà
ReturnValue
Struct
Quando si crea una classe di attributo, per impostazione predefinita C# consente di usare l'attributo in una
qualsiasi delle destinazioni possibili. Se si vuole limitare l'attributo ad alcune destinazioni, è possibile usare
AttributeUsageAttribute nella classe di attributo, ovvero un attributo in un attributo.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

Se si prova a inserire l'attributo precedente in un elemento diverso da una classe o da un tipo struct, si otterrà
un errore del compilatore simile al seguente:
Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on
'class, struct' declarations
public class Foo
{
// if the below attribute was uncommented, it would cause a compiler error
// [MyAttributeForClassAndStructOnly]
public Foo()
{ }
}

Come usare attributi associati a un elemento di codice


Gli attributi agiscono come metadati. Senza una forza verso l'esterno, non hanno alcuna funzione.
Per trovare e intervenire sugli attributi, è in genere necessario usare la reflection. Il concetto di reflection non
verrà approfondito in questa esercitazione ma, in termini generali, la reflection consente di scrivere codice C#
che esamina altro codice.
È possibile, ad esempio, usare la reflection per ottenere informazioni su una classe (aggiungere
using System.Reflection; all'inizio del codice ):

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();


Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

Verrà generato un output simile al seguente:


The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null

Dopo aver creato un oggetto TypeInfo (o MemberInfo , FieldInfo e così via), è possibile usare il metodo
GetCustomAttributes . Verrà restituita una raccolta di oggetti Attribute . È possibile anche usare
GetCustomAttribute e specificare un tipo di attributo.

Di seguito è riportato un esempio d'uso di GetCustomAttributes in un'istanza di MemberInfo per MyClass (che,
come specificato in precedenza, include un attributo [Obsolete] ).

var attrs = typeInfo.GetCustomAttributes();


foreach(var attr in attrs)
Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

Nella console verrà visualizzato l'output seguente: Attribute on MyClass: ObsoleteAttribute . Provare ad
aggiungere altri attributi a MyClass .
È importante sottolineare che le istanze di questi oggetti Attribute vengono create in modo differito, ovvero
non vengono create finché non si usa GetCustomAttribute o GetCustomAttributes . L'istanza, inoltre, viene creata
ogni volta. Se si chiama GetCustomAttributes due volte in una riga, verranno restituite due istanze diverse di
ObsoleteAttribute .

Attributi comuni nella libreria di classi base


Gli attributi vengono usati da molti tipi di strumenti e framework. NUnit si avvale di attributi come [Test] e
[TestFixture] , usati dal test runner NUnit. ASP.NET MVC usa attributi come [Authorize] e fornisce un
framework di filtri di azione per applicare questioni trasversali su azioni MVC. PostSharp usa la sintassi di
attributo per consentire la programmazione orientata agli aspetti in C#.
Di seguito sono illustrati alcuni attributi importanti incorporati nelle librerie di classi base di .NET Core:
[Obsolete] . Questo attributo è stato usato negli esempi precedenti e si trova nello spazio dei nomi
System . È utile per fornire documentazione dichiarativa su una codebase in fase di modifica. È possibile
specificare un messaggio sotto forma di stringa e usare un altro parametro booleano per convertire un
avviso del compilatore in un errore del compilatore.
[Conditional]. Questo attributo si trova nello spazio dei nomi System.Diagnostics e può essere
applicato a metodi (o a classi di attributo). È necessario passare una stringa al costruttore. Se la stringa
non corrisponde a una direttiva #define , qualsiasi chiamata al metodo (ma non il metodo stesso) verrà
rimossa dal compilatore C#. Questo attributo viene in genere usato per attività di debug (diagnostica).
[CallerMemberName] . Questo attributo può essere usato nei parametri e si trova nello spazio dei nomi
System.Runtime.CompilerServices . Consente di inserire il nome del metodo che chiama un altro metodo.
Viene in genere usato per eliminare "stringhe magiche" quando si implementa INotifyPropertyChanged
in vari framework di interfaccia utente. Ad esempio:

public class MyUIClass : INotifyPropertyChanged


{
public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged([CallerMemberName] string propertyName = null)


{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private string _name;


public string Name
{
get { return _name;}
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged(); // notice that "Name" is not needed here explicitly
}
}
}
}

Nel codice precedente non è necessario che sia inclusa una stringa "Name" letterale. In questo modo, infatti, si
evitano bug correlati a errori di digitazione e si semplificano eventuali operazioni di
refactoring/ridenominazione.

Riepilogo
Gli attributi integrano funzioni dichiarative in C#, ma rappresentano elementi di codice simili ai metadati e da
soli non hanno alcuna funzione.
Novità in C# 9.0
28/01/2021 • 33 minutes to read • Edit Online

C# 9,0 aggiunge le funzionalità e i miglioramenti seguenti al linguaggio C#:


Record
Setter di sola inizializzazione
Istruzioni di primo livello
Miglioramenti dei criteri di ricerca
Interi di dimensioni native
Puntatori funzione
Non visualizzare il flag di creazione localsinit
Espressioni new con tipo di destinazione
funzioni anonime statiche
Espressioni condizionali tipizzate di destinazione
Tipi restituiti covarianti
GetEnumerator Supporto dell'estensione per i foreach cicli
Parametri di rimozione lambda
Attributi per le funzioni locali
Inizializzatori di modulo
Nuove funzionalità per i metodi parziali
C# 9,0 è supportato in .NET 5 . Per ulteriori informazioni, vedere controllo delle versioni del linguaggio C#.
È possibile scaricare la versione più recente di .NET SDK dalla pagina dei download di .NET.

Tipi di record
C# 9,0 introduce *tipi di record _, che sono un tipo di riferimento che fornisce metodi sintetizzati per fornire la
semantica del valore per verificarne l'uguaglianza. I record non sono modificabili per impostazione predefinita.
I tipi di record semplificano la creazione di tipi di riferimento non modificabili in .NET. Storicamente, i tipi .NET
vengono classificati in gran parte come tipi di riferimento (incluse classi e tipi anonimi) e tipi di valore (inclusi
struct e Tuple). Mentre i tipi di valore non modificabili sono consigliati, i tipi di valore modificabili non
introducono spesso errori. Le variabili di tipo valore contengono i valori in modo che le modifiche vengano
apportate a una copia dei dati originali quando i tipi di valore vengono passati ai metodi.
Sono disponibili molti vantaggi anche per i tipi di riferimento non modificabili. Questi vantaggi sono più
evidenti nei programmi simultanei con dati condivisi. Sfortunatamente, C# ha forzato a scrivere un po' di codice
aggiuntivo per creare tipi di riferimento non modificabili. I record forniscono una dichiarazione di tipo per un
tipo di riferimento non modificabile che usa la semantica del valore per verificarne l'uguaglianza. I metodi
sintetizzati per l'uguaglianza e i codici hash considerano due record uguali se le rispettive proprietà sono tutte
uguali. Prendere in considerazione questa definizione:
public record Person
{
public string LastName { get; }
public string FirstName { get; }

public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

La definizione del record crea un Person tipo che contiene due proprietà di sola lettura: FirstName e LastName .
Il Person tipo è un tipo di riferimento. Se si esamina il linguaggio il, è una classe. Non è modificabile perché
nessuna delle proprietà può essere modificata dopo che è stata creata. Quando si definisce un tipo di record, il
compilatore sintetizza diversi altri metodi:
Metodi per i confronti di uguaglianza basati su valori
Esegui override per GetHashCode()
Copiare e clonare i membri
PrintMembers e ToString()

I record supportano l'ereditarietà. È possibile dichiarare un nuovo record derivato da Person come indicato di
seguito:

public record Teacher : Person


{
public string Subject { get; }

public Teacher(string first, string last, string sub)


: base(first, last) => Subject = sub;
}

È anche possibile sigillare i record per impedire ulteriori derivazioni:

public sealed record Student : Person


{
public int Level { get; }

public Student(string first, string last, int level) : base(first, last) => Level = level;
}

Il compilatore sintetizza versioni diverse dei metodi precedenti. Le firme del metodo dipendono da se il tipo di
record è sealed e se la classe di base diretta è Object. I record devono avere le funzionalità seguenti:
L'uguaglianza è basata sul valore e include un controllo per verificare che i tipi corrispondano. Ad esempio,
un oggetto Student non può essere uguale a un Person , anche se i due record condividono lo stesso nome.
I record hanno una rappresentazione di stringa coerente generata automaticamente.
I record supportano la costruzione di copia. La corretta costruzione della copia deve includere le gerarchie di
ereditarietà e le proprietà aggiunte dagli sviluppatori.
I record possono essere copiati con la modifica. Queste operazioni di copia e modifica supportano la
mutazione non distruttiva.
Oltre ai noti EqualsOverload, operator == e operator != , il compilatore sintetizza una nuova
EqualityContract Proprietà. La proprietà restituisce un Type oggetto che corrisponde al tipo del record. Se il
tipo di base è object , la proprietà è virtual . Se il tipo di base è un altro tipo di record, la proprietà è un
oggetto override . Se il tipo di record è sealed , la proprietà è sealed . Il sintetizzato GetHashCode utilizza
GetHashCode da tutte le proprietà e i campi dichiarati nel tipo di base e nel tipo di record. Questi metodi
sintetizzati applicano l'uguaglianza basata sul valore all'interno di una gerarchia di ereditarietà. Ciò significa che
Student non verrà mai considerato uguale a un Person con lo stesso nome. I tipi dei due record devono
corrispondere e tutte le proprietà condivise tra i tipi di record sono uguali.
I record hanno anche un costruttore sintetizzato e un metodo di "clonazione" per la creazione di copie. Il
costruttore sintetizzato ha un solo parametro del tipo di record. Produce un nuovo record con gli stessi valori
per tutte le proprietà del record. Questo costruttore è privato se il record è sealed; in caso contrario, è protetto. Il
metodo "clone" sintetizzato supporta la costruzione di copie per le gerarchie di record. Il termine "clone" è
racchiuso tra virgolette perché il nome effettivo è generato dal compilatore. Non è possibile creare un metodo
denominato Clone in un tipo di record. Il metodo "clone" sintetizzato restituisce il tipo di record copiato
mediante la distribuzione virtuale. Il compilatore aggiunge diversi modificatori per il metodo "clone" a seconda
dei modificatori di accesso in record :
Se il tipo di record è abstract , anche il metodo "clone" è abstract . Se il tipo di base non è object , anche
il metodo è override .
Per i tipi di record che non sono abstract quando il tipo di base è object :
Se il record è sealed , non viene aggiunto alcun modificatore aggiuntivo al metodo "clone" (ovvero
non è virtual ).
Se il record non è sealed , il metodo "clone" è virtual .
Per i tipi di record che non sono abstract quando il tipo di base non è object :
Se il record è sealed , sarà anche il metodo "clone" sealed .
Se il record non è sealed , il metodo "clone" è override .

Il risultato di tutte queste regole è che l'uguaglianza è implementata in modo coerente in qualsiasi gerarchia di
tipi di record. Due record sono uguali tra loro se le proprietà sono uguali e i loro tipi sono uguali, come illustrato
nell'esempio seguente:

var person = new Person("Bill", "Wagner");


var student = new Student("Bill", "Wagner", 11);

Console.WriteLine(student == person); // false

Il compilatore sintetizza due metodi che supportano l'output stampato: un ToString() override, e PrintMembers .
PrintMembers Accetta System.Text.StringBuilder come argomento. Viene aggiunto un elenco delimitato da
virgole di nomi e valori di proprietà per tutte le proprietà nel tipo di record. PrintMembers chiama
l'implementazione di base per tutti i record derivati da altri record. L' ToString() override restituisce la stringa
prodotta da PrintMembers , racchiusa tra { e } . Ad esempio, il ToString() metodo per Student restituisce un
oggetto string come il codice seguente:

"Student { LastName = Wagner, FirstName = Bill, Level = 11 }"

Gli esempi illustrati finora usano la sintassi tradizionale per dichiarare le proprietà. Esiste una forma più concisa
denominata record posizionali. Di seguito sono riportati i tre tipi di record definiti in precedenza come record
posizionali:

public record Person(string FirstName, string LastName);

public record Teacher(string FirstName, string LastName,


string Subject)
: Person(FirstName, LastName);

public sealed record Student(string FirstName,


string LastName, int Level)
: Person(FirstName, LastName);
Queste dichiarazioni creano le stesse funzionalità della versione precedente (con due funzionalità aggiuntive
descritte nella sezione seguente). Queste dichiarazioni terminano con un punto e virgola anziché tra parentesi
quadre, perché questi record non aggiungono altri metodi. È possibile aggiungere un corpo e includere anche
eventuali metodi aggiuntivi:

public record Pet(string Name)


{
public void ShredTheFurniture() =>
Console.WriteLine("Shredding furniture");
}

public record Dog(string Name) : Pet(Name)


{
public void WagTail() =>
Console.WriteLine("It's tail wagging time");

public override string ToString()


{
StringBuilder s = new();
base.PrintMembers(s);
return $"{s.ToString()} is a dog";
}
}

Il compilatore produce un Deconstruct metodo per i record posizionali. Il Deconstruct metodo ha parametri
che corrispondono ai nomi di tutte le proprietà pubbliche nel tipo di record. Il Deconstruct metodo può essere
usato per decostruire il record nelle proprietà del componente:

var person = new Person("Bill", "Wagner");

var (first, last) = person;


Console.WriteLine(first);
Console.WriteLine(last);

Infine, registra le with espressionidi supporto. Un * with expression_ _ indica al compilatore di creare una
copia di un record, ma _WITH proprietà specificate modificate:

Person brother = person with { FirstName = "Paul" };

La riga precedente crea un nuovo Person record in cui la LastName proprietà è una copia di person e l'oggetto
FirstName è "Paul" . È possibile impostare un numero qualsiasi di proprietà in un' with espressione. È anche
possibile usare with espressioni per creare una copia esatta. Specificare il set vuoto per le proprietà da
modificare:

Person clone = person with { };

Qualsiasi membro sintetizzato, ad eccezione del metodo "clone", può essere scritto dall'utente. Se un tipo di
record dispone di un metodo che corrisponde alla firma di un metodo sintetizzato, il compilatore non sintetizza
tale metodo. Nell'esempio di record precedente è Dog contenuto un metodo codificato a mano ToString() come
esempio.
Altre informazioni sui tipi di record in questa esercitazione sull' esplorazione dei record .

Setter di sola inizializzazione


*I Setter solo init _ forniscono una sintassi coerente per inizializzare i membri di un oggetto. Gli inizializzatori
di proprietà rendono chiaro quale valore imposta la proprietà. Lo svantaggio è che tali proprietà devono essere
impostate. A partire da C# 9,0, è possibile creare init funzioni di accesso anziché set funzioni di accesso per
le proprietà e gli indicizzatori. I chiamanti possono usare la sintassi dell'inizializzatore di proprietà per impostare
questi valori nelle espressioni di creazione, ma tali proprietà sono ReadOnly una volta completata la costruzione.
I setter solo init forniscono una finestra per modificare lo stato. La finestra viene chiusa al termine della fase di
costruzione. La fase di costruzione termina in modo efficace dopo che tutte le inizializzazioni, inclusi gli
inizializzatori di proprietà e le espressioni with sono state completate.
È possibile dichiarare init solo Setter in qualsiasi tipo scritto. Ad esempio, lo struct seguente definisce una
struttura di osservazione Meteo:

public struct WeatherObservation


{
public DateTime RecordedAt { get; init; }
public decimal TemperatureInCelsius { get; init; }
public decimal PressureInMillibars { get; init; }

public override string ToString() =>


$"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
$"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}

I chiamanti possono usare la sintassi dell'inizializzatore di proprietà per impostare i valori, mantenendo al
tempo stesso l'immutabilità:

var now = new WeatherObservation


{
RecordedAt = DateTime.Now,
TemperatureInCelsius = 20,
PressureInMillibars = 998.0m
};

Tuttavia, la modifica di un'osservazione dopo l'inizializzazione è un errore assegnando a una proprietà solo init
al di fuori dell'inizializzazione:

// Error! CS8852.
now.TemperatureInCelsius = 18;

I setter solo init possono essere utili per impostare le proprietà della classe base dalle classi derivate. Possono
inoltre impostare proprietà derivate tramite helper in una classe base. I record posizionali dichiarano le
proprietà usando solo Setter di inizializzazione. Questi Setter vengono usati nelle espressioni with. È possibile
dichiarare i setter solo init per qualsiasi class o struct definito.

Istruzioni di primo livello


Le istruzioni di primo livello rimuovono la cerimonia superflua da molte applicazioni. Si consideri il "Hello
World!" canonico programma
using System;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Esiste solo una riga di codice che esegue operazioni. Con le istruzioni di primo livello, è possibile sostituire tutto
questo standard con l' using istruzione e la singola riga che esegue il lavoro:

using System;

Console.WriteLine("Hello World!");

Se si desidera un programma a una riga, è possibile rimuovere la using direttiva e utilizzare il nome completo
del tipo:

System.Console.WriteLine("Hello World!");

Solo un file nell'applicazione può utilizzare le istruzioni di primo livello. Se il compilatore trova le istruzioni di
primo livello in più file di origine, si tratta di un errore. Si tratta di un errore anche se si combinano le istruzioni
di primo livello con un metodo del punto di ingresso del programma dichiarato, in genere un Main metodo. In
un certo senso, si può pensare che un file contenga le istruzioni che normalmente si trovano nel Main metodo
di una Program classe.
Uno degli usi più comuni di questa funzionalità consiste nel creare materiale didattico. Gli sviluppatori C#
principianti possono scrivere il "Hello World!" canonico in una o due righe di codice. Non è necessaria alcuna
cerimonia aggiuntiva. Tuttavia, gli sviluppatori esperti troveranno molti usi anche per questa funzionalità. Le
istruzioni di primo livello consentono un'esperienza simile a uno script per la sperimentazione simile a quella
fornita da Jupyter notebook. Le istruzioni di primo livello sono ottime per utilità e programmi console di piccole
dimensioni. Funzioni di Azure è un caso d'uso ideale per le istruzioni di primo livello.
Soprattutto, le istruzioni di primo livello non limitano l'ambito o la complessità dell'applicazione. Tali istruzioni
possono accedere a qualsiasi classe .NET o utilizzarla. Non limitano inoltre l'utilizzo di argomenti della riga di
comando o valori restituiti. Le istruzioni di primo livello possono accedere a una matrice di stringhe denominate
args. Se le istruzioni di primo livello restituiscono un valore integer, tale valore diventerà il codice integer
restituito da un metodo sintetizzato Main . Le istruzioni di primo livello possono contenere espressioni
asincrone. In tal caso, il punto di ingresso sintetizzato restituisce Task o Task<int> .

Miglioramenti dei criteri di ricerca


C# 9 include nuovi miglioramenti ai criteri di ricerca:
I modelli di tipo corrispondono a una variabile è un tipo
I modelli tra parentesi applicano o enfatizzano la precedenza delle combinazioni di modelli
I and modelli congiuntiva richiedono la corrispondenza di entrambi i modelli
I or modelli disgiuntiva richiedono la corrispondenza di uno schema
Per i * not modelli negati_*_ è necessario che un modello non corrisponda
Per i modelli relazionali è necessario che l'input sia minore di, maggiore di, minore o uguale o maggiore o
uguale a una costante specificata.
Questi modelli arricchiscono la sintassi per i modelli. Considerare i seguenti esempi:

public static bool IsLetter(this char c) =>


c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

In alternativa, con le parentesi facoltative per renderlo chiaro con and precedenza maggiore di or :

public static bool IsLetterOrSeparator(this char c) =>


c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

Uno degli usi più comuni è una nuova sintassi per un controllo null:

if (e is not null)
{
// ...
}

Ognuno di questi modelli può essere usato in qualsiasi contesto in cui sono consentiti i modelli: is espressioni
di pattern, switch espressioni, modelli annidati e il modello dell'etichetta di un' switch istruzione case .

Prestazioni e interoperabilità
Tre nuove funzionalità migliorano il supporto per l'interoperabilità nativa e le librerie di basso livello che
richiedono prestazioni elevate: integer a dimensione nativa, puntatori a funzione e omissione del localsinit
flag.
Gli Integer con dimensione nativa, nint e nuint , sono tipi Integer. Sono espresse dai tipi sottostanti
System.IntPtr e da System.UIntPtr . Il compilatore presenta le conversioni e le operazioni aggiuntive per questi
tipi come int nativi. Gli Integer con dimensione nativa definiscono le proprietà per MaxValue o MinValue . Questi
valori non possono essere espressi come costanti in fase di compilazione perché dipendono dalle dimensioni
native di un numero intero nel computer di destinazione. Tali valori sono di sola lettura in fase di esecuzione. È
possibile utilizzare valori costanti per nint nell'intervallo [ int.MinValue .. int.MaxValue ]. È possibile utilizzare
valori costanti per nuint nell'intervallo [ uint.MinValue .. uint.MaxValue ]. Il compilatore esegue la riduzione
costante per tutti gli operatori unari e binari usando i System.Int32 System.UInt32 tipi e. Se il risultato non
rientra in 32 bit, l'operazione viene eseguita in fase di esecuzione e non è considerata una costante. Gli Integer
con dimensione nativa possono migliorare le prestazioni in scenari in cui la matematica di interi viene usata in
modo estensivo e deve avere le prestazioni più rapide possibile.
I puntatori a funzione forniscono una sintassi semplice per accedere ai codici operativi il ldftn e calli . È
possibile dichiarare i puntatori a funzione usando la nuova delegate_ sintassi. Un delegate* tipo è un tipo di
puntatore. La chiamata del delegate* tipo utilizza calli , a differenza di un delegato che utilizza callvirt sul
Invoke() metodo. Sintatticamente, le chiamate sono identiche. La chiamata del puntatore a funzione usa la
managed convenzione di chiamata. Aggiungere la unmanaged parola chiave dopo la delegate* sintassi per
dichiarare che si desidera la unmanaged convenzione di chiamata. È possibile specificare altre convenzioni di
chiamata usando gli attributi nella delegate* dichiarazione.
Infine, è possibile aggiungere System.Runtime.CompilerServices.SkipLocalsInitAttribute per indicare al
compilatore di non emettere il localsinit flag. Questo flag indica a CLR di inizializzare in modo zero tutte le
variabili locali. Il localsinit flag è stato il comportamento predefinito per C# a partire da 1,0. Tuttavia,
l'inizializzazione zero aggiuntiva potrebbe avere un notevole effetto sulle prestazioni in alcuni scenari. In
particolare, quando si usa stackalloc . In questi casi, è possibile aggiungere SkipLocalsInitAttribute . È possibile
aggiungerlo a un singolo metodo o a una proprietà oppure a un, class struct , interface o anche a un
modulo. Questo attributo non influisce sui abstract metodi e influisce sul codice generato per
l'implementazione.
Queste funzionalità possono migliorare le prestazioni in alcuni scenari. Devono essere usati solo dopo
un'attenta benchmarking sia prima che dopo l'adozione. Il codice che interessa gli Integer con dimensione nativa
deve essere testato su più piattaforme di destinazione con dimensioni intere diverse. Le altre funzionalità
richiedono codice unsafe.

Adatta e termina le funzionalità


Molte delle altre funzionalità consentono di scrivere codice in modo più efficiente. In C# 9,0 è possibile omettere
il tipo in un' new espressione quando il tipo dell'oggetto creato è già noto. L'uso più comune è nelle
dichiarazioni di campo:

private List<WeatherObservation> _observations = new();

new È possibile usare la tipizzazione di destinazione anche quando è necessario creare un nuovo oggetto da
passare come argomento a un metodo. Si consideri un ForecastFor() metodo con la firma seguente:

public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)

È possibile chiamarla come segue:

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

Un altro ottimo utilizzo di questa funzionalità consiste nel combinarlo con proprietà solo init per inizializzare un
nuovo oggetto:

WeatherStation station = new() { Location = "Seattle, WA" };

È possibile restituire un'istanza di creata dal costruttore predefinito usando un' return new(); istruzione.
Una funzionalità simile migliora la risoluzione del tipo di destinazione delle espressioni condizionali. Con questa
modifica, le due espressioni non devono disporre di una conversione implicita da una all'altra, ma possono
avere entrambe le conversioni implicite in un tipo di destinazione. Probabilmente non si noterà questa modifica.
Ciò che si noterà è che alcune espressioni condizionali che in precedenza richiedevano i cast o non sarebbero
state compilate ora funzionano semplicemente.
A partire da C# 9,0, è possibile aggiungere il static modificatore alle espressioni lambda o ai metodi anonimi.
Le espressioni lambda statiche sono analoghe alle static funzioni locali: un metodo lambda statico o anonimo
non può acquisire le variabili locali o lo stato dell'istanza. Il static modificatore impedisce l'acquisizione
accidentale di altre variabili.
I tipi restituiti covarianti forniscono flessibilità per i tipi restituiti dei metodi di override . Un metodo di override
può restituire un tipo derivato dal tipo restituito del metodo di base sottoposto a override. Questa operazione
può essere utile per i record e per altri tipi che supportano i metodi Clone o Factory virtuali.
Inoltre, il ciclo rileverà e userà un metodo di estensione GetEnumerator che altrimenti soddisfa il
foreach
foreach modello. Questa modifica significa foreach coerente con altre costruzioni basate su modelli, ad
esempio il modello asincrono e la decostruzione basata su modelli. In pratica, questa modifica significa che è
possibile aggiungere il foreach supporto a qualsiasi tipo. È necessario limitarne l'utilizzo a quando
l'enumerazione di un oggetto è sensata nella progettazione.
Successivamente, è possibile utilizzare le variabili Discard come parametri delle espressioni lambda. Questa
praticità consente di evitare di denominare l'argomento e il compilatore può evitarne l'uso. Usare _ per
qualsiasi argomento. Per altre informazioni, vedere la sezione parametri di input di un'espressione lambda
dell'articolo sulle espressioni lambda .
Infine, è ora possibile applicare gli attributi alle funzioni locali. Ad esempio, è possibile applicare annotazioni di
attributi Nullable alle funzioni locali.

Supporto per i generatori di codice


Due funzionalità finali supportano i generatori di codice C#. I generatori di codice C# sono un componente che è
possibile scrivere che è simile a un analizzatore Roslyn o a una correzione del codice. La differenza è che i
generatori di codice analizzano il codice e scrivono i nuovi file di codice sorgente come parte del processo di
compilazione. Un generatore di codice tipico esegue la ricerca di attributi o altre convenzioni nel codice.
Un generatore di codice legge gli attributi o altri elementi di codice usando le API di analisi Roslyn. Da queste
informazioni, aggiunge nuovo codice alla compilazione. I generatori di origine possono aggiungere solo codice;
non è consentito modificare il codice esistente nella compilazione.
Le due funzionalità aggiunte per i generatori di codice sono estensioni per sintassi del metodo parziale _ e
_inizializzatori del modulo*. In primo luogo, le modifiche apportate ai metodi parziali. Prima di C# 9,0, i metodi
parziali sono private ma non possono specificare un modificatore di accesso, avere un valore void restituito e
non possono avere out parametri. Queste restrizioni hanno significato che se non viene fornita alcuna
implementazione del metodo, il compilatore rimuove tutte le chiamate al metodo parziale. C# 9,0 rimuove
queste restrizioni, ma richiede che le dichiarazioni di metodo parziale dispongano di un'implementazione. I
generatori di codice possono fornire tale implementazione. Per evitare di introdurre una modifica di rilievo, il
compilatore considera qualsiasi metodo parziale senza un modificatore di accesso per seguire le regole
precedenti. Se il metodo parziale include il private modificatore di accesso, le nuove regole regolano tale
metodo parziale.
La seconda nuova funzionalità per i generatori di codice è _ inizializzatori di modulo *. Gli inizializzatori di
modulo sono metodi a cui è ModuleInitializerAttribute associato l'attributo. Questi metodi verranno chiamati dal
runtime prima di qualsiasi altro accesso al campo o chiamata al metodo all'interno dell'intero modulo. Metodo
inizializzatore di modulo:
Deve essere statico
Deve essere senza parametri
Deve restituire void
Non deve essere un metodo generico
Non deve essere contenuto in una classe generica
Deve essere accessibile dal modulo contenitore
L'ultimo punto elenco indica in modo efficace che il metodo e la classe che lo contiene devono essere interni o
pubblici. Il metodo non può essere una funzione locale.
Novità di C# 8.0
02/11/2020 • 29 minutes to read • Edit Online

C# 8,0 aggiunge le funzionalità e i miglioramenti seguenti al linguaggio C#:


Membri di sola lettura
Metodi di interfaccia predefiniti
Miglioramentidi criteri di ricerca:
Espressioni switch
Criteri per le proprietà
Criteri per le tuple
Criteri per la posizione
Utilizzo di dichiarazioni
Funzioni locali statiche
Struct ref Disposable
Tipi riferimento nullable
Flussi asincroni
Monouso asincrono
Indici e intervalli
Assegnazione di Unione null
Tipi costruiti non gestiti
Stackalloc nelle espressioni annidate
Miglioramento delle stringhe verbatim interpolate
C# 8,0 è supportato in .NET Core 3. x e .NET standard 2,1 . Per ulteriori informazioni, vedere controllo delle
versioni del linguaggio C#.
Il resto di questo articolo descrive brevemente queste funzionalità. Se sono disponibili articoli approfonditi,
vengono forniti collegamenti a queste panoramiche ed esercitazioni. È possibile esplorare queste funzionalità
nell'ambiente in uso tramite lo strumento globale dotnet try :
1. Installare lo strumento globale dotnet-try.
2. Clonare il repository dotnet/try-samples.
3. Impostare la directory corrente sulla sottodirectory csharp8 per il repository try-samples.
4. Eseguire dotnet try .

Membri di sola lettura


È possibile applicare il readonly modificatore ai membri di uno struct. Indica che il membro non modifica lo
stato. È più granulare rispetto all'applicazione del modificatore readonly a una dichiarazione struct .
Considerare lo struct modificabile seguente:
public struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);

public override string ToString() =>


$"({X}, {Y}) is {Distance} from the origin";
}

Come la maggior parte degli struct, il ToString() metodo non modifica lo stato. Si potrebbe indicare questa
condizione aggiungendo il modificatore readonly alla dichiarazione di ToString() :

public readonly override string ToString() =>


$"({X}, {Y}) is {Distance} from the origin";

La modifica precedente genera un avviso del compilatore, perché ToString accede alla Distance proprietà, che
non è contrassegnata come readonly :

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an
implicit copy of 'this'

Il compilatore genera un avviso quando deve creare una copia difensiva. La Distance proprietà non modifica lo
stato, quindi è possibile correggere questo avviso aggiungendo il readonly modificatore alla dichiarazione:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

Si noti che il readonly modificatore è necessario in una proprietà di sola lettura. Il compilatore non presuppone
get che le funzioni di accesso non modifichino lo stato. è necessario dichiarare in readonly modo esplicito. Le
proprietà implementate automaticamente sono un'eccezione. il compilatore considererà tutti i Getter
implementati automaticamente come readonly , quindi non è necessario aggiungere il readonly modificatore
alle X Y proprietà e.
Il compilatore applica la regola che i readonly membri non modificano lo stato. Il metodo seguente non verrà
compilato a meno che non si rimuova il readonly modificatore:

public readonly void Translate(int xOffset, int yOffset)


{
X += xOffset;
Y += yOffset;
}

Questa funzionalità consente di specificare la finalità della progettazione in modo che il compilatore possa
imporla e applicare le ottimizzazioni in base a tale finalità.
Per ulteriori informazioni, vedere la sezione readonly membri di istanza dell'articolo tipi di struttura .

Metodi di interfaccia predefiniti


È ora possibile aggiungere membri alle interfacce e fornire un'implementazione per tali membri. Questa
funzionalità del linguaggio consente agli autori di API di aggiungere metodi a un'interfaccia nelle versioni più
recenti senza compromettere l'origine o la compatibilità binaria con le implementazioni esistenti di tale
interfaccia. Le implementazioni esistenti ereditano l'implementazione predefinita. Questa funzionalità supporta
anche l'interoperabilità di C# con API destinate ad Android o Swift, che supporta funzionalità simili. I metodi di
interfaccia predefiniti consentono inoltre scenari simili alla funzionalità del linguaggio "tratti".
I metodi di interfaccia predefiniti interessano molti scenari ed elementi del linguaggio. La prima esercitazione
illustra l'aggiornamento di un'interfaccia con le implementazioni predefinite. Sono previsti altre esercitazioni e
aggiornamenti dei riferimenti in tempo per il rilascio generale.

Più criteri in più posizioni


I criteri di ricerca offrono strumenti per fornire funzionalità dipendenti dalla forma su tipi di dati correlati ma
diversi. In C# 7,0 è stata introdotta la sintassi per i modelli di tipo e costanti usando l' is espressione e l'
switch istruzione. Queste funzionalità rappresentano il primo passo per il supporto dei paradigmi di
programmazione in cui i dati e la funzionalità sono separati. Man mano che il settore effettua la transizione
verso più microservizi e altre architetture basate sul cloud, diventano necessari altri strumenti del linguaggio.
C# 8.0 espande questo vocabolario, in modo da poter usare più espressioni di criteri in più posizioni nel codice.
Prendere in considerazione queste funzionalità quando i dati e le funzionalità sono separati. Prendere in
considerazione i criteri di ricerca quando gli algoritmi dipendono da un fatto diverso dal tipo di runtime di un
oggetto. Queste tecniche offrono un altro modo per esprimere le progettazioni.
Oltre ai nuovi criteri disponibili in nuove posizioni, C# 8.0 introduce i criteri ricorsivi . Il risultato di qualsiasi
espressione di criteri è un'espressione. Un criterio ricorsivo è semplicemente un'espressione di criteri applicata
all'output di un'altra espressione di criteri.
Espressioni switch
Spesso un' switch istruzione produce un valore in ogni case blocco. Le espressioni switch consentono di
usare una sintassi più concisa per le espressioni con meno parole chiave case e break ripetitive e un numero
inferiore di parentesi graffe. Ad esempio, si consideri l'enumerazione seguente che elenca i colori
dell'arcobaleno:

public enum Rainbow


{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}

Se l'applicazione ha definito un tipo RGBColor costituito dai componenti R , G e B , è possibile convertire un


valore Rainbow nei relativi valori RGB usando il metodo seguente che contiene un'espressione switch:

public static RGBColor FromRainbow(Rainbow colorBand) =>


colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName:
nameof(colorBand)),
};
Questo esempio include numerosi miglioramenti della sintassi:
La variabile precede la parola chiave switch . L'ordine diverso rende più semplice distinguere visivamente
l'espressione switch dall'istruzione switch.
Gli elementi case e : vengono sostituiti con => . È più conciso e intuitivo.
Il caso default è sostituito con un discard _ .
I corpi sono espressioni e non istruzioni.
Confrontare questo codice con quello equivalente che usa l'istruzione switch classica:

public static RGBColor FromRainbowClassic(Rainbow colorBand)


{
switch (colorBand)
{
case Rainbow.Red:
return new RGBColor(0xFF, 0x00, 0x00);
case Rainbow.Orange:
return new RGBColor(0xFF, 0x7F, 0x00);
case Rainbow.Yellow:
return new RGBColor(0xFF, 0xFF, 0x00);
case Rainbow.Green:
return new RGBColor(0x00, 0xFF, 0x00);
case Rainbow.Blue:
return new RGBColor(0x00, 0x00, 0xFF);
case Rainbow.Indigo:
return new RGBColor(0x4B, 0x00, 0x82);
case Rainbow.Violet:
return new RGBColor(0x94, 0x00, 0xD3);
default:
throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
};
}

Criteri per le proprietà


I criteri per le proprietà consentono di individuare corrispondenze in base alle proprietà dell'oggetto
esaminato. Si consideri un sito di e-commerce che deve calcolare le imposte sulle vendite in base all'indirizzo
dell'acquirente. Tale calcolo non è un compito fondamentale di una Address classe. È soggetto a variazioni nel
tempo, probabilmente più spesso rispetto a eventuali modifiche del formato dell'indirizzo. L'importo delle
imposte sulle vendite dipende dalla proprietà State dell'indirizzo. Il metodo seguente usa i criteri per le
proprietà per calcolare l'imposta sulle vendite dall'indirizzo e dal prezzo:

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>


location switch
{
{ State: "WA" } => salePrice * 0.06M,
{ State: "MN" } => salePrice * 0.075M,
{ State: "MI" } => salePrice * 0.05M,
// other cases removed for brevity...
_ => 0M
};

I criteri di ricerca creano una sintassi concisa per esprimere questo algoritmo.
Criteri per le tuple
Alcuni algoritmi dipendono da più input. I criteri per le tuple consentono di passare da un valore a un altro,
espressi come tupla. Il codice seguente illustra un'espressione switch per il gioco rock, paper, scissors (carta-
forbice-sasso):
public static string RockPaperScissors(string first, string second)
=> (first, second) switch
{
("rock", "paper") => "rock is covered by paper. Paper wins.",
("rock", "scissors") => "rock breaks scissors. Rock wins.",
("paper", "rock") => "paper covers rock. Paper wins.",
("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
("scissors", "rock") => "scissors is broken by rock. Rock wins.",
("scissors", "paper") => "scissors cuts paper. Scissors wins.",
(_, _) => "tie"
};

I messaggi indicano il vincitore. Il caso discard rappresenta le tre combinazioni di valori equivalenti o altri input
di testo.
Criteri per la posizione
Alcuni tipi includono un metodo Deconstruct che decostruisce le proprietà in variabili discrete. Quando un
metodo Deconstruct è accessibile, è possibile usare i criteri per la posizione per esaminare le proprietà
dell'oggetto e usare tali proprietà per un criterio. Considerare la classe Point seguente che include un metodo
Deconstruct per creare variabili discrete per X e Y :

public class Point


{
public int X { get; }
public int Y { get; }

public Point(int x, int y) => (X, Y) = (x, y);

public void Deconstruct(out int x, out int y) =>


(x, y) = (X, Y);
}

Considerare anche l'enumerazione seguente, che rappresenta posizioni diverse in un quadrante:

public enum Quadrant


{
Unknown,
Origin,
One,
Two,
Three,
Four,
OnBorder
}

Il metodo seguente usa i criteri per la posizione per estrarre i valori di x e y . Quindi usa una clausola
when per determinare l'elemento Quadrant del punto:

static Quadrant GetQuadrant(Point point) => point switch


{
(0, 0) => Quadrant.Origin,
var (x, y) when x > 0 && y > 0 => Quadrant.One,
var (x, y) when x < 0 && y > 0 => Quadrant.Two,
var (x, y) when x < 0 && y < 0 => Quadrant.Three,
var (x, y) when x > 0 && y < 0 => Quadrant.Four,
var (_, _) => Quadrant.OnBorder,
_ => Quadrant.Unknown
};
I criteri discard nell'espressione switch precedente trovano una corrispondenza quando x o y è uguale a 0,
ma non entrambi. Un'espressione switch deve produrre un valore o generare un'eccezione. Se nessuno dei casi
corrisponde, l'espressione switch genera un'eccezione. Il compilatore genera un avviso se non si coprono tutti i
casi possibili nell'espressione switch.
È possibile esplorare le tecniche dei criteri di ricerca in questa esercitazione avanzata sui criteri di ricerca.

Dichiarazioni using
Una dichiarazione using è una dichiarazione di variabile preceduta dalla parola chiave using . Indica al
compilatore che la variabile dichiarata deve essere eliminata alla fine dell'ambito di inclusione. Ad esempio, si
consideri il codice seguente che consente di scrivere un file di testo:

static int WriteLinesToFile(IEnumerable<string> lines)


{
using var file = new System.IO.StreamWriter("WriteLines2.txt");
int skippedLines = 0;
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
// Notice how skippedLines is in scope here.
return skippedLines;
// file is disposed here
}

Nell'esempio precedente il file viene eliminato quando viene raggiunta la parentesi graffa di chiusura per il
metodo. Questa è la fine dell'ambito in cui viene dichiarato file . Il codice precedente è equivalente al codice
seguente con l'istruzione using classica:

static int WriteLinesToFile(IEnumerable<string> lines)


{
using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
{
int skippedLines = 0;
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
return skippedLines;
} // file is disposed here
}

Nell'esempio precedente il file viene eliminato quando viene raggiunta la parentesi graffa di chiusura associata
all'istruzione using .
In entrambi i casi, il compilatore genera la chiamata a Dispose() . Il compilatore genera un errore se
l'espressione nell' using istruzione non è Disposable.

Funzioni locali statiche


È ora possibile aggiungere il static modificatore alle funzioni locali per assicurarsi che la funzione locale non
acquisisca (riferimento) le variabili dall'ambito che lo contiene. In questo modo viene generato l'errore CS8421 ,
"A static local function can't contain a reference to <variable>." (Una funzione locale statica non può fare
riferimento a 'variabile')
Si consideri il codice seguente. La funzione locale LocalFunction accede alla variabile y , dichiarata nell'ambito
di inclusione (il metodo M ). Pertanto, non è possibile dichiarare LocalFunction con il modificatore static :

int M()
{
int y;
LocalFunction();
return y;

void LocalFunction() => y = 0;


}

Il codice seguente contiene una funzione locale statica. Può essere statica perché non accede ad alcuna variabile
nell'ambito di inclusione:

int M()
{
int y = 5;
int x = 7;
return Add(x, y);

static int Add(int left, int right) => left + right;


}

Struct ref Disposable


Una struct dichiarata con il ref modificatore non può implementare alcuna interfaccia e pertanto non può
implementare IDisposable . Pertanto, per abilitare un ref struct per l'eliminazione, deve avere un metodo
void Dispose() accessibile. Questa funzionalità si applica anche alle readonly ref struct dichiarazioni.

Tipi riferimento nullable


All'interno di un contesto delle annotazioni nullable, qualsiasi variabile di un tipo riferimento viene considerata
come un tipo riferimento non nullable . Se si vuole indicare che una variabile può essere Null, è necessario
aggiungere ? al nome del tipo per dichiarare la variabile come un tipo riferimento nullable .
Per i tipi riferimento non nullable, il compilatore usa l'analisi di flusso per garantire che le variabili locali
vengano inizializzate su un valore diverso da Null al momento della dichiarazione. I campi devono essere
inizializzati durante la costruzione. Il compilatore genera un avviso se la variabile non è impostata da una
chiamata a uno dei costruttori disponibili o da un inizializzatore. Inoltre, non è possibile assegnare ai tipi
riferimento non nullable un valore che potrebbe essere Null.
I tipi riferimento nullable non vengono controllati per verificare che non vengano assegnati o inizializzati su
Null. Tuttavia, il compilatore usa l'analisi di flusso per garantire che qualsiasi variabile di un tipo riferimento
nullable venga controllata per i valori Null prima dell'accesso o prima che venga assegnata a un tipo riferimento
non nullable.
Altre informazioni su questa funzionalità sono disponibili nella panoramica dei tipi riferimento nullable. È
possibile provare in autonomia in una nuova applicazione in questa esercitazione sui tipi riferimento nullable.
Per informazioni sulla procedura per eseguire la migrazione di una base di codice esistente per l'uso dei tipi
riferimento nullable, vedere l'esercitazione sulla migrazione di un'applicazione per l'uso dei tipi riferimento
nullable.

Flussi asincroni
A partire da C# 8.0, è possibile creare e utilizzare i flussi in modo asincrono. Un metodo che restituisce un flusso
asincrono ha tre proprietà:
1. È stato dichiarato con il modificatore async .
2. Restituisce IAsyncEnumerable<T>.
3. Il metodo contiene istruzioni yield return per restituire gli elementi successivi nel flusso asincrono.

Per utilizzare un flusso asincrono, è necessario aggiungere la parola chiave await prima della parola chiave
foreach quando si enumerano gli elementi del flusso. Per l'aggiunta della parola chiave await , il metodo che
enumera il flusso asincrono deve essere dichiarato con il modificatore async e restituire un tipo consentito per
un metodo async . In genere ciò significa restituire Task o Task<TResult>. Può anche essere ValueTask o
ValueTask<TResult>. Un metodo può sia utilizzare che produrre un flusso asincrono, il che significa che
restituirebbe IAsyncEnumerable<T>. Il codice seguente genera una sequenza da 0 a 19, con un'attesa di 100 ms
tra la generazione di ogni numero:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()


{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
yield return i;
}
}

La sequenza viene enumerata usando l'istruzione await foreach :

await foreach (var number in GenerateSequence())


{
Console.WriteLine(number);
}

È possibile provare i flussi asincroni in autonomia nell'esercitazione dedicata alla creazione e all'utilizzo di flussi
asincroni. Per impostazione predefinita, gli elementi del flusso vengono elaborati nel contesto acquisito. Se si
desidera disabilitare l'acquisizione del contesto, utilizzare il TaskAsyncEnumerableExtensions.ConfigureAwait
metodo di estensione. Per ulteriori informazioni sui contesti di sincronizzazione e sull'acquisizione del contesto
corrente, vedere l'articolo sull' utilizzo del modello asincrono basato su attività.

Monouso asincrono
A partire da C# 8,0, il linguaggio supporta i tipi monouso asincroni che implementano l'
System.IAsyncDisposable interfaccia. Usare l' await using istruzione per lavorare con un oggetto eliminabile in
modo asincrono. Per altre informazioni, vedere l'articolo implementare un metodo DisposeAsync .

Indici e intervalli
Gli indici e gli intervalli forniscono una sintassi concisa per l'accesso a singoli elementi o intervalli in una
sequenza.
Questo supporto per il linguaggio si basa su due nuovi tipi e due nuovi operatori:
System.Index rappresenta un indice in una sequenza.
Indice dell'operatore end ^ , che specifica che un indice è relativo alla fine della sequenza.
System.Range rappresenta un intervallo secondario di una sequenza.
Operatore Range .. , che specifica l'inizio e la fine di un intervallo come operandi.

Per iniziare, ecco come funzionano le regole per gli indici. Prendere in considerazione una matrice sequence .
L'indice 0 è uguale a sequence[0] . L'indice ^0 è uguale a sequence[sequence.Length] . Si noti che
sequence[^0] genera un'eccezione, proprio come sequence[sequence.Length] . Per qualsiasi numero n , l'indice
^n è uguale a sequence.Length - n .

Un intervallo specifica inizio e fine di un intervallo. L'inizio dell'intervallo è inclusivo, ma la fine dell'intervallo è
esclusiva, ovvero l' inizio è incluso nell'intervallo, ma la fine non è inclusa nell'intervallo. L'intervallo [0..^0]
rappresenta l'intero intervallo, proprio come [0..sequence.Length] rappresenta l'intero intervallo.
Di seguito vengono illustrati alcuni esempi. Si consideri la matrice seguente, annotata con il relativo indice
dall'inizio e dalla fine:

var words = new string[]


{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0

È possibile recuperare l'ultima parola con l'indice ^1 :

Console.WriteLine($"The last word is {words[^1]}");


// writes "dog"

Il codice seguente crea un intervallo secondario con le parole "quick", "brown" e "fox". Include da words[1] a
words[3] . L'elemento words[4] non è compreso nell'intervallo.

var quickBrownFox = words[1..4];

Il codice seguente crea un intervallo secondario con "lazy" e "dog". Include words[^2] e words[^1] . L'indice
finale words[^0] non è incluso:

var lazyDog = words[^2..^0];

Gli esempi seguenti creano intervalli aperti alle estremità, per l'inizio, la fine o entrambe:
var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

È anche possibile dichiarare gli intervalli come variabili:

Range phrase = 1..4;

L'intervallo può quindi essere usato all'interno dei caratteri [ e ] :

var text = words[phrase];

Non solo le matrici supportano gli indici e gli intervalli. È anche possibile usare gli indici e gli intervalli con
String, Span<T> o ReadOnlySpan<T> . Per altre informazioni, vedere supporto dei tipi per indici e intervalli.
È possibile ottenere maggiori informazioni su indici e intervalli nell'esercitazione Indici e intervalli.

Assegnazione di Unione null


C# 8,0 introduce l'operatore di assegnazione di Unione null ??= . È possibile usare l' ??= operatore per
assegnare il valore dell'operando destro all'operando sinistro solo se l'operando sinistro restituisce null .

List<int> numbers = null;


int? i = null;

numbers ??= new List<int>();


numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers)); // output: 17 17


Console.WriteLine(i); // output: 17

Per ulteriori informazioni, vedere ?? e?? = articolo Operators .

Tipi costruiti non gestiti


In C# 7,3 e versioni precedenti, un tipo costruito (un tipo che include almeno un argomento di tipo) non può
essere un tipo non gestito. A partire da C# 8,0, un tipo di valore costruito non è gestito se contiene campi solo di
tipi non gestiti.
Ad esempio, data la seguente definizione del tipo generico Coords<T> :

public struct Coords<T>


{
public T X;
public T Y;
}

il Coords<int> tipo è un tipo non gestito in C# 8,0 e versioni successive. Come per qualsiasi tipo non gestito, è
possibile creare un puntatore a una variabile di questo tipo o allocare un blocco di memoria nello stack per le
istanze di questo tipo:
Span<Coords<int>> coordinates = stackalloc[]
{
new Coords<int> { X = 0, Y = 0 },
new Coords<int> { X = 0, Y = 3 },
new Coords<int> { X = 4, Y = 0 }
};

Per ulteriori informazioni, vedere tipi non gestiti.

Stackalloc nelle espressioni annidate


A partire da C# 8,0, se il risultato di un'espressione stackalloc è di System.Span<T> System.ReadOnlySpan<T>
tipo o, è possibile usare l' stackalloc espressione in altre espressioni:

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };


var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6, 8 });
Console.WriteLine(ind); // output: 1

Miglioramento delle stringhe verbatim interpolate


L'ordine dei $ @ token e nelle stringhe verbatim interpolate può essere any: sia $@"..." che @$"..." sono
stringhe verbatim interpolate valide. Nelle versioni precedenti di C# il $ token deve essere visualizzato prima
del @ token.
Novità di C# 7,0 tramite C# 7,3
28/01/2021 • 46 minutes to read • Edit Online

C# 7,0 tramite C# 7,3 ha introdotto numerose funzionalità e miglioramenti incrementali per la tua esperienza di
sviluppo con C#. Questo articolo fornisce una panoramica delle nuove funzionalità del linguaggio e delle
opzioni del compilatore. Le descrizioni descrivono il comportamento per C# 7,3, che è la versione più recente
supportata per le applicazioni basate su .NET Framework.
L'elemento di configurazione della selezione della versione della lingua è stato aggiunto con C# 7,1, che
consente di specificare la versione del linguaggio del compilatore nel file di progetto.
C# 7.0-7.3 aggiunge le funzionalità e i temi seguenti al linguaggio C#:
Tuple e variabili Discard
È possibile creare tipi leggeri e senza nome che contengono più campi pubblici. I compilatori e gli
strumenti dell'IDE comprendono la semantica di questi tipi.
Le variabili discard sono variabili temporanee di sola scrittura usate nelle assegnazioni quando non si
è interessati al valore assegnato. Sono utili soprattutto per la decostruzione di tuple e tipi definiti
dall'utente, nonché per la chiamata di metodi con i parametri out .
Criteri di ricerca
È possibile creare una logica di salto condizionato basata su tipi e valori arbitrari dei membri di tali
tipi.
async``Main Metodo
Il punto di ingresso per un'applicazione può avere il modificatore async .
Funzioni locali
È possibile annidare funzioni all'interno di altre funzioni per limitarne l'ambito e visibilità.
Più membri con corpo di espressione
L'elenco dei membri che possono essere creati con le espressioni è cresciuto.
throw Espressioni
È possibile generare eccezioni in costrutti di codice che in precedenza non erano consentiti, perché
throw era un'istruzione.
default espressioni letterali
Quando è possibile dedurre il tipo di destinazione, si possono usare espressioni letterali predefinite
nelle espressioni con valore predefinito.
Miglioramenti della sintassi dei valori letterali numerici
Nuovi token migliorano la leggibilità delle costanti numeriche.
out variabili
È possibile dichiarare valori out inline come argomenti per il metodo in cui vengono usati.
Argomenti denominati non finali
Gli argomenti denominati possono essere seguiti da argomenti posizionali.
private protected modificatore di accesso
Il modificatore di accesso private protected consente l'accesso per le classi derivate nello stesso
assembly.
Risoluzione dell'overload migliorata
Nuove regole per risolvere l'ambiguità di risoluzione dell'overload.
Tecniche per la scrittura di codice efficiente e sicuro
Una combinazione di miglioramenti della sintassi che consentono l'utilizzo dei tipi valore tramite la
semantica di riferimento.
Infine, il compilatore ha nuove opzioni:
-refout e che controllano la generazione dell'assembly di riferimento.
-refonly
-publicsign per abilitare la firma degli assembly con software open source.
-pathmap per fornire un mapping per le directory di origine.

La parte restante di questo articolo illustra una panoramica di ogni funzionalità. Per ogni funzionalità, verranno
illustrati i ragionamenti dietro e la sintassi. È possibile esplorare queste funzionalità nell'ambiente in uso tramite
lo strumento globale dotnet try :
1. Installare lo strumento globale dotnet-try.
2. Clonare il repository dotnet/try-samples.
3. Impostare la directory corrente sulla sottodirectory csharp7 per il repository try-samples.
4. Eseguire dotnet try .

Tuple e variabili Discard


C# offre una sintassi completa per le classi e gli struct usati per spiegare la finalità della progettazione. In alcuni
casi tale sintassi richiede ulteriore lavoro con vantaggi minimi. Spesso è possibile scrivere metodi che
richiedono una struttura semplice contenente più di un elemento dati. Per supportare questi scenari, sono state
aggiunte tuple a C#. Le tuple sono strutture di dati leggere che contengono più campi per rappresentare i
membri dati. I campi non vengono convalidati e non è possibile definire metodi personalizzati. I tipi di tupla C#
supportano == e != . Per altre informazioni.

NOTE
Le tuple erano già disponibili prima di C# 7.0, ma in modo poco efficiente e senza supporto del linguaggio. Questo
significava poter fare riferimento agli elementi delle tuple solo come Item1 , Item2 e così via. C# 7.0 introduce il
supporto del linguaggio per le tuple, che consente nomi semantici per i campi di una tupla con tipi di tupla nuovi e più
efficienti.

È possibile creare una tupla assegnando un valore a ogni membro e fornendo facoltativamente nomi semantici
per ogni membro della tupla:

(string Alpha, string Beta) namedLetters = ("a", "b");


Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

La tupla namedLetters contiene campi detti Alpha e Beta . Tali nomi sono disponibili solo in fase di
compilazione e non vengono conservati, ad esempio quando si esamina la tupla utilizzando la reflection in fase
di esecuzione.
In un'assegnazione di tupla è anche possibile specificare i nomi dei campi sul lato destro dell'assegnazione:

var alphabetStart = (Alpha: "a", Beta: "b");


Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

Può rendersi necessario decomprimere i membri di una tupla che sono stati restituiti da un metodo. In questo
caso è possibile dichiarare variabili separate per ogni valore nella tupla. Questa operazione di decompressione è
detta decostruzione della tupla:
(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

È anche possibile specificare una decostruzione simile per qualsiasi tipo in .NET. Si scrive un metodo
Deconstruct come membro della classe. Tale metodo Deconstruct specifica un set di argomenti out per
ognuna delle proprietà da estrarre. Considerare questa classe Point che specifica un metodo di decostruzione
che estrae le coordinate X e Y :

public class Point


{
public Point(double x, double y)
=> (X, Y) = (x, y);

public double X { get; }


public double Y { get; }

public void Deconstruct(out double x, out double y) =>


(x, y) = (X, Y);
}

È possibile estrarre i singoli campi assegnando un oggetto Point a una tupla:

var p = new Point(3.14, 2.71);


(double X, double Y) = p;

Molte volte quando si Inizializza una tupla, le variabili usate per il lato destro dell'assegnazione corrispondono ai
nomi desiderati per gli elementi della tupla: i nomi degli elementi della tupla possono essere dedotti dalle
variabili usate per inizializzare la tupla:

int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"

Per altre informazioni su questa funzionalità, vedere l'articolo tipi di tupla .


Spesso durante lo decostruzione di una tupla o la chiamata di un metodo con parametri out , si è costretti a
definire una variabile con un valore non significativo e che non si intende usare. C# aggiunge il supporto delle
variabili discard per gestire questo scenario. Una variabile discard è una variabile di sola scrittura il cui nome è
_ (carattere di sottolineatura). È possibile assegnare tutti i valori che non occorre mantenere alla singola
variabile. Una variabile discard è simile a una variabile non assegnata. Con l'eccezione dell'istruzione di
assegnazione, la variabile discard non può essere usata nel codice.
Le variabili discard sono supportate negli scenari seguenti:
Per la decostruzione di tuple o tipi definiti dall'utente.
Per la chiamata di metodi con parametri out.
In operazioni con criteri di ricerca con le istruzioni is e switch.
Come identificatore autonomo quando si vuole identificare in modo esplicito il valore di un'assegnazione
come variabile discard.
L'esempio seguente definisce un metodo QueryCityDataForYears che restituisce una tupla con 6 elementi che
contiene i dati di una città per due anni diversi. La chiamata del metodo nell'esempio è interessata solo ai due
valori di popolazione restituiti dal metodo e quindi gestisce i valori rimanenti nella tupla come variabili discard
quando decostruisce la tupla.
using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");


}

private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;

if (name == "New York City")


{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}

return ("", 0, 0, 0, 0, 0);


}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149

Per altre informazioni, vedere Variabili discard.

Criteri di ricerca
Criteri di ricerca è un set di funzionalità che consentono nuovi modi per esprimere il flusso di controllo nel
codice. È possibile testare le variabili per il tipo, i valori o i valori delle relative proprietà. Queste tecniche creano
un flusso di codice più leggibile.
I criteri di ricerca supportano le espressioni is e le espressioni switch . Ognuno consente di esaminare un
oggetto e le relative proprietà per determinare se l'oggetto soddisfa il criterio ricercato. Usare la parola chiave
when per specificare regole aggiuntive per il modello.

L' is espressione del criterio estende l' is operatore familiare per eseguire una query su un oggetto relativo
al tipo e assegnare il risultato in un'unica istruzione. Il codice seguente controlla se una variabile è un int e in
tal caso lo aggiunge alla somma corrente:

if (input is int count)


sum += count;

Il breve esempio precedente dimostra i miglioramenti all'espressione is . È possibile eseguire test rispetto a tipi
di valore e tipi di riferimento e assegnare il risultato positivo a una nuova variabile del tipo corretto.
L'espressione di ricerca switch ha una sintassi familiare, basata sull'istruzione switch che fa già parte del
linguaggio C#. L'istruzione switch aggiornata presenta vari nuovi costrutti:
Il tipo che gestisce un'espressione switch non è più limitato a tipi integrali, tipi Enum , string o a un tipo
nullable corrispondente a uno di questi tipi. Ora è possibile usare qualsiasi tipo.
È possibile eseguire il test del tipo dell'espressione switch in ogni etichetta case . Come per l'espressione
is , è possibile assegnare una nuova variabile a tale tipo.
È possibile aggiungere una clausola when per eseguire ulteriori test delle condizioni per tale variabile.
L'ordine delle etichette case ora è importante. Viene eseguito il primo ramo che registra una
corrispondenza; gli altri rami vengono ignorati.
Il codice seguente dimostra queste nuove funzionalità:

public static int SumPositiveNumbers(IEnumerable<object> sequence)


{
int sum = 0;
foreach (var i in sequence)
{
switch (i)
{
case 0:
break;
case IEnumerable<int> childSequence:
{
foreach(var item in childSequence)
sum += (item > 0) ? item : 0;
break;
}
case int n when n > 0:
sum += n;
break;
case null:
throw new NullReferenceException("Null found in sequence");
default:
throw new InvalidOperationException("Unrecognized type");
}
}
return sum;
}

case 0: è il criterio costante familiare.


case IEnumerable<int> childSequence: è un criterio del tipo.
case int n when n > 0: è un criterio del tipo con una condizione when aggiuntiva.
case null: è il criterio null.
default: è il case predefinito già noto.

A partire da C# 7.1, l'espressione di criteri per is e il criterio di tipo switch possono avere il tipo di un
parametro di tipo generico. Questo è particolarmente utile quando si esegue il controllo dei tipi che possono
essere struct o class e si vuole evitare la conversione boxing.
Altre informazioni sui criteri di ricerca sono disponibili nell'argomento Criteri di ricerca.

Async main
Un metodo async main consente di usare await nel proprio metodo Main . In precedenza sarebbe stato
necessario scrivere:
static int Main()
{
return DoAsyncWork().GetAwaiter().GetResult();
}

Ora è possibile scrivere:

static async Task<int> Main()


{
// This could also be replaced with the body
// DoAsyncWork, including its await expressions:
return await DoAsyncWork();
}

Se il programma non restituisce un codice di uscita, è possibile dichiarare un metodo Main che restituisce una
classe Task:

static async Task Main()


{
await SomeAsyncMethod();
}

Per altre informazioni dettagliate, leggere l'articolo async main nella guida alla programmazione.

Funzioni locali
Molte progettazioni per le classi includono metodi che vengono chiamati da un solo percorso. Si tratta di metodi
privati aggiuntivi che rendono più circoscritto e mirato ogni metodo. Le funzioni locali consentono di dichiarare
metodi all'interno del contesto di un altro metodo. Le funzioni locali rendono più semplice per i lettori della
classe verificare se il metodo locale viene chiamato solo dal contesto in cui è dichiarato.
Esistono due casi d'uso comuni per le funzioni locali: i metodi iterator pubblici e i metodi async pubblici.
Entrambi i tipi di metodi generano codice che segnala gli errori in ritardo rispetto a quanto previsto dai
programmatori. Per i metodi iterator le eccezioni vengono riscontrate solo quando si chiama il codice che
enumera la sequenza restituita. Per i metodi async tutte le eccezioni vengono riscontrate solo quando è atteso
l'oggetto restituito Task . L'esempio seguente illustra la separazione tra convalida dei parametri e
implementazione dell'iteratore usando una funzione locale:

public static IEnumerable<char> AlphabetSubset3(char start, char end)


{
if (start < 'a' || start > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
if (end < 'a' || end > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

if (end <= start)


throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

return alphabetSubsetImplementation();

IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
La stessa tecnica può essere usata con i metodi async per garantire che le eccezioni risultanti dalla convalida
degli argomenti vengano generate prima che inizino le attività asincrone:

public Task<string> PerformLongRunningWork(string address, int index, string name)


{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-
negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

return longRunningWorkImplementation();

async Task<string> longRunningWorkImplementation()


{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
}
}

Questa sintassi ora è supportata:

[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }

L'attributo SomeThingAboutFieldAttribute viene applicato al campo sottostante generato dal compilatore per
SomeProperty . Per altre informazioni, vedere la sezione relativa agli attributi nella Guida per programmatori C#.

NOTE
Alcune delle progettazioni supportate dalle funzioni locali possono essere eseguite anche usando le espressioni lambda.
Per ulteriori informazioni, vedere funzioni locali rispetto alle espressioni lambda.

Più membri con corpo di espressione


C# 6 ha introdotto membri con corpo di espressione per le funzioni membro e le proprietà di sola lettura. C# 7.0
amplia la gamma di membri consentiti che possono essere implementati come espressioni. In C# 7.0 è possibile
implementare costruttori, finalizzatori e funzioni di accesso get e set per proprietà e indicizzatori. Nel codice
riportato di seguito vengono illustrati esempi di ogni tipo di membro:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.


public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
NOTE
In questo esempio non è necessario un finalizzatore, ma viene incluso per illustrare la sintassi. Non implementare un
finalizzatore nella classe a meno che non sia necessario per rilasciare risorse non gestite. È inoltre consigliabile usare la
classe SafeHandle anziché gestire direttamente le risorse non gestite.

Le nuove posizioni per i membri con corpo di espressione rappresentano un importante punto cardine per il
linguaggio C#: queste funzionalità sono state implementate dai membri della community che lavoravano al
progetto open source Roslyn.
La modifica di un metodo in un membro con corpo di espressione è una modifica compatibile a livello binario.

Espressioni throw
In C# throw è sempre stata un'istruzione. Poiché throw è un'istruzione, non un'espressione, non può essere
usata in determinati costrutti di C#. Sono incluse le espressioni condizionali, le espressioni Null ridondanti e
alcune espressioni lambda. L'aggiunta di membri con corpo di espressione consente di aggiungere più posizioni
in cui le espressioni throw possono risultare utili. Per fare in modo che sia possibile scrivere uno di questi
costrutti, C# 7.0 introduce le espressioni throw .
Questa aggiunta facilita la scrittura di codice basato sulle espressioni. Non sono necessarie istruzioni aggiuntive
per il controllo degli errori.

Espressioni letterali predefinite


Le espressioni letterali predefinite rappresentano un miglioramento delle espressioni con valore predefinito.
Queste espressioni inizializzano una variabile sul valore predefinito. Mentre in precedenza si sarebbe scritto:

Func<string, bool> whereClause = default(Func<string, bool>);

È ora possibile omettere il tipo sul lato destro dell'inizializzazione:

Func<string, bool> whereClause = default;

Per altre informazioni, vedere la sezione Valore letterale predefinito dell'articolo Operatore default.

Miglioramenti della sintassi dei valori letterali numerici


La lettura non corretta delle costanti numeriche può rendere più difficile la comprensione del codice quando
viene letto per la prima volta. Le maschere di bit o altri valori simbolici possono dare origine a interpretazioni
errate. C# 7.0 include due nuove funzionalità per la scrittura dei numeri in modo che siano più leggibili per l'uso
previsto: valori letterali binari e separatori di cifre.
Ogni volta che si creano maschere di bit o che una rappresentazione binaria di un numero rende il codice più
leggibile, scrivere il numero in formato binario:

public const int Sixteen = 0b0001_0000;


public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

0b all'inizio della costante indica che il numero viene scritto come numero binario. I numeri binari possono
essere lunghi, quindi è spesso più facile visualizzare gli schemi di bit introducendo _ come separatore di cifre,
come illustrato nella costante binaria nell'esempio precedente. Il separatore di cifre può apparire ovunque nella
costante. Per i numeri in base 10, è comune utilizzarlo come separatore delle migliaia. I valori letterali numerici
esadecimali e binari possono iniziare con _ :

public const long BillionsAndBillions = 100_000_000_000;

Il separatore di cifre può essere usato anche con i tipi decimal , float e double :

public const double AvogadroConstant = 6.022_140_857_747_474e23;


public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

Nel loro insieme, è possibile dichiarare le costanti numeriche ottimizzando la leggibilità.

Variabili out
La sintassi esistente che supporta i out parametri è stata migliorata in C# 7. Ora è possibile dichiarare le
variabili out nell'elenco di argomenti di una chiamata al metodo, anziché scrivere un'istruzione di dichiarazione
separata:

if (int.TryParse(input, out int result))


Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");

Per maggiore chiarezza, è consigliabile specificare il tipo di out variabile, come illustrato nell'esempio
precedente. Il linguaggio tuttavia supporta l'uso di una variabile locale tipizzata in modo implicito:

if (int.TryParse(input, out var answer))


Console.WriteLine(answer);
else
Console.WriteLine("Could not parse input");

Il codice è più facile da leggere.


Viene dichiarata la variabile out in cui viene usata, non in una riga di codice precedente.
Non è necessario assegnare un valore iniziale.
Se si dichiara la variabile out nel punto in cui viene usata in una chiamata al metodo, non è possibile
usarla accidentalmente prima che venga assegnata.
La sintassi aggiunta in C# 7.0 per consentire le dichiarazioni di variabili out è stata estesa in modo da includere
inizializzatori di campo, inizializzatori di proprietà, inizializzatori di costruttore e clausole di query. Questo
consente l'uso di codice simile al seguente:
public class B
{
public B(int i, out int j)
{
j = i;
}
}

public class D : B
{
public D(int i) : base(i, out var j)
{
Console.WriteLine($"The value of 'j' is {j}");
}
}

Argomenti denominati non finali


Per le chiamate di metodi è ora possibile usare argomenti denominati che precedono gli argomenti posizionali
quando questi argomenti denominati sono nelle posizioni corrette. Per ulteriori informazioni, vedere argomenti
denominati e facoltativi.

Modificatore di accesso private protected


Il nuovo modificatore di accesso composto private protected indica che un membro è accessibile dalla classe
che lo contiene o dalle classi derivate dichiarate nello stesso assembly. Anche se protected internal consente
l'accesso da classi derivate o classi nello stesso assembly, private protected limita l'accesso ai tipi derivati
dichiarati nello stesso assembly.
Per ulteriori informazioni, vedere modificatori di accesso nelle informazioni di riferimento sul linguaggio.

Miglioramento dei candidati per l'overload


In ogni versione, le regole di risoluzione dell'overload vengono aggiornate per gestire le situazioni in cui le
chiamate di metodi ambigui presentano una scelta "ovvia". Questa versione aggiunge tre nuove regole per
aiutare il compilatore a selezionare la scelta ovvia:
1. Quando un gruppo di metodi contiene sia membri di istanza che membri statici, il compilatore ignora i
membri di istanza se il metodo è stato richiamato senza un contesto o un ricevitore di istanza. Se il metodo è
stato richiamato con un ricevitore di istanza, il compilatore ignora i membri statici. Quando non è presente
alcun ricevitore, il compilatore include solo i membri statici in un contesto statico. In caso contrario, include
sia i membri statici che quelli di istanza. Quando il ricevitore è in modo ambiguo un'istanza o un tipo, il
compilatore include entrambi. Un contesto statico, in cui non è possibile usare un ricevitore di istanza this
implicito, include il corpo dei membri in cui non è definito this , ad esempio i membri statici, nonché i punti
in cui non può essere usato this , ad esempio gli inizializzatori di campo e gli inizializzatori di costruttore.
2. Quando un gruppo di metodi include alcuni metodi generici i cui argomenti di tipo non soddisfano i vincoli,
questi membri vengono rimossi dal set di candidati.
3. Per la conversione di un gruppo di metodi, i metodi candidati il cui tipo restituito non corrisponde al quello
del delegato vengono rimossi dal set.
Sarà possibile notare questa modifica solo perché saranno presenti meno errori del compilatore per overload di
metodi ambigui quando si sa con certezza quale metodo è preferibile.

Miglioramento dell'efficienza del codice gestito


È possibile scrivere codice C# in modo gestito con prestazioni altrettanto elevate di quelle del codice non gestito.
Il codice gestito evita classi di errori come sovraccarichi del buffer, puntatori errati e altri errori di accesso alla
memoria. Queste nuove funzionalità espandono le capacità del codice gestito verificabile. È possibile scrivere
una parte più ampia del codice usando costrutti gestiti. Queste funzionalità rendono più semplice tale attività.
Le nuove funzionalità seguenti supportano il tema del miglioramento delle prestazioni per il codice gestito:
È possibile accedere a campi fissi senza blocco.
È possibile riassegnare le variabili locali ref .
È possibile usare gli inizializzatori nelle matrici stackalloc .
È possibile usare istruzioni fixed con qualsiasi tipo che supporta un modello.
È possibile usare vincoli generici aggiuntivi.
Il modificatore in per i parametri, per specificare che un argomento viene passato per riferimento, ma non
modificato dal metodo chiamato. L'aggiunta del modificatore in a un argomento è una modifica
compatibile a livello di codice sorgente.
Il modificatore ref readonly per i valori restituiti dai metodi, per indicare che un metodo restituisce il valore
per riferimento, ma non consente scritture su tale oggetto. L'aggiunta del modificatore ref readonly è una
modifica compatibile a livello di codice sorgente, se il valore restituito viene assegnato a un valore.
L'aggiunta del modificatore readonly a un'istruzione return ref esistente è una modifica incompatibile.
Richiede ai chiamanti di aggiornare la dichiarazione delle variabili locali ref per includere il modificatore
readonly .
La dichiarazione readonly struct per indicare che uno struct non è modificabile e deve essere passato come
parametro in ai relativi metodi membro. L'aggiunta del modificatore readonly a una dichiarazione di
struct esistente è una modifica compatibile a livello binario.
La dichiarazione ref struct per indicare che un tipo di struct accede direttamente alla memoria gestita e
deve sempre essere allocato nello stack. L'aggiunta del modificatore ref a una dichiarazione struct
esistente è una modifica incompatibile. ref struct non può essere un membro di una classe o usato in altre
posizioni in cui potrebbe allocato nell'heap.
È possibile leggere altre informazioni su tutte queste modifiche in Scrivere codice efficiente e sicuro.
Variabili locali e valori restituiti per riferimento
Questa funzionalità abilita algoritmi che usano e restituiscono riferimenti a variabili definite altrove. Un esempio
è lavorare con matrici di grandi dimensioni e trovare un'unica posizione con determinate caratteristiche. Il
metodo seguente restituisce un riferimento a questa risorsa di archiviazione nella matrice:

public static ref int Find(int[,] matrix, Func<int, bool> predicate)


{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}

È possibile dichiarare il valore restituito come ref e modificare tale valore nella matrice, come illustrato nel
codice seguente:

ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);


Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);
Il linguaggio C# usa diverse regole per evitare l'uso improprio delle variabili locali ref e dei valori restituiti:
È necessario aggiungere la parola chiave ref alla firma del metodo e a tutte le istruzioni return in un
metodo.
In questo modo appare chiaro che le restituzioni avvengono in base al riferimento nell'intero metodo.
Un elemento ref return può essere assegnato a una variabile value o a una variabile ref .
Il chiamante determina se il valore restituito viene copiato oppure no. L'omissione del modificatore
ref quando si assegna il valore restituito indica che il chiamante desidera una copia del valore e non
un riferimento alla risorsa di archiviazione.
Non è possibile assegnare un valore restituito del metodo standard a una variabile locale ref .
Questo non consente istruzioni come ref int i = sequence.Count();
Non è possibile restituire un elemento ref a una variabile la cui durata è limitata alla durata di esecuzione
del metodo.
Ciò significa che non è possibile restituire un riferimento a una variabile locale o a una variabile con
ambito simile.
Non è possibile usare variabili locali e valori restituiti ref con i metodi asincroni.
Il compilatore non può stabilire se la variabile a cui si fa riferimento è stata impostata sul valore finale
quando il metodo asincrono restituisce il controllo.
L'aggiunta di variabili locali e valori restituiti ref abilita algoritmi più efficienti, evitando la copia dei valori o
l'esecuzione ripetuta di operazioni di dereferenziazione.
L'aggiunta di ref al valore restituito è una modifica compatibile a livello di codice sorgente. Il codice esistente
viene compilato, ma il valore restituito ref viene copiato quando è assegnato. I chiamanti devono aggiornare
l'archiviazione per il valore restituito in una variabile locale ref per archiviare il valore restituito come
riferimento.
Ora è possibile riassegnare le variabili locali ref per fare riferimento a istanze diverse dopo l'inizializzazione. Il
codice seguente ora consente di eseguire la compilazione:

ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization


refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

Per ulteriori informazioni, vedere l'articolo relativo a ref returni e ref variabili localie l'articolo su foreach .
Per altre informazioni, vedere l'articolo relativo alla parola chiave ref.
Espressioni condizionali ref

Infine, l'espressione condizionale può produrre un risultato ref invece di un risultato valore. Ad esempio, per
recuperare un riferimento al primo elemento in una di due matrici, scrivere il codice seguente:

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

La variabile r è un riferimento al primo valore in arr o otherArr .


Per altre informazioni, vedere Operatore ?: nelle informazioni di riferimento sul linguaggio.
in modificatore di parametro
La in parola chiave integra le parole chiave ref e out esistenti per passare gli argomenti per riferimento. La
parola chiave in specifica il passaggio dell'argomento per riferimento, ma il metodo chiamato non modifica il
valore.
È possibile dichiarare gli overload che passano per valore o per riferimento di sola lettura, come illustrato nel
codice seguente:

static void M(S arg);


static void M(in S arg);

L'overload per valore (prima nell'esempio precedente) è migliore della versione di riferimento per ReadOnly. Per
chiamare la versione con l'argomento di riferimento di sola lettura, è necessario includere il modificatore in
durante la chiamata del metodo.
Per ulteriori informazioni, vedere l'articolo sul in modificatore di parametro.
L'istruzione fixed è supportata da più tipi
L'istruzione fixedsupportava un set di tipi limitato. A partire da C# 7.3, qualsiasi tipo che contiene un metodo
GetPinnableReference() che restituisce ref T oppure ref readonly T può essere fixed . L'aggiunta di questa
funzionalità implica che è possibile usare fixed con System.Span<T> e i tipi correlati.
Per ulteriori informazioni, vedere l'articolo relativo all' fixed istruzione nelle informazioni di riferimento sul
linguaggio.
I campi di indicizzazione fixed non richiedono il blocco
Si prenda in considerazione lo struct seguente:

unsafe struct S
{
public fixed int myFixedField[10];
}

Nelle versioni precedenti di C# era necessario bloccare una variabile per accedere a uno dei valori Integer che
fanno parte di myFixedField . Il codice seguente viene ora compilato senza bloccare la variabile p in
un'istruzione fixed separata:

class C
{
static S s = new S();

unsafe public void M()


{
int p = s.myFixedField[5];
}
}

La variabile p accede a un elemento myFixedField . Non è necessario dichiarare una variabile int* distinta. È
ancora necessario un unsafe contesto. Nelle versioni precedenti di C# era necessario dichiarare un secondo
puntatore fisso:
class C
{
static S s = new S();

unsafe public void M()


{
fixed (int* ptr = s.myFixedField)
{
int p = ptr[5];
}
}
}

Per ulteriori informazioni, vedere l'articolo relativo all' fixed istruzione.


Le matrici stackalloc supportano gli inizializzatori
In precedenza era possibile specificare i valori per gli elementi in una matrice al momento dell'inizializzazione:

var arr = new int[3] {1, 2, 3};


var arr2 = new int[] {1, 2, 3};

Ora la stessa sintassi può essere applicata alle matrici dichiarate con stackalloc :

int* pArr = stackalloc int[3] {1, 2, 3};


int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

Per ulteriori informazioni, vedere l'articolo relativo all' stackalloc operatore .


Miglioramento dei vincoli generici
Ora è possibile specificare il tipo System.Enum o System.Delegate come vincoli di classe di base per un
parametro di tipo.
È anche possibile usare il nuovo unmanaged vincolo per specificare che un parametro di tipo deve essere un tipo
non gestitoche non ammette i valori null.
Per ulteriori informazioni, vedere gli articoli sui vincoli where generici e sui vincoli sui parametri di tipo.
L'aggiunta di questi vincoli ai tipi esistenti è una modifica incompatibile. I tipi generici chiusi potrebbero non
soddisfare più questi nuovi vincoli.
Tipi restituiti asincroni generalizzati
La restituzione di un oggetto Task dai metodi asincroni può introdurre colli di bottiglia delle prestazioni in
determinati percorsi. Task è un tipo di riferimento, quindi usarlo significa allocare un oggetto. Nei casi in cui un
metodo dichiarato con il modificatore async restituisce un risultato memorizzato nella cache o viene
completato in modo sincrono, le allocazioni aggiuntive possono diventare impegnative in termini di tempo nelle
sezioni di codice critiche per le prestazioni. Possono diventare onerose se si verificano in cicli ridotti.
La nuova funzionalità del linguaggio significa che i tipi restituiti dal metodo asincrono non sono limitati a Task ,
Task<T> e void . Il tipo restituito deve comunque essere conforme al modello asincrono ovvero deve essere
accessibile un metodo GetAwaiter . Come esempio concreto, il ValueTask tipo è stato aggiunto a .NET per usare
questa nuova funzionalità del linguaggio:
public async ValueTask<int> Func()
{
await Task.Delay(100);
return 5;
}

NOTE
È necessario aggiungere il pacchetto NuGet System.Threading.Tasks.Extensions > per poter usare il
ValueTask<TResult> tipo.

Questo miglioramento è particolarmente utile perché consente agli autori di librerie di evitare l'allocazione di un
Task in codice critico per le prestazioni.

Nuove opzioni del compilatore


Le nuove opzioni del compilatore supportano nuovi scenari di compilazione e DevOps per i programmi in C#.
Generazione assembly di riferimento
Sono disponibili due nuove opzioni del compilatore che generano gli assembly di solo riferimento: -refout e -
refonly. Gli articoli collegati illustrano in modo più dettagliato queste opzioni e gli assembly di riferimento.
Firma pubblica o open source
L'opzione del compilatore -publicsign indica al compilatore di firmare l'assembly usando una chiave pubblica.
L'assembly viene contrassegnato come firmato, ma la firma proviene dalla chiave pubblica. Questa opzione
consente di compilare assembly firmati da progetti open source con una chiave pubblica.
Per altre informazioni, vedere l'articolo sull'opzione del compilatore -publicsign.
pathmap
L'opzione del compilatore -pathmap indica al compilatore di sostituire i percorsi di origine dall'ambiente di
compilazione con i percorsi di origine mappati. L'opzione -pathmap controlla il percorso di origine scritto dal
compilatore nei file PDB o per CallerFilePathAttribute.
Per altre informazioni, vedere l'articolo sull'opzione del compilatore -pathmap.
Informazioni sulle modifiche di rilievo nel
compilatore C#
02/11/2020 • 2 minutes to read • Edit Online

Il team Roslyn gestisce un elenco di modifiche di rilievo nei compilatori C# e Visual Basic. Per informazioni sulle
modifiche, vedere questi collegamenti nel repository GitHub:
Modifiche di rilievo nella versione 16,8 di VS2019 che verranno introdotte per .NET 5,0 e C# 9,0
Modifiche di rilievo in VS2019 Update 1 e versioni successive rispetto a VS2019
Modifiche di rilievo rispetto a VS2017 (C# 7)
Modifiche di rilievo in Roslyn 3,0 (VS2019) da Roslyn 2. * (VS2017)
Modifiche di rilievo in Roslyn 2,0 (VS2017) da Roslyn 1. * (VS2015) e dal compilatore C# nativo (VS2013 e
precedenti).
Modifiche di rilievo in Roslyn 1,0 (VS2015) dal compilatore C# nativo (VS2013 e precedenti).
Modifica della versione Unicode in C# 6
Cronologia di C#
28/01/2021 • 23 minutes to read • Edit Online

Questo articolo include la cronologia di ogni versione principale del linguaggio C#. Il team di C# continua a
innovare il prodotto e ad aggiungere nuove funzionalità. Informazioni dettagliate sullo stato delle funzionalità
del linguaggio, include le funzionalità prese in considerazione per versioni future, sono reperibili nel repository
dotnet/roslyn su GitHub.

IMPORTANT
Per alcune funzionalità, il linguaggio C# si basa sui tipi e metodi inclusi in quella che la specifica C# definisce la libreria
standard. La piattaforma .NET rende disponibili questi tipi e metodi in numerosi pacchetti. Un esempio è l'elaborazione
delle eccezioni. Ogni istruzione o espressione throw viene controllata per assicurarsi che l'oggetto generato derivi da
Exception. Analogamente, ogni istruzione catch viene controllata per verificare che il tipo intercettato derivi da
Exception. In ogni versione potrebbero essere aggiunti nuovi requisiti. Per usare le funzionalità del linguaggio più recenti
in ambienti meno recenti, potrebbe essere necessario installare librerie specifiche. Queste dipendenze sono documentate
nella pagina per ogni versione specifica. Le informazioni sulle relazioni tra linguaggio e libreria possono essere utili per
comprendere meglio questa dipendenza.

Gli strumenti di compilazione per C# considerano la versione del linguaggio principale più recente la versione
del linguaggio predefinita. Potrebbero esistere versioni intermedie tra le versioni principali, descritte in dettaglio
in altri articoli in questa sezione. Per usare le funzionalità più recenti in una versione a punti, è necessario
configurare la versione in lingua del compilatore e selezionare la versione. Sono state rilasciate tre versioni a
partire da C# 7,0:
C# 7,3:
C# 7.3 è disponibile a partire da Visual Studio 2017 versione 15.7 e .NET Core 2.1 SDK.
C# 7,2:
C# 7,2 è disponibile a partire da Visual Studio 2017 versione 15,5 e .NET Core 2,0 SDK.
C# 7,1:
C# 7.1 è disponibile a partire da Visual Studio 2017 versione 15.3 e .NET Core 2.0 SDK.

C# versione 1.0
Quando si torna indietro e si guarda, C# versione 1,0, rilasciata con Visual Studio .NET 2002, è molto simile a
Java. Come affermato negli obiettivi di progettazione dichiarati per ECMA, C# cercava di essere "un linguaggio
orientato agli oggetti di utilizzo generico, semplice e moderno". All'epoca, somigliare molto a Java significava
aver raggiunto tali obiettivi di progettazione di allora.
Ma se si guarda ora com'era C# 1.0, la sensazione è di disorientamento. Mancavano le capacità asincrone
predefinite e alcune delle semplici funzionalità relative ai generics che ora si danno per scontate. In realtà,i
generics mancavano completamente. E LINQ? Non era ancora disponibile. Queste aggiunte avrebbero richiesto
ancora alcuni anni.
Rispetto alla versione odierna, C# versione 1.0 sembra privo di funzionalità e costringeva gli sviluppatori a
scrivere codice piuttosto prolisso. Da qualche parte, tuttavia, bisognava cominciare. Per la piattaforma Windows,
C# versione 1.0 rappresentava una valida alternativa a Java.
Le principali funzionalità di C# 1.0 includevano:
Classi
Struct
Interfacce
Eventi
Proprietà
Delegati
Operatori ed espressioni
Istruzioni
Attributes (Attributi)

Versione C# 1.2
C# versione 1,2 fornita con Visual Studio .NET 2003. Conteneva alcuni piccoli miglioramenti del linguaggio.
L'aspetto più importante è che a partire da questa versione, il codice veniva generato in un ciclo foreach
denominato Dispose in IEnumerator quando IEnumerator implementava IDisposable.

C# versione 2.0
Qui le cose iniziano a farsi interessanti. Ecco alcune delle funzionalità principali di C# 2.0, rilasciato nel 2005
insieme a Visual Studio 2005:
Generics
Tipi parziali
Metodi anonimi
Tipi valore nullable
Iterators
Covarianza e controvarianza
Altre funzionalità di C# 2.0 aggiungevano capacità alle funzionalità esistenti:
Accessibilità separata getter/setter
Conversioni di gruppi di metodi (delegati)
Classi statiche
Inferenza del delegato
All'inizio C# era un linguaggio orientato agli oggetti (OO) generico, ma con la versione 2.0 la situazione cambiò
con grande rapidità. Dopo aver trovato una posizione stabile, gli sviluppatori di C# hanno affrontato alcuni gravi
punti dolenti. E li hanno affrontati alla grande.
Con generics, tipi e metodi possono operare su un tipo arbitrario, mantenendo comunque l'indipendenza dal
tipo. La classe List<T>, ad esempio, consente di eseguire un'iterazione attraverso List<string> o List<int>
eseguendo operazioni indipendenti dai tipi su tali stringhe o tali numeri interi. È consigliabile usare i generics
anziché creare ListInt che deriva da ArrayList o eseguire il cast da Object per ogni operazione.
Con C# versione 2.0 sono arrivati gli iteratori. In breve, gli iteratori consentono di esaminare tutti gli elementi di
un List (o di altri tipi enumerabili) con un ciclo foreach . La presenza degli iteratori come parte fondamentale
del linguaggio ha migliorato notevolmente la leggibilità del codice e la possibilità di comprenderlo.
C#, tuttavia, continuava a correre dietro a Java, che aveva già rilasciato versioni che prevedevano generics e
iteratori, ma questa situazione sarebbe cambiata presto. Man mano che procedevano nella loro evoluzione,
infatti, i due linguaggi continuarono a differenziarsi.

C# versione 3.0
C# versione 3.0 è stato rilasciato alla fine del 2007, insieme a Visual Studio 2008, anche se la gamma completa
delle funzionalità del linguaggio è stata in realtà rilasciata con .NET Framework versione 3.5. Questa versione
rappresenta un cambiamento fondamentale, perché caratterizza C# come linguaggio di programmazione dalle
caratteristiche davvero eccezionali. Ecco alcune delle funzionalità principali di questa versione:
Proprietà implementate automaticamente
Tipi anonimi
Espressioni di query
Espressioni lambda
Alberi delle espressioni
Metodi di estensione
Variabili locali tipizzate in modo implicito
Metodi parziali
Inizializzatori di oggetto e di raccolta
A posteriori, molte di queste funzionalità sembrano inevitabili e inseparabili, perché si fondono l'una con l'altra
in modo strategico. Si pensa in genere che la funzionalità killer di questa versione di C# sia l'espressione di
query, nota anche come Language-Integrated Query (LINQ),
che, grazie a una vista più particolareggiata, consente di esaminare alberi delle espressioni, espressioni lambda
e tipi anonimi, le fondamenta su cui la funzionalità LINQ stessa si basa. In entrambi i casi, tuttavia, C# 3.0 ha
introdotto un concetto rivoluzionario. C# 3.0 ha iniziato a creare i presupposti per la trasformazione di C# in un
linguaggio ibrido, orientato agli oggetti e funzionale.
In particolare, ha reso possibile scrivere query dichiarative nello stile di SQL per eseguire operazioni, tra l'altro,
sulle raccolte. Anziché scrivere un ciclo for per calcolare la media di un elenco di numeri interi, è diventato
possibile eseguire tale operazione semplicemente usando list.Average() . La combinazione di espressioni di
query e di metodi di estensione rendeva quell'elenco di numeri interi molto più interessante.
La comprensione e l'integrazione effettive del concetto da parte degli utenti ha richiesto tempo, ma si è
gradualmente realizzata. E ora, dopo anni, il codice è molto più conciso, semplice e funzionale.

C# versione 4.0
In C# la versione 4,0, rilasciata con Visual Studio 2010, avrebbe avuto un tempo difficile per lo stato
rivoluzionario della versione 3,0. Con la versione 3.0, il linguaggio C# è uscito con decisione dall'ombra di Java e
ha assunto una propria rilevanza. Il linguaggio stava rapidamente diventando elegante.
La versione successiva introdusse comunque alcune nuove interessanti funzionalità:
Associazione dinamica
Argomenti denominati/facoltativi
Covarianti e controvarianti generiche
Tipi di interoperabilità incorporati
I tipi di interoperabilità risolvevano un problema di distribuzione. Le covarianze e le controvarianze generiche
offrono modalità più avanzate per l'uso dei generics, ma hanno un'aria accademica e sono probabilmente
apprezzate soprattutto dagli autori di librerie e di framework. I parametri denominati e facoltativi consentono di
eliminare molti overload di metodi e sono più comodi da usare. Ma nessuna di queste funzionalità è
precisamente un modifica di paradigma.
La caratteristica principale è stata l'introduzione della parola chiave dynamic . La parola chiave dynamic
introdotta in C# versione 4.0 offre la possibilità di eseguire l'override del compilatore per la tipizzazione in fase
di compilazione. Con la parola chiave dynamic è possibile creare costrutti simili a quelli dei linguaggi tipizzati in
modo dinamico, come JavaScript. È possibile creare dynamic x = "a string" e quindi aggiungere 6, lasciando
decidere al runtime cosa deve succedere dopo.
L'associazione dinamica è suscettibile di errori ma offre anche la possibilità di usare il linguaggio in modo
estremamente avanzato.

C# versione 5.0
C# versione 5,0, rilasciata con Visual Studio 2012, è stata una versione focalizzata del linguaggio. Quasi tutto
l'impegno per questa versione è stato indirizzato verso un altro concetto rivoluzionario: il modello async e
await per la programmazione asincrona. Ecco l'elenco delle funzionalità principali:

Membri asincroni
Attributi informativi sul chiamante
Vedere anche
Progetto di codice: Attributi informativi sul chiamante in C# 5.0
Gli attributi informativi sul chiamante consentono di recuperare facilmente informazioni sul contesto di
esecuzione senza dover ricorrere a una grande quantità di codice di reflection boilerplate. Questi attributi hanno
molte applicazioni nelle attività di diagnostica e di registrazione.
Ma le vere stelle di questa versione sono async e await . Con il rilascio di queste funzionalità nel 2012, C# ha
cambiato di nuovo le carte in tavola, incorporando l'asincronia nel linguaggio e assegnandole un ruolo
fondamentale. Tutti coloro che hanno avuto a che fare con operazioni di lunga esecuzione e con
l'implementazione di reti di callback hanno molto apprezzato questa funzionalità.

C# versione 6.0
Le versioni 3.0 e 5.0 hanno aggiunto a C# nuove funzionalità eccezionali per un linguaggio orientato agli
oggetti. Con la versione 6,0, rilasciata con Visual Studio 2015, è possibile evitare di eseguire una funzionalità
killer dominante e rilasciare invece molte funzionalità più piccole che rendevano più produttive la
programmazione in C#. Eccone alcuni:
Importazioni statiche
Filtri eccezioni
Inizializzatori di proprietà automatiche
Membri con corpo di espressione
Propagazione Null
Interpolazione di stringhe
operatore NameOf
Le altre nuove funzionalità includono:
Inizializzatori di indice
Await nei blocchi catch e finally
Valori predefiniti per le proprietà solo getter
Ognuna di queste funzionalità è interessante in sé, ma osservandole nel loro complesso si scopre uno schema
interessante. In questa versione, C# ha eliminato il boilerplate del linguaggio, per rendere il codice più conciso e
leggibile. Per gli amanti della pulizia e della semplicità del codice, questa versione del linguaggio rappresenta
una vittoria notevole.
Con questa versione è stata introdotta un'altra caratteristica, che di per sé non rappresenta una funzionalità
tradizionale del linguaggio. È stato rilasciato Roslyn, il compilatore come servizio. Il compilatore C# è ora scritto
in C# e può essere usato all'interno dei progetti di programmazione.

C# versione 7.0
C# versione 7,0 è stato rilasciato con Visual Studio 2017. Questa versione presenta alcune delle caratteristiche
evolutive e innovative di C# 6.0, ma senza il compilatore come servizio. Ecco alcune delle nuove funzionalità:
Variabili out
Tuple e decostruzione
Criteri di ricerca
Funzioni locali
Membri di espressioni corpo espansi
Variabili locali e valori restituiti per riferimento
Altre funzionalità:
Variabili discard
Valori letterali binari e separatori di cifre
Espressioni throw
Tutte queste caratteristiche offrono nuove utili funzionalità agli sviluppatori, oltre alla possibilità di scrivere
codice più pulito che mai. Una funzionalità di particolare rilievo è la possibilità di condensare la dichiarazione di
variabili da usare con la parola chiave out , consentendo più valori restituiti tramite tupla.
Ma C# è ora destinato a un uso ancora più ampio. .NET Core ora supporta qualsiasi sistema operativo ed è
decisamente orientato al cloud e alla portabilità. Queste nuove capacità, insieme alla realizzazione di nuove
funzionalità, tengono sicuramente impegnati i progettisti del linguaggio.

C# versione 7,1
C# ha iniziato a rilasciare le versioni dei punti con c# 7,1. Questa versione ha aggiunto l'elemento di
configurazione della selezione della versione della lingua , tre nuove funzionalità del linguaggio e il nuovo
comportamento del compilatore.
Le nuove funzionalità relative al linguaggio in questa versione sono:
async``Main Metodo
Il punto di ingresso per un'applicazione può avere il modificatore async .
default espressioni letterali
Quando è possibile dedurre il tipo di destinazione, si possono usare espressioni letterali predefinite
nelle espressioni con valore predefinito.
Nomi di elemento di tupla dedotti
In molti casi i nomi degli elementi della tupla possono essere dedotti dall'inizializzazione tupla.
Criteri di ricerca su parametri di tipo generico
È possibile usare espressioni di criteri di ricerca sulle variabili in cui il tipo è un parametro di tipo
generico.
Infine, il compilatore offre due opzioni -refout e -refonly che controllano la generazione dell'assembly di
riferimento.

C# versione 7,2
C# 7,2 ha aggiunto diverse funzionalità del linguaggio Small:
Tecniche per la scrittura di codice efficiente e sicuro
Una combinazione di miglioramenti della sintassi che consentono l'utilizzo dei tipi valore tramite la
semantica di riferimento.
Argomenti denominati non finali
Gli argomenti denominati possono essere seguiti da argomenti posizionali.
Caratteri di sottolineatura iniziali nei valori letterali numerici
I valori letterali numerici possono ora includere caratteri di sottolineatura iniziali prima di qualsiasi
cifra stampata.
private protected modificatore di accesso
Il modificatore di accesso private protected consente l'accesso per le classi derivate nello stesso
assembly.
Espressioni condizionali ref
Il risultato di un'espressione condizionale ( ?: ) può ora essere un riferimento.

C# versione 7,3
Esistono due temi principali per C# versione 7.3. Un tema comprende funzionalità che consentono al codice
gestito di offrire prestazioni altrettanto elevate di quelle del codice non gestito. Il secondo tema comprende
miglioramenti incrementali delle funzionalità esistenti. Inoltre, in questa versione sono state aggiunte nuove
opzioni del compilatore.
Le nuove funzionalità seguenti supportano il tema del miglioramento delle prestazioni per il codice gestito:
È possibile accedere a campi fissi senza blocco.
È possibile riassegnare le ref variabili locali.
È possibile usare gli inizializzatori nelle stackalloc matrici.
È possibile usare fixed istruzioni con qualsiasi tipo che supporta un modello.
È possibile usare vincoli generici aggiuntivi.
Sono stati apportati i miglioramenti seguenti alle funzionalità esistenti:
È possibile testare == e != con i tipi di tupla.
È possibile usare le variabili di espressione in più posizioni.
È possibile associare gli attributi al campo sottostante delle proprietà implementate automaticamente.
È stata migliorata la risoluzione del metodo quando gli argomenti sono diversi da in .
La risoluzione dell'overload ora presenta un minor numero di casi ambigui.
Le nuove opzioni del compilatore sono le seguenti:
-publicsign per abilitare la firma degli assembly con software open source.
-pathmap per fornire un mapping per le directory di origine.

C# versione 8,0
C# 8,0 è la prima versione principale di C# che è destinata specificamente a .NET Core. Alcune funzionalità si
basano sulle nuove funzionalità CLR, altre sui tipi di libreria aggiunti solo in .NET Core. C# 8,0 aggiunge le
funzionalità e i miglioramenti seguenti al linguaggio C#:
Membri di sola lettura
Metodi di interfaccia predefiniti
Miglioramentidi criteri di ricerca:
Espressioni switch
Criteri per le proprietà
Criteri per le tuple
Criteri per la posizione
Utilizzo di dichiarazioni
Funzioni locali statiche
Struct ref Disposable
Tipi riferimento nullable
Flussi asincroni
Indici e intervalli
Assegnazione di Unione null
Tipi costruiti non gestiti
Stackalloc nelle espressioni annidate
Miglioramento delle stringhe verbatim interpolate
I membri di interfaccia predefiniti richiedono miglioramenti in CLR. Queste funzionalità sono state aggiunte in
CLR per .NET Core 3,0. Gli intervalli e gli indici e i flussi asincroni richiedono nuovi tipi nelle librerie di .NET Core
3,0. I tipi di riferimento Nullable, implementati nel compilatore, sono molto più utili quando le librerie vengono
annotate per fornire informazioni semantiche relative allo stato null degli argomenti e dei valori restituiti.
Queste annotazioni vengono aggiunte nelle librerie di .NET Core.
Articolo originariamente pubblicato nel blog NDepend, gentilmente concesso da Erik Dietrich e Patrick
Smacchia.
Relazioni tra funzionalità del linguaggio e tipi di
libreria
02/11/2020 • 4 minutes to read • Edit Online

La definizione del linguaggio C# richiede che una libreria standard includa determinati tipi e specifici membri
accessibili per tali tipi. Il compilatore genera codice che usa questi tipi e membri richiesti per numerose
funzionalità del linguaggio diverse. Quando necessario, sono disponibili pacchetti NuGet che contengono i tipi
necessari per le versioni più recenti del linguaggio durante la scrittura di codice per gli ambienti in cui tali tipi o
membri non sono ancora stati distribuiti.
Questa dipendenza dalla funzionalità della libreria standard fa parte del linguaggio C# fin dalla prima versione.
Alcuni esempi in quella versione sono i seguenti:
Exception - usato per tutte le eccezioni generate dal compilatore.
String - il tipo C# string è sinonimo di String.
Int32 - sinonimo di int .
La prima versione era semplice: il compilatore e la libreria standard erano forniti assieme ed esisteva una sola
versione di ognuno.
Nelle versioni successive del linguaggio C# sono stati aggiunti occasionalmente nuovi tipi o membri alle
dipendenze. Alcuni esempi sono INotifyCompletion, CallerFilePathAttribute o CallerMemberNameAttribute. C#
7.0 continua in questa direzione aggiungendo una dipendenza per ValueTuple per implementare la funzionalità
del linguaggio tuple.
Il team di progettazione del linguaggio si impegna per ridurre al minimo la superficie di tipi e membri richiesti
in una libreria standard conforme. Tale obiettivo deve convivere con l'esigenza di realizzare una progettazione
lineare che consenta il facile incorporamento delle nuove funzionalità della libreria nel linguaggio. Le versioni
future di C# includeranno nuove funzionalità che richiedono nuovi tipi e membri in una libreria standard. È
importante comprendere come gestire tali dipendenze nel lavoro di sviluppo.

Gestione delle dipendenze


Gli strumenti del compilatore C# sono ora separati dal ciclo di rilascio delle librerie .NET nelle piattaforme
supportate. In effetti, le diverse librerie .NET hanno cicli di rilascio diversi: .NET Framework in Windows viene
rilasciato come aggiornamento di Windows, .NET Core viene distribuito con una pianificazione separata e le
versioni di Xamarin degli aggiornamenti delle librerie vengono forniti con gli strumenti di Xamarin per ogni
piattaforma di destinazione.
Nella maggior parte dei casi, non si noteranno queste differenze. Quando si lavora con una versione più recente
del linguaggio che richiede funzionalità non ancora presenti nelle librerie .NET nella piattaforma, tuttavia, sarà
necessario fare riferimento ai pacchetti NuGet per fornire i nuovi tipi. Dato che le piattaforme supportate
dall'app vengono aggiornate con le nuove installazioni dei framework, è possibile rimuovere il riferimento
aggiuntivo.
Questa separazione significa che è possibile usare le nuove funzionalità del linguaggio, anche quando i
computer di destinazione potrebbero non disporre del framework corrispondente.
Considerazioni su versione e aggiornamento per gli
sviluppatori C#
02/11/2020 • 4 minutes to read • Edit Online

La compatibilità è un obiettivo molto importante quando vengono aggiunte nuove funzionalità al linguaggio C#.
Nella quasi totalità dei casi, il codice esistente può essere ricompilato con una nuova versione del compilatore
senza alcun problema.
Può essere necessaria maggiore attenzione quando si adottano le nuove funzionalità del linguaggio in una
libreria. Si supponga di creare una nuova libreria con le funzionalità disponibili nella versione più recente e di
dover verificare che le app create con le versioni precedenti del compilatore siano in grado di usarla. In un altro
caso, potrebbe essere necessario aggiornare una libreria esistente quando molti utenti non hanno ancora
eseguito l'aggiornamento delle versioni. Quando si prendono decisioni relative all'adozione di nuove
funzionalità, è necessario tenere presenti due tipi di compatibilità: la compatibilità a livello binario e la
compatibilità a livello di codice sorgente.

Modifiche compatibili a livello binario


Le modifiche apportate a una libreria sono compatibili a livello binario quando la libreria aggiornata può
essere usata senza ricompilare le applicazioni e le librerie che la usano. Gli assembly dipendenti non devono
essere ricompilati, né sono necessarie modifiche al codice sorgente. Le modifiche compatibili a livello binario
sono anche compatibili a livello di codice sorgente.

Modifiche compatibili a livello di codice sorgente


Le modifiche apportate a una libreria sono compatibili a livello di codice sorgente quando le applicazioni e
le librerie che usano la libreria non richiedono modifiche al codice sorgente, ma l'origine deve essere
ricompilata con la nuova versione per garantirne il corretto funzionamento.

Modifiche incompatibili
Se una modifica non è compatibile a livello di codice sorgente né compatibile a livello binario , sono
necessarie sia modifiche al codice sorgente che la ricompilazione nelle applicazioni e librerie dipendenti.

Valutare la libreria
Questi concetti relativi alla compatibilità interessano le dichiarazioni pubbliche e protette per la libreria, non la
relativa implementazione interna. Internamente, l'adozione delle nuove funzionalità è sempre compatibile a
livello binario .
Le modifiche **compatibili a livello binario ** forniscono una nuova sintassi che genera lo stesso codice
compilato per le dichiarazioni pubbliche della sintassi precedente. Ad esempio, la modifica di un metodo in un
membro con corpo di espressione è una modifica compatibile a livello binario :
Codice originale:
public double CalculateSquare(double value)
{
return value * value;
}

Nuovo codice:

public double CalculateSquare(double value) => value * value;

Le modifiche compatibili a livello di codice sorgente introducono una sintassi che modifica il codice
compilato per un membro pubblico, ma in modo compatibile con i siti di chiamata esistenti. Ad esempio, la
modifica di una firma di un metodo da un parametro per valore a un parametro in per riferimento è
compatibile a livello di codice sorgente, ma non a livello binario:
Codice originale:

public double CalculateSquare(double value) => value * value;

Nuovo codice:

public double CalculateSquare(in double value) => value * value;

Negli articoli sulle novità è specificato se l'introduzione di una funzionalità che influisce sulle dichiarazioni
pubbliche è compatibile a livello di codice sorgente o a livello binario.
Tipi (Guida per programmatori C#)
28/01/2021 • 24 minutes to read • Edit Online

Tipi, variabili e valori


C# è un linguaggio fortemente tipizzato. Ogni variabile e costante ha un tipo, così come ogni espressione che
restituisce un valore. Ogni dichiarazione di metodo specifica un nome, un numero di parametri, un tipo e un tipo
(valore, riferimento o output) per ogni parametro di input e per il valore restituito. La libreria di classi .NET
definisce un set di tipi numerici incorporati e tipi più complessi che rappresentano un'ampia gamma di costrutti
logici, ad esempio la file system, le connessioni di rete, le raccolte e le matrici di oggetti e le date. Un tipico
programma C# usa i tipi della libreria di classi e dei tipi definiti dall'utente che modellano i concetti specifici del
dominio del problema del programma.
Le informazioni archiviate in un tipo possono includere gli elementi seguenti:
Lo spazio di archiviazione richiesto da una variabile del tipo.
I valori minimi e massimi che può rappresentare.
I membri (metodi, campi, eventi e così via) in esso contenuti.
Il tipo di base da cui eredita.
Interfaccia o interfacce implementate.
Il percorso in cui viene allocata la memoria per le variabili in fase di esecuzione.
I tipi di operazioni consentite.
Il compilatore usa le informazioni sul tipo per assicurarsi che tutte le operazioni eseguite nel codice siano
indipendenti dai tipi. Se ad esempio si dichiara una variabile di tipo int, il compilatore consente di usare la
variabile anche in operazioni di addizione e sottrazione. Se si prova a eseguire le stesse operazioni su una
variabile di tipo bool, il compilatore genera un errore, come illustrato nell'esempio seguente:

int a = 5;
int b = a + 2; //OK

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;

NOTE
Gli sviluppatori C e C++ devono tenere presente che, in C#, bool non è convertibile in int.

Il compilatore incorpora le informazioni sul tipo nel file eseguibile come metadati. Il Common Language
Runtime (CLR) usa i metadati in fase di esecuzione per garantire una maggiore indipendenza dai tipi quando
alloca e recupera la memoria.
Specifica dei tipi nelle dichiarazioni di variabile
Quando si dichiara una variabile o una costante in un programma, è necessario specificarne il tipo oppure usare
la parola chiave var per consentire al compilatore di dedurre il tipo. L'esempio seguente illustra alcune
dichiarazioni di variabili che usano sia tipi numerici incorporati sia tipi complessi definiti dall'utente:
// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):


char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;

I tipi di parametri del metodo e i valori restituiti vengono specificati nella dichiarazione di metodo. La firma
seguente illustra un metodo che richiede un int come argomento di input e restituisce una stringa:

public string GetName(int ID)


{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Dopo aver dichiarato una variabile, non è possibile ridichiararla con un nuovo tipo e non è possibile assegnare
un valore non compatibile con il tipo dichiarato. Ad esempio, non è possibile dichiarare un valore int e quindi
assegnargli un valore booleano true . Tuttavia, i valori possono essere convertiti in altri tipi, ad esempio
quando sono assegnati a nuove variabili o passati come argomenti di metodo. Una conversione del tipo che non
provoca la perdita di dati viene eseguita automaticamente dal compilatore. mentre una conversione che può
causare la perdita di dati richiede un cast nel codice sorgente.
Per altre informazioni, vedere Cast e conversioni di tipi.

Tipi incorporati
C# fornisce un set standard di tipi predefiniti per rappresentare numeri interi, valori a virgola mobile,
espressioni booleane, caratteri di testo, valori decimali e altri tipi di dati. Sono anche disponibili tipi string e
object incorporati, Questi tipi sono disponibili per l'uso in qualsiasi programma C#. Per l'elenco completo dei
tipi incorporati, vedere tipi incorporati.

Tipi personalizzati
Usare i costrutti struct, class, interface e enum per creare tipi personalizzati. La libreria di classi .NET stessa è una
raccolta di tipi personalizzati offerti da Microsoft che è possibile usare nelle proprie applicazioni. Per
impostazione predefinita, i tipi più comunemente usati nella libreria di classi sono disponibili in qualsiasi
programma C#, Altri diventano disponibili solo quando si aggiunge in modo esplicito un riferimento di progetto
all'assembly in cui sono definiti. Nel momento in cui il compilatore ha un riferimento all'assembly, è possibile
dichiarare variabili (e costanti) dei tipi dichiarati nell'assembly in codice sorgente. Per altre informazioni, vedere
Libreria di classi .NET.

Common Type System


È importante comprendere due punti fondamentali sul sistema di tipi in .NET:
Supporta il principio di ereditarietà. I tipi possono derivare da altri tipi, denominati tipi di base. Il tipo derivato
eredita (con alcune limitazioni) metodi, proprietà e altri membri del tipo di base, che a sua volta può derivare
da un altro tipo. In questo caso, il tipo derivato eredita i membri di entrambi i tipi di base nella gerarchia di
ereditarietà. Tutti i tipi, inclusi i tipi numerici predefiniti, ad esempio System.Int32 (parola chiave C#: int),
derivano in definitiva da un unico tipo di base, ovvero System.Object (parola chiave C#: object). Questa
gerarchia di tipi unificata prende il nome di Common Type System (CTS). Per altre informazioni
sull'ereditarietà in C#, vedere Ereditarietà.
Nel CTS ogni tipo è definito come tipo valore o tipo riferimento. Questi tipi includono tutti i tipi personalizzati
nella libreria di classi .NET e anche i tipi definiti dall'utente. I tipi definiti tramite la parola chiave struct sono
tipi valore e tutti i tipi numerici incorporati sono tipi structs . I tipi definiti tramite la parola chiave class sono
tipi riferimento. I tipi di riferimento e i tipi di valore hanno regole diverse e un comportamento diverso in
fase di esecuzione.
La figura seguente illustra la relazione tra tipi valore e tipi riferimento nel CTS.

NOTE
È possibile osservare come i tipi usati con maggiore frequenza siano tutti organizzati nello spazio dei nomi System.
L'inserimento di un tipo in uno spazio dei nomi, tuttavia, è indipendente dalla categoria a cui appartiene il tipo.

Tipi valore
I tipi valore derivano da System.ValueType, che deriva da System.Object. I tipi che derivano da System.ValueType
hanno un comportamento speciale in CLR. Le variabili dei tipi valore contengono direttamente i rispettivi valori,
ovvero la memoria viene allocata inline nel contesto in cui è dichiarata la variabile. Non esiste un'allocazione
heap o un overhead di Garbage Collection separato per le variabili di tipo valore.
Esistono due categorie di tipi valore: struct e enum.
I tipi numerici incorporati sono struct e hanno campi e metodi a cui è possibile accedere:

// constant field on type byte.


byte b = byte.MaxValue;

Ma si dichiarano e si assegnano valori come se fossero tipi non aggregati semplici:


byte num = 0xA;
int i = 5;
char c = 'Z';

I tipi di valore sono sealed, il che significa che non è possibile derivare un tipo da un tipo di valore, ad esempio
System.Int32 . Non è possibile definire uno struct da ereditare da qualsiasi classe o struct definita dall'utente
perché uno struct può ereditare solo da System.ValueType . Un tipo struct può tuttavia implementare una o più
interfacce. È possibile eseguire il cast di un tipo struct a qualsiasi tipo di interfaccia implementato; questo cast fa
in modo che un'operazione di conversione boxing incapsulare lo struct all'interno di un oggetto tipo riferimento
sull'heap gestito. Le operazioni di conversione boxing si verificano quando si passa un tipo valore a un metodo
che accetta System.Object o qualsiasi tipo di interfaccia come parametro di input. Per altre informazioni, vedere
Boxing e unboxing.
Usare la parola chiave struct per creare tipi valore personalizzati. In genere, un tipo struct viene usato come
contenitore per un piccolo set di variabili correlate, come illustrato nell'esempio seguente:

public struct Coords


{
public int x, y;

public Coords(int p1, int p2)


{
x = p1;
y = p2;
}
}

Per ulteriori informazioni sugli struct, vedere tipi di struttura. Per ulteriori informazioni sui tipi di valore, vedere
tipi di valore.
L'altra categoria di tipi valore è enum. Un tipo enum definisce un set di costanti integrali denominate.
L'enumerazione System.IO.FileMode nella libreria di classi .NET, ad esempio, contiene un set di valori interi
costanti e denominati che specificano come deve essere aperto un file. Viene definito come illustrato
nell'esempio seguente:

public enum FileMode


{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}

Il valore della costante System.IO.FileMode.Create è 2. Tuttavia, il nome è molto più significativo per gli utenti
che leggono il codice sorgente e per questo motivo è preferibile usare le enumerazioni anziché i numeri letterali
costanti. Per altre informazioni, vedere System.IO.FileMode.
Tutte le enumerazioni ereditano da System.Enum, che eredita da System.ValueType. Tutte le regole valide per i
tipi struct sono valide anche per le enumerazioni. Per altre informazioni sulle enumerazioni, vedere tipi di
enumerazione.
Tipi riferimento
Un tipo definito come classe, delegato, matrice o interfaccia è un tipo riferimento. In fase di esecuzione, quando
si dichiara una variabile di un tipo riferimento, la variabile contiene il valore null fino a quando non si crea in
modo esplicito un oggetto usando l'operatore new o fino a quando non le viene assegnato un oggetto creato
altrove tramite new , come illustrato nell'esempio seguente:

MyClass mc = new MyClass();


MyClass mc2 = mc;

Un'interfaccia deve essere inizializzata insieme a un oggetto classe che la implementa. Se MyClass implementa
IMyInterface , si crea un'istanza di IMyInterface , come illustrato nell'esempio seguente:

IMyInterface iface = new MyClass();

Quando viene creato l'oggetto, la memoria viene allocata nell'heap gestito e la variabile mantiene solo un
riferimento al percorso dell'oggetto. I tipi nell'heap gestito richiedono un overhead quando vengono allocati e
quando vengono recuperati dalla funzionalità di gestione automatica della memoria di CLR, nota come Garbage
Collection. Tuttavia, anche Garbage Collection è altamente ottimizzata e, nella maggior parte degli scenari, non
crea un problema di prestazioni. Per altre informazioni sulla Garbage Collection, vedere Gestione automatica
della memoria.
Tutte le matrici sono tipi riferimento, anche se i relativi elementi sono tipi valore. Le matrici derivano in modo
implicito dalla classe System.Array, ma vengono dichiarate e usate con la sintassi semplificata fornita da C#,
come illustrato nell'esempio seguente:

// Declare and initialize an array of integers.


int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.


int len = nums.Length;

I tipi riferimento supportano completamente l'ereditarietà. Quando si crea una classe, è possibile ereditare da
qualsiasi altra interfaccia o classe che non è definita come sealede altre classi possono ereditare dalla classe ed
eseguire l'override dei metodi virtuali. Per altre informazioni su come creare classi personalizzate, vedere Classi
e struct. Per altre informazioni sull'ereditarietà e sui metodi virtuali, vedere Ereditarietà.

Tipi di valori letterali


In C# i valori letterali ricevono un tipo dal compilatore. È possibile specificare come deve essere tipizzato un
valore letterale numerico aggiungendo una lettera alla fine del numero. Per specificare, ad esempio, che il valore
4.56 deve essere considerato come un tipo float, aggiungere una "f" o una "F" dopo il numero: 4.56f . Se non
viene aggiunta alcuna lettera, il compilatore dedurrà un tipo per il valore letterale. Per ulteriori informazioni sui
tipi che è possibile specificare con i suffissi di lettera, vedere tipi numerici integrali e tipi numerici a virgola
mobile.
Poiché i valori letterali sono tipizzati e tutti i tipi derivano in ultima analisi da System.Object , è possibile scrivere
e compilare codice come il codice seguente:

string s = "The answer is " + 5.ToString();


// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();


// Outputs: "System.Int32"
Console.WriteLine(type);
Tipi generici
Un tipo può essere dichiarato con uno o più parametri di tipo che agiscono da segnaposto per il tipo effettivo
(tipo concreto) che il codice client specifica quando si crea un'istanza del tipo. Questi tipi sono definiti tipi
generici. Ad esempio, il tipo .NET System.Collections.Generic.List<T> ha un parametro di tipo a cui per
convenzione viene assegnato il nome T. Quando si crea un'istanza del tipo, si specifica il tipo degli oggetti che
l'elenco conterrà, ad esempio, String:

List<string> stringList = new List<string>();


stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);

L'uso del parametro di tipo consente di riutilizzare la stessa classe per contenere qualsiasi tipo di elemento
senza dover convertire ogni elemento in object. Le classi di raccolte generiche sono denominate raccolte
fortemente tipizzate perché il compilatore conosce il tipo specifico degli elementi della raccolta e può generare
un errore in fase di compilazione se, ad esempio, si tenta di aggiungere un numero intero all' stringList
oggetto nell'esempio precedente. Per altre informazioni, vedere Generics.

Tipi impliciti, tipi anonimi e tipi di valore Nullable


Come indicato in precedenza, è possibile tipizzare una variabile locale (ma non membri di classe) in modo
implicito usando la parola chiave var. Alla variabile viene comunque assegnato un tipo in fase di compilazione,
specificato dal compilatore. Per altre informazioni, vedere Variabili locali tipizzate in modo implicito.
Può essere scomodo creare un tipo denominato per set semplici di valori correlati che non si intende archiviare
o passare all'esterno dei limiti del metodo. A questo scopo è possibile creare tipi anonimi. Per ulteriori
informazioni, vedere tipi anonimi.
I tipi di valore normali non possono avere un valore null. Tuttavia, è possibile creare tipi di valore Nullable
accodando un oggetto ? dopo il tipo. Ad esempio, int? è un tipo int che può avere anche il valore null. I tipi
di valore nullable sono istanze del tipo di struct generico System.Nullable<T> . I tipi di valore nullable sono
particolarmente utili quando si passano dati da e verso database in cui i valori numerici potrebbero essere null.
Per altre informazioni, vedere tipi di valore Nullable.

Tipo in fase di compilazione e tipo di runtime


Una variabile può presentare tipi diversi in fase di compilazione e in fase di esecuzione. Il tipo in fase di
compilazione è il tipo dichiarato o derivato della variabile nel codice sorgente. Il tipo in fase di esecuzione è il
tipo dell'istanza a cui fa riferimento la variabile. Spesso questi due tipi sono uguali, come nell'esempio seguente:

string message = "This is a string of characters";

In altri casi, il tipo in fase di compilazione è diverso, come illustrato nei due esempi seguenti:

object anotherMessage = "This is another string of characters";


IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";

In entrambi gli esempi precedenti, il tipo in fase di esecuzione è string . Il tipo in fase di compilazione si trova
object nella prima riga e IEnumerable<char> nel secondo.

Se i due tipi sono diversi per una variabile, è importante comprendere quando si applicano il tipo in fase di
compilazione e il tipo di Runtime. Il tipo in fase di compilazione determina tutte le azioni eseguite dal
compilatore. Queste azioni del compilatore includono la risoluzione della chiamata al metodo, la risoluzione
dell'overload e i cast impliciti ed espliciti disponibili. Il tipo in fase di esecuzione determina tutte le azioni che
vengono risolte in fase di esecuzione. Queste azioni di run-time includono l'invio di chiamate al metodo virtuale,
la valutazione is di switch espressioni ed altre API di test dei tipi. Per comprendere meglio il modo in cui il
codice interagisce con i tipi, riconoscere l'azione da applicare al tipo.

Sezioni correlate
Per altre informazioni, vedere gli articoli seguenti:
Cast e conversioni di tipi (C#)
Conversione boxing e unboxing
Utilizzo del tipo dinamico
Tipi di valore
Tipi di riferimento
Classi e struct
Tipi anonimi
Generics

Specifiche del linguaggio C#


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

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Conversione dei tipi di dati XML
Tipi integrali
Tipi riferimento nullable
28/01/2021 • 18 minutes to read • Edit Online

In C# 8.0 sono stati introdotti i tipi riferimento nullable e i tipi riferimento non nullable che consentono
di creare istruzioni importanti sulle proprietà delle variabili dei tipi riferimento:
Un riferimento non deve essere null . Quando le variabili non devono essere null, il compilatore impone
regole che assicurano la dereferenziazione sicura di queste variabili senza prima verificare che non sia null:
La variabile deve essere inizializzata come valore non Null.
Alla variabile non può mai essere assegnato il valore null .
Un riferimento potrebbe essere Null . Quando le variabili possono essere Null, il compilatore applica
regole diverse per assicurarsi che sia stata verificata correttamente la presenza di un riferimento Null:
La variabile può essere dereferenziata solo quando il compilatore può garantire che il valore non sia
Null.
Queste variabili possono essere inizializzate con il valore predefinito null e possono ricevere
l'assegnazione del valore null in un altro codice.
Questa nuova funzionalità offre vantaggi significativi rispetto alla gestione delle variabili di riferimento nelle
versioni precedenti di C#, in cui la finalità di progettazione non può essere determinata dalla dichiarazione di
variabile. Il compilatore non offriva sicurezza contro le eccezioni dei riferimenti Null per i tipi riferimento:
Un riferimento può essere Null . Il compilatore non emette avvisi quando una variabile di tipo riferimento
viene inizializzata null o assegnata in un secondo momento null . Il compilatore genera avvisi quando
queste variabili vengono dereferenziate senza controlli null.
Si suppone che un riferimento sia non Null . Il compilatore non genera avvisi quando i tipi riferimento
sono dereferenziati. Il compilatore genera avvisi se una variabile è impostata su un'espressione che può
essere null.
Questi avvisi vengono generati in fase di compilazione. Il compilatore non aggiunge alcun controllo null o altri
costrutti di runtime in un contesto Nullable. In fase di esecuzione, un riferimento nullable e un riferimento non
nullable sono equivalenti.
Con l'aggiunta di tipi riferimento nullable, è possibile dichiarare in modo più chiaro la finalità. Il valore null è il
modo corretto di indicare che una variabile non fa riferimento a un valore. Non usare questa funzionalità per
rimuovere tutti i valori null dal codice. È invece consigliabile dichiarare la finalità al compilatore e agli altri
sviluppatori che leggono il codice. Dichiarando la finalità, il compilatore informa l'utente quando scrive codice
non coerente con tale finalità.
Viene inserita una nota in un tipo riferimento nullable usando la stessa sintassi dei tipi valore nullable: viene
aggiunto ? al tipo della variabile. La dichiarazione della variabile seguente, ad esempio, rappresenta una
variabile di stringa nullable, name :

string? name;

Qualsiasi variabile in cui ? non è aggiunto al nome del tipo è un tipo di riferimento che non ammette i
valori null . Che include tutte le variabili di tipo riferimento nel codice esistente quando è stata abilitata questa
funzionalità.
Il compilatore usa l'analisi statica per determinare se un riferimento nullable è notoriamente non null. Il
compilatore genera un avviso se si dereferenzia un riferimento nullable quando potrebbe essere null. È possibile
eseguire l'override di questo comportamento usando l' operatore che perdona i valori null ! dopo un nome di
variabile. Se ad esempio si è certi che la variabile name non sia Null, ma il compilatore genera un avviso, è
possibile scrivere il codice seguente per eseguire l'override dell'analisi del compilatore:

name!.Length;

Supporto dei valori Null dei tipi


Qualsiasi tipo riferimento può avere una delle quattro tipologie di supporto dei valori Null, che descrive quando
vengono generati gli avvisi:
Non ammette valori null: non è possibile assegnare null a variabili di questo tipo. Non è necessario verificare
la presenza di valori Null nelle variabili di questo tipo prima della dereferenziazione.
Nullable: null può essere assegnato a variabili di questo tipo. Se si dereferenziano le variabili di questo tipo
senza prima verificare la presenza di valori null , viene generato un avviso.
Ignaro: lo stato del pre-C # 8,0 è ignaro. Le variabili di questo tipo possono essere dereferenziate o assegnate
senza avvisi.
Sconosciuto: sconosciuto è in genere per i parametri di tipo in cui i vincoli non indicano al compilatore che il
tipo deve essere Nullable o non ammette valori null.
Il supporto dei valori Null di un tipo in una dichiarazione di variabile viene controllato dal contesto nullable in
cui viene dichiarata la variabile.

Contesti nullable
I contesti nullable consentono il controllo con granularità fine di come il compilatore interpreta le variabili dei
tipi riferimento. Il contesto di annotazione Nullable di una determinata riga di codice sorgente è abilitato o
disabilitato. È possibile considerare il compilatore pre-C # 8,0 come compilare tutto il codice in un contesto
Nullable disabilitato: qualsiasi tipo di riferimento può essere null. Il contesto degli avvisi Nullable può anche
essere abilitato o disabilitato. Il contesto degli avvisi nullable specifica gli avvisi generati dal compilatore usando
l'analisi del flusso.
Il contesto di annotazione nullable e il contesto di avviso nullable possono essere impostati per un progetto
utilizzando l' Nullable elemento nel file con estensione csproj . Questo elemento configura come il compilatore
interpreta il supporto dei valori Null dei tipi e quali avvisi vengono generati. Le impostazioni valide sono:
enable : Il contesto di annotazione Nullable è abilitato . Il contesto dell'avviso nullable è enabled .
Le variabili di un tipo riferimento, ad esempio string , sono non nullable. Tutti gli avvisi relativi al
supporto dei valori Null sono abilitati.
warnings : Il contesto di annotazione Nullable è disabilitato . Il contesto dell'avviso nullable è enabled .
Le variabili di un tipo riferimento sono indipendenti dai valori. Tutti gli avvisi relativi al supporto dei
valori Null sono abilitati.
annotations : Il contesto di annotazione Nullable è abilitato . Il contesto dell'avviso nullable è disabled .
Le variabili di un tipo di riferimento, ad esempio una stringa, non ammettono valori null. Tutti gli avvisi
relativi al supporto dei valori Null sono disabilitati.
disable : Il contesto di annotazione Nullable è disabilitato . Il contesto dell'avviso nullable è disabled .
Le variabili di un tipo riferimento sono indipendenti dai valori, come nelle versioni precedenti di C#.
Tutti gli avvisi relativi al supporto dei valori Null sono disabilitati.
Esempio :
<Nullable>enable</Nullable>

È anche possibile usare le direttive per impostare questi stessi contesti ovunque nel progetto:
#nullable enable : Imposta il contesto di annotazione nullable e il contesto di avviso Nullable su Enabled .
#nullable disable : Imposta il contesto di annotazione nullable e il contesto di avviso Nullable su disabled .
#nullable restore : Ripristina il contesto di annotazione nullable e il contesto di avviso Nullable nelle
impostazioni del progetto.
#nullable disable warnings : Impostare il contesto di avviso Nullable su disabled .
#nullable enable warnings : Impostare il contesto di avviso Nullable su Enabled .
#nullable restore warnings : Ripristina il contesto di avviso Nullable nelle impostazioni del progetto.
#nullable disable annotations : Impostare il contesto di annotazione Nullable su disabled .
#nullable enable annotations : Impostare il contesto di annotazione Nullable su Enabled .
#nullable restore annotations : Ripristina il contesto di avviso delle annotazioni nelle impostazioni del
progetto.

IMPORTANT
Il contesto Nullable globale non è applicabile per i file di codice generati. In entrambe le strategie, il contesto Nullable è
disabilitato per qualsiasi file di origine contrassegnato come generato. Ciò significa che qualsiasi API nei file generati non
viene annotata. Sono disponibili quattro modi per contrassegnare un file come generato:
1. In. EditorConfig specificare generated_code = true in una sezione che si applica a tale file.
2. Inserire <auto-generated> o <auto-generated/> in un commento all'inizio del file. Può trovarsi in qualsiasi riga del
commento, ma il blocco di commento deve essere il primo elemento del file.
3. Avviare il nome file con TemporaryGeneratedFile_
4. Terminare il nome del file con estensione designer.cs, generated.cs, g.cs o g.i.cs.
I generatori possono acconsentire esplicitamente usando la direttiva per il #nullable preprocessore.

Per impostazione predefinita, i contesti di annotazione e avviso nullable sono disabilitati , inclusi i nuovi
progetti. Ciò significa che il codice esistente viene compilato senza modifiche e senza generare nuovi avvisi.
Queste opzioni forniscono due strategie distinte per l'aggiornamento di una codebase esistente per l' utilizzo di
tipi di riferimento Nullable.

Contesto dell'annotazione nullable


Il compilatore usa le regole seguenti in un contesto di annotazione nullable disabilitato:
Non è possibile dichiarare riferimenti nullable in un contesto disabilitato.
A tutte le variabili di riferimento può essere assegnato un valore null.
Non vengono generati avvisi quando una variabile di un tipo riferimento viene dereferenziata.
L'operatore null-forgiving non può essere usato in un contesto disabilitato.
Il comportamento è lo stesso delle versioni precedenti di C#.
Il compilatore usa le regole seguenti in un contesto di annotazione nullable abilitato:
Qualsiasi variabile di un tipo riferimento è un riferimento non nullable .
Qualsiasi riferimento non nullable può essere dereferenziato in modo sicuro.
Qualsiasi tipo riferimento nullable (indicato da ? dopo il tipo nella dichiarazione della variabile) può essere
null. L'analisi statica determina se il valore è noto come non null quando viene dereferenziato. In caso
contrario, il compilatore genera un avviso.
È possibile usare l'operatore null-forgiving per dichiarare che un riferimento nullable non è Null.
In un contesto di annotazione nullable abilitato il carattere ? aggiunto al tipo riferimento dichiara un tipo
riferimento nullable . L' operatore che perdona i valori null ! può essere aggiunto a un'espressione per
dichiarare che l'espressione non è null.

Contesto dell'avviso nullable


Il contesto dell'avviso nullable è diverso dal contesto dell'annotazione nullable. Gli avvisi possono essere abilitati
anche quando le nuove annotazioni sono disabilitate. Il compilatore usa l'analisi statica del flusso per
determinare lo stato null di qualsiasi riferimento. Lo stato null è not null o maybe null quando il contesto
dell'avviso nullable non è disabled . Se si dereferenzia un riferimento quando il compilatore ha determinato che
è maybe null , il compilatore genera un avviso. Lo stato di un riferimento è maybe null a meno che il
compilatore non riesca a determinare una di due condizioni:
1. Alla variabile è stato assegnato definitivamente un valore non null.
2. La variabile o l'espressione è stata confrontata con null prima di dereferenziarla.
Il compilatore genera avvisi quando si dereferenzia una variabile o un'espressione che è forse null in un
contesto di avviso Nullable. Inoltre, il compilatore genera avvisi quando a una variabile di tipo riferimento non
null viene assegnata un'espressione o una variabile null in un contesto di annotazione Nullable abilitato.

Attributi che descrivono le API


Si aggiungono attributi alle API che forniscono al compilatore ulteriori informazioni su quando gli argomenti o i
valori restituiti possono o non possono essere null. Per altre informazioni su questi attributi, vedere l'articolo di
riferimento per il linguaggio che copre gli attributi Nullable. Questi attributi vengono aggiunti alle librerie .NET
nelle versioni correnti e future. Le API usate più di frequente vengono aggiornate per prime.

Trappole note
Le matrici e gli struct che contengono tipi di riferimento sono trappole note nella funzionalità tipi di riferimento
Nullable.
Struct
Uno struct che contiene tipi di riferimento che non ammettono valori null consente di assegnarlo default senza
alcun avviso. Prendere in considerazione gli esempi seguenti:
using System;

#nullable enable

public struct Student


{
public string FirstName;
public string? MiddleName;
public string LastName;
}

public static class Program


{
public static void PrintStudent(Student student)
{
Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
Console.WriteLine($"Middle name: {student.MiddleName.ToUpper()}");
Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
}

public static void Main() => PrintStudent(default);


}

Nell'esempio precedente non è presente alcun avviso in PrintStudent(default) mentre i tipi di riferimento non
Nullable FirstName e LastName sono null.
Un altro caso più comune è quando si gestiscono struct generici. Prendere in considerazione gli esempi
seguenti:

#nullable enable

public struct Foo<T>


{
public T Bar { get; set; }
}

public static class Program


{
public static void Main()
{
string s = default(Foo<string>).Bar;
}
}

Nell'esempio precedente, la proprietà è in fase Bar di null esecuzione e viene assegnata a una stringa non
nullable senza avvisi.
Matrici
Le matrici rappresentano anche un punto di intrappolamento noto nei tipi di riferimento Nullable. Si consideri
l'esempio seguente che non genera avvisi:
using System;

#nullable enable

public static class Program


{
public static void Main()
{
string[] values = new string[10];
string s = values[0];
Console.WriteLine(s.ToUpper());
}
}

Nell'esempio precedente, la dichiarazione della matrice contiene stringhe che non ammettono valori null,
mentre i relativi elementi vengono inizializzati su null. A questo punto, alla variabile s viene assegnato un
valore null (il primo elemento della matrice). Infine, la variabile s viene dereferenziata causando un'eccezione
in fase di esecuzione.

Vedi anche
Bozza Specifica tipi di riferimento Nullable
Introduzione all'esercitazione sui riferimenti nullable
Eseguire la migrazione di una base di codice esistente a riferimenti nullable
-Nullable (opzione del compilatore C#)
Aggiornare le librerie per usare i tipi di riferimento
nullable e comunicare le regole Nullable ai
chiamanti
28/01/2021 • 21 minutes to read • Edit Online

L'aggiunta di tipi di riferimento Nullable significa che è possibile dichiarare se è consentito o meno un null
valore per ogni variabile. Inoltre, è possibile applicare diversi attributi: AllowNull , DisallowNull , MaybeNull ,
NotNull , NotNullWhen , MaybeNullWhen e NotNullIfNotNull per descrivere completamente gli Stati null di
argomenti e valori restituiti. Che offre un'esperienza ottimale durante la scrittura del codice. Si ottengono avvisi
se una variabile che non ammette i valori null può essere impostata su null . Si ottengono avvisi se una
variabile nullable non è controllata da null prima di dereferenziarla. L'aggiornamento delle librerie può
richiedere tempo, ma i profitti valgono. Maggiori sono le informazioni fornite al compilatore quando un null
valore è consentito o proibito, gli avvisi migliori che gli utenti dell'API otterranno. Iniziamo con un esempio
familiare. Si supponga che la libreria disponga dell'API seguente per recuperare una stringa di risorsa:

bool TryGetMessage(string key, out string message)

L'esempio precedente segue il Try* modello familiare in .NET. Per questa API sono disponibili due argomenti di
riferimento: key e message . Questa API presenta le regole seguenti relative al valore null di questi argomenti:
I chiamanti non devono passare null come argomento per key .
I chiamanti possono passare una variabile il cui valore è null come argomento per message .
Se il TryGetMessage metodo restituisce true , il valore di message non è null. Se il valore restituito è false,
il valore di message (e il relativo stato null) è null.
La regola per key può essere interamente espressa dal tipo di variabile: key deve essere un tipo di riferimento
non nullable. Il message parametro è più complesso. Consente null come argomento, ma garantisce che, in
seguito all'esito positivo, l' out argomento non sia null. Per questi scenari, è necessario un vocabolario più
completo per descrivere le aspettative.
Per l'aggiornamento della libreria per i riferimenti Nullable è necessario più di un'irrigazione ? su alcune
variabili e nomi di tipo. L'esempio precedente mostra che è necessario esaminare le API e prendere in
considerazione le aspettative per ogni argomento di input. Prendere in considerazione le garanzie per il valore
restituito e out ref gli eventuali argomenti o sul valore restituito dal metodo. Comunicare quindi tali regole al
compilatore e il compilatore fornirà avvisi quando i chiamanti non rispettano tali regole.
Questo lavoro richiede tempo. Iniziamo con le strategie per rendere la tua libreria o l'applicazione compatibile
con i valori null, con il bilanciamento di altri requisiti. Si vedrà come bilanciare lo sviluppo in corso abilitando i
tipi di riferimento Nullable. Verranno illustrate le esigenze per le definizioni di tipo generico. Si apprenderà come
applicare gli attributi per descrivere le condizioni preliminari e successive sulle singole API.

Scegliere una strategia per i tipi di riferimento Nullable


La prima scelta è se i tipi di riferimento nullable devono essere attivati o disattivati per impostazione predefinita.
Sono disponibili due strategie:
Abilitare i tipi di riferimento Nullable per l'intero progetto e disabilitarlo nel codice non pronto.
Abilitare solo i tipi di riferimento Nullable per il codice che è stato annotato per i tipi di riferimento Nullable.
La prima strategia funziona meglio quando si aggiungono altre funzionalità alla libreria durante
l'aggiornamento per i tipi di riferimento Nullable. Tutti i nuovi sviluppi supportano i valori null. Quando si
aggiorna il codice esistente, si abilitano i tipi di riferimento nullable in tali classi.
Seguendo questa prima strategia, seguire questa procedura:
1. Abilitare i tipi di riferimento Nullable per l'intero progetto aggiungendo l' <Nullable>enable</Nullable>
elemento ai file csproj .
2. Aggiungere il #nullable disable pragma a ogni file di origine nel progetto.
3. Quando si lavora su ogni file, rimuovere il pragma e risolvere eventuali avvisi.
Questa prima strategia ha più lavoro iniziale per aggiungere il pragma a ogni file. Il vantaggio è che ogni nuovo
file di codice aggiunto al progetto sarà abilitato per i valori null. Eventuali nuovi lavori saranno in grado di
riconoscere i valori null. è necessario aggiornare solo il codice esistente.
La seconda strategia funziona meglio se la libreria è stabile e l'obiettivo principale dello sviluppo è l'adozione di
tipi di riferimento Nullable. Si attivano i tipi di riferimento Nullable durante l'annotazione delle API. Al termine, si
abilitano i tipi di riferimento Nullable per l'intero progetto.
Seguendo questa seconda strategia si eseguono i passaggi seguenti:
1. Aggiungere il #nullable enable pragma al file che si desidera rendere compatibile con Nullable.
2. Risolvere tutti gli avvisi.
3. Continuare questi primi due passaggi fino a quando non è stata resa disponibile l'intera libreria Nullable.
4. Abilitare i tipi Nullable per l'intero progetto aggiungendo l' <Nullable>enable</Nullable> elemento ai file
csproj .
5. Rimuovere i #nullable enable pragma, perché non sono più necessari.
Questa seconda strategia ha meno lavoro in primo piano. Il compromesso consiste nel fatto che la prima attività
quando si crea un nuovo file consiste nell'aggiungere il pragma e renderlo compatibile con i valori null. Se gli
sviluppatori del team dimenticano, il nuovo codice si trova ora nel backlog di lavoro per rendere il codice
Nullable compatibile.
La scelta di queste strategie dipende dalla quantità di attività di sviluppo attive nel progetto. Il progetto è più
maturo e stabile, migliore sarà la seconda strategia. Maggiore è la maggior parte delle funzionalità sviluppate,
migliore sarà la prima strategia.

IMPORTANT
Il contesto Nullable globale non è applicabile per i file di codice generati. In entrambe le strategie, il contesto Nullable è
disabilitato per qualsiasi file di origine contrassegnato come generato. Ciò significa che qualsiasi API nei file generati non
viene annotata. Sono disponibili quattro modi per contrassegnare un file come generato:
1. In. EditorConfig specificare generated_code = true in una sezione che si applica a tale file.
2. Inserire <auto-generated> o <auto-generated/> in un commento all'inizio del file. Può trovarsi in qualsiasi riga del
commento, ma il blocco di commento deve essere il primo elemento del file.
3. Avviare il nome file con TemporaryGeneratedFile_
4. Terminare il nome del file con estensione designer.cs, generated.cs, g.cs o g.i.cs.
I generatori possono acconsentire esplicitamente usando la direttiva per il #nullable preprocessore.

Gli avvisi Nullable introducono modifiche di rilievo?


Prima di abilitare i tipi di riferimento Nullable, le variabili sono considerate Nullable ignare. Quando si abilitano i
tipi di riferimento Nullable, tutte queste variabili non ammettono valori null. Il compilatore emetterà avvisi se tali
variabili non vengono inizializzate su valori non null.
Un'altra fonte di avvisi probabile è la restituzione di valori quando il valore non è stato inizializzato.
Il primo passaggio per indirizzare gli avvisi del compilatore consiste nell'usare le ? annotazioni sui tipi
Parameter e Return per indicare quando gli argomenti o i valori restituiti possono essere null. Quando le
variabili di riferimento non devono essere null, la dichiarazione originale è corretta. Quando si esegue questa
attività, l'obiettivo non è solo correggere gli avvisi. L'obiettivo più importante è fare in modo che il compilatore
conosca le finalità dei potenziali valori null. Quando si esaminano gli avvisi, si raggiunge la decisione principale
successiva per la raccolta. Si desidera modificare le firme dell'API per comunicare in modo più chiaro lo scopo
della progettazione? Una firma API migliore per il TryGetMessage Metodo esaminato in precedenza potrebbe
essere:

string? TryGetMessage(string key);

Il valore restituito indica l'esito positivo o negativo e contiene il valore se il valore è stato trovato. In molti casi, la
modifica delle firme API può migliorare il modo in cui comunicano i valori null.
Tuttavia, per le librerie pubbliche o le librerie con basi utente di grandi dimensioni, è preferibile non introdurre
modifiche alla firma dell'API. Per questi casi e altri modelli comuni, è possibile applicare gli attributi per definire
in modo più chiaro quando un argomento o un valore restituito può essere null . Indipendentemente dalla
possibilità di modificare la superficie dell'API, probabilmente si noterà che le annotazioni di tipo non sono
sufficienti per descrivere null i valori per gli argomenti o i valori restituiti. In questi casi, è possibile applicare
gli attributi per descrivere più chiaramente un'API.

Attributi estensione delle annotazioni di tipo


Sono stati aggiunti diversi attributi per esprimere informazioni aggiuntive sullo stato null delle variabili. Tutto il
codice scritto prima di C# 8 ha introdotto tipi di riferimento Nullable è un valore null ignaro. Ciò significa che
qualsiasi variabile di tipo riferimento può essere null, ma non sono necessari controlli null. Quando il codice
ammette i valori null, tali regole cambiano. I tipi di riferimento non devono mai essere il null valore e i tipi di
riferimento nullable devono essere controllati null prima di essere dereferenziati.
Le regole per le API sono probabilmente più complesse, come si è visto con lo TryGetValue scenario API. Molte
API hanno regole più complesse quando le variabili possono o non possono essere null . In questi casi, si
useranno gli attributi per esprimere tali regole. Gli attributi che descrivono la semantica dell'API sono disponibili
nell'articolo sugli attributi che influiscano sull'analisi dei valori null.

Definizioni generiche e supporto dei valori null


La comunicazione corretta dello stato null dei tipi generici e dei metodi generici richiede particolare attenzione.
La cura aggiuntiva deriva dal fatto che un tipo di valore nullable e un tipo di riferimento nullable sono
fondamentalmente diversi. Un oggetto int? è un sinonimo di Nullable<int> , mentre string? è string con
un attributo aggiunto dal compilatore. Il risultato è che il compilatore non può generare codice corretto per T?
senza sapere se T è class o struct .
Questo fatto non significa che non è possibile usare un tipo Nullable (tipo di valore o tipo di riferimento) come
argomento di tipo per un tipo generico chiuso. List<string?> E List<int?> sono istanze valide di List<T> .
Cosa significa che non è possibile usare T? in una dichiarazione di classe o metodo generica senza vincoli. Ad
esempio, Enumerable.FirstOrDefault<TSource>(IEnumerable<TSource>) non verrà modificato in return T? .
Per ovviare a questa limitazione, è possibile aggiungere il struct class vincolo o. Con uno di questi vincoli, il
compilatore sa come generare codice sia per T che per T? .
Potrebbe essere necessario limitare i tipi utilizzati per un argomento di tipo generico in modo che siano tipi non
nullable. A tale scopo, è possibile aggiungere il notnull vincolo su tale argomento di tipo. Quando viene
applicato il vincolo, l'argomento di tipo non deve essere un tipo Nullable.

Proprietà con inizializzazione tardiva, oggetti Trasferimento dati e


supporto dei valori null
L'indicazione del supporto di valori Null delle proprietà che sono inizializzate in ritardo, ovvero impostate dopo
la costruzione, potrebbe richiedere particolare attenzione per garantire che la classe continui a esprimere
correttamente l'intento di progettazione originale.
I tipi che contengono proprietà inizializzate in ritardo, ad esempio oggetti Trasferimento dati (dto), vengono
spesso creati da una libreria esterna, ad esempio un database ORM (Object Relational Mapper), un
deserializzatore o un altro componente che popola automaticamente le proprietà da un'altra origine.
Si consideri la seguente classe DTO, prima di abilitare i tipi di riferimento Nullable, che rappresenta uno
studente:

class Student
{
[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }

public string VehicleRegistration { get; set; }


}

Lo scopo della progettazione, indicato in questo caso dall' Required attributo, suggerisce che in questo sistema
le FirstName proprietà e LastName sono obbligatorie e pertanto non null.
La VehicleRegistration proprietà non è obbligatoria , pertanto può essere null.
Quando si abilitano i tipi di riferimento Nullable, si vuole indicare quali proprietà del DTO possono essere
nullable, coerenti con la finalità originale:

class Student
{
[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }

public string? VehicleRegistration { get; set; }


}

Per questo DTO, l'unica proprietà che può essere null è VehicleRegistration .
Tuttavia, il compilatore genera CS8618 avvisi sia per che per FirstName LastName , per indicare che le proprietà
che non ammettono i valori null non sono inizializzate.
Sono disponibili tre opzioni che consentono di risolvere gli avvisi del compilatore in modo da mantenere la
finalità originale. Una di queste opzioni è valida; è consigliabile scegliere quella più adatta alle proprie esigenze
di progettazione e stile di codifica.
Inizializzazione nel costruttore
Il modo ideale per risolvere gli avvisi non inizializzati consiste nell'inizializzare le proprietà nel costruttore:

class Student
{
public Student(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }

public string? VehicleRegistration { get; set; }


}

Questo approccio funziona solo se la libreria utilizzata per creare un'istanza della classe supporta il passaggio di
parametri nel costruttore.
Una libreria può supportare il passaggio di alcune proprietà nel costruttore, ma non tutte. Ad esempio, EF Core
supporta il binding del costruttore per le proprietà di colonna normali, ma non le proprietà di navigazione.
Controllare la documentazione sulla libreria che crea un'istanza della classe, per comprendere in che misura è
supportata l'associazione del costruttore.
Proprietà con campo di supporto Nullable
Se l'associazione del costruttore non funziona per l'utente, un modo per risolvere questo problema consiste nel
disporre di una proprietà che non ammette i valori null con un campo sottostante Nullable:

private string? _firstName;

[Required]
public string FirstName
{
set => _firstName = value;
get => _firstName
?? throw new InvalidOperationException("Uninitialized " + nameof(FirstName))
}

In questo scenario, se FirstName si accede alla proprietà prima che sia stata inizializzata, il codice genera
un'eccezione InvalidOperationException , perché il contratto API è stato usato in modo errato.
Si tenga presente che alcune librerie possono avere considerazioni speciali quando si usano i campi sottoposti a
backup. Ad esempio, potrebbe essere necessario configurare EF Core per l'uso corretto dei campi di backup .
Inizializza la proprietà su null
Come alternativa Terser all'uso di un campo di supporto nullable o se la libreria che crea un'istanza della classe
non è compatibile con questo approccio, è possibile inizializzare la proprietà null direttamente su, con l'aiuto
dell'operatore che perdona i valori null ( ! ):
[Required]
public string FirstName { get; set; } = null!;

[Required]
public string LastName { get; set; } = null!;

public string? VehicleRegistration { get; set; }

Non si osserverà mai un valore null effettivo in fase di esecuzione, ad eccezione di un bug di programmazione,
accedendo alla proprietà prima che sia stata inizializzata correttamente.

Vedi anche
Eseguire la migrazione di una base di codice esistente a riferimenti nullable
Utilizzo dei tipi di riferimento nullable in EF Core
Spazi dei nomi (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Gli spazi dei nomi vengono usati frequentemente nella programmazione C# in due modi. In primo luogo, .NET
usa gli spazi dei nomi per organizzare le numerose classi, come indicato di seguito:

System.Console.WriteLine("Hello World!");

System è uno spazio dei nomi e Console è una classe in quello spazio dei nomi. Si può usare la parola chiave
using in modo tale che il nome completo non sia necessario, come nell'esempio seguente:

using System;

Console.WriteLine("Hello World!");

Per altre informazioni, vedere la direttiva using.


Secondo, dichiarando i propri spazi dei nomi è possibile controllare l'ambito dei nomi di classi e metodi nei
progetti di programmazione più grandi. Usare la parola chiave namespace per dichiarare uno spazio dei nomi,
come nell'esempio seguente:

namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}

Il nome dello spazio dei nomi deve essere un nome di identificatore C# valido.

Panoramica degli spazi dei nomi


Gli spazi dei nomi hanno le proprietà riportate di seguito:
Consentono di organizzare progetti di codice di grandi dimensioni.
Vengono delimitati usando l'operatore . .
La direttiva using elimina la necessità di specificare il nome dello spazio dei nomi per ogni classe.
Lo spazio dei nomi global è lo spazio dei nomi "radice": global::System farà sempre riferimento allo spazio
dei nomi System di .NET.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Spazi dei nomi della specifica del linguaggio C#.
Vedere anche
Guida per programmatori C#
Uso degli spazi dei nomi
Come usare lo spazio dei nomi My
Nomi di identificatore
Direttiva using
Operatore::
Tipi, variabili e valori
28/01/2021 • 12 minutes to read • Edit Online

C# è un linguaggio fortemente tipizzato. Ogni variabile e costante ha un tipo, così come ogni espressione che
restituisce un valore. Ogni firma del metodo specifica un tipo per ogni parametro di input e per il valore
restituito. La libreria di classi .NET definisce un set di tipi numerici predefiniti, nonché tipi più complessi che
rappresentano un'ampia gamma di costrutti logici, ad esempio il file system, le connessioni di rete, le raccolte e
matrici di oggetti e le date. Un tipico programma C# usa tipi dalla libreria di classi e tipi definiti dall'utente che
modellano i concetti specifici del dominio relativo al problema del programma.
Le informazioni archiviate in un tipo possono includere quanto segue:
Lo spazio di archiviazione richiesto da una variabile del tipo.
I valori minimi e massimi che può rappresentare.
I membri (metodi, campi, eventi e così via) in esso contenuti.
Il tipo di base da cui eredita.
Interfaccia o interfacce implementate.
Il percorso in cui viene allocata la memoria per le variabili in fase di esecuzione.
I tipi di operazioni consentite.
Il compilatore usa le informazioni sul tipo per assicurarsi che tutte le operazioni eseguite nel codice siano
indipendenti dai tipi. Se ad esempio si dichiara una variabile di tipo int, il compilatore consente di usare la
variabile anche in operazioni di addizione e sottrazione. Se si prova a eseguire le stesse operazioni su una
variabile di tipo bool, il compilatore genera un errore, come illustrato nell'esempio seguente:

int a = 5;
int b = a + 2; //OK

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;

NOTE
Gli sviluppatori C e C++ devono tenere presente che, in C#, bool non è convertibile in int.

Il compilatore incorpora le informazioni sul tipo nel file eseguibile come metadati. Il Common Language
Runtime (CLR) usa i metadati in fase di esecuzione per garantire una maggiore indipendenza dai tipi quando
alloca e recupera la memoria.

Specifica dei tipi nelle dichiarazioni di variabile


Quando si dichiara una variabile o una costante in un programma, è necessario specificarne il tipo oppure usare
la parola chiave var per consentire al compilatore di dedurre il tipo. L'esempio seguente illustra alcune
dichiarazioni di variabili che usano sia tipi numerici incorporati sia tipi complessi definiti dall'utente:
// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):


char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;

I tipi di parametri e valori restituiti del metodo sono specificati nella firma del metodo. La firma seguente illustra
un metodo che richiede un int come argomento di input e restituisce una stringa:

public string GetName(int ID)


{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Una variabile dichiarata non può essere dichiarata una seconda volta con un tipo nuovo e non è possibile
assegnare a tale variabile un valore non compatibile con il relativo tipo dichiarato. Ad esempio, non è possibile
dichiarare un valore int e quindi assegnargli un valore booleano true . I valori possono tuttavia essere
convertiti in altri tipi, ad esempio quando vengono assegnati a nuove variabili o passati come argomenti di
metodo. Una conversione del tipo che non causa la perdita di dati viene eseguita automaticamente dal
compilatore, mentre una conversione che può causare la perdita di dati richiede un cast nel codice sorgente.
Per altre informazioni, vedere cast e conversioni di tipi.

Tipi incorporati
Il linguaggio C# offre un set standard di tipi numerici incorporati per rappresentare numeri interi, valori a
virgola mobile, espressioni booleane, caratteri di testo, valori decimali e altri tipi di dati. Sono anche disponibili
tipi string e object predefiniti. Questi possono essere usati in qualsiasi programma C#. Per l'elenco completo
dei tipi incorporati, vedere tipi incorporati.

Tipi personalizzati
Usare i costrutti struct, class, interface e enum per creare tipi personalizzati. La libreria di classi .NET stessa è una
raccolta di tipi personalizzati offerti da Microsoft che è possibile usare nelle proprie applicazioni. Per
impostazione predefinita, i tipi più comunemente usati nella libreria di classi sono disponibili in qualsiasi
programma C#, mentre altri diventano disponibili solo quando si aggiunge in modo esplicito un riferimento di
progetto all'assembly in cui sono definiti. Nel momento in cui il compilatore ha un riferimento all'assembly, è
possibile dichiarare variabili (e costanti) dei tipi dichiarati nell'assembly in codice sorgente.

Tipi generici
Un tipo può essere dichiarato con uno o più parametri di tipo che agiscono da segnaposto per il tipo effettivo (
tipo concreto ) che il codice client specifica quando si crea un'istanza del tipo. Questi tipi sono definiti tipi
generici. Ad esempio, List<T> ha un parametro di tipo a cui per convenzione viene assegnato il nome T. Quando
si crea un'istanza del tipo, si specifica il tipo degli oggetti che l'elenco conterrà, ad esempio, String:
List<string> strings = new List<string>();

L'uso del parametro di tipo consente di riutilizzare la stessa classe per contenere qualsiasi tipo di elemento
senza dover convertire ogni elemento in object. Le classi di raccolte generiche sono denominate raccolte
fortemente tipizzate perché il compilatore conosce il tipo specifico degli elementi della raccolta e può generare
un errore in fase di compilazione se, ad esempio, si tenta di aggiungere un numero intero all' strings oggetto
nell'esempio precedente. Per altre informazioni, vedere Generics.

Tipi impliciti, tipi anonimi e tipi di tupla


Come indicato in precedenza, è possibile tipizzare una variabile locale (ma non membri di classe) in modo
implicito usando la parola chiave var. Alla variabile viene comunque assegnato un tipo in fase di compilazione,
specificato dal compilatore. Per altre informazioni, vedere variabili locali tipizzate in modo implicito.
In alcuni casi non è consigliabile creare un tipo denominato per set semplici di valori correlati che non si intende
archiviare o passare fuori dai limiti del metodo. A questo scopo è possibile creare tipi anonimi. Per ulteriori
informazioni, vedere tipi anonimi.
È prassi comune voler restituire più valori da un metodo. È possibile creare tipi di tupla che restituiscono più
valori in una singola chiamata al metodo. Per altre informazioni, vedere tipi di tupla.

Sistema dei tipi comuni


È importante tenere presente due aspetti fondamentali del sistema dei tipi in .NET:
Supporta il principio di ereditarietà. I tipi possono derivare da altri tipi, denominati tipi di base. Il tipo
derivato eredita (con alcune limitazioni) metodi, proprietà e altri membri del tipo di base, che a sua volta
può derivare da un altro tipo. In questo caso, il tipo derivato eredita i membri di entrambi i tipi di base
nella gerarchia di ereditarietà. Tutti i tipi, inclusi i tipi numerici predefiniti, ad esempio Int32 (parola chiave
C#: int ), derivano in definitiva da un unico tipo di base, ovvero Object (parola chiave C#: object ).
Questa gerarchia di tipi unificata è chiamata Common Type System (CTS). Per altre informazioni
sull'ereditarietà in C#, vedere Ereditarietà.
Nel CTS ogni tipo è definito come tipo valore o tipo riferimento. In queste due categorie sono inclusi
anche tutti i tipi personalizzati nella libreria di classi .NET e i tipi definiti dall'utente. I tipi definiti tramite la
struct enum parola chiave o sono tipi di valore. Per ulteriori informazioni sui tipi di valore, vedere tipi di
valore. I tipi definiti tramite la parola chiave class sono tipi riferimento. Per altre informazioni sui tipi di
riferimento, vedere Classi. I tipi di riferimento e i tipi di valore hanno regole diverse e un comportamento
diverso in fase di esecuzione.

Vedere anche
Tipi di struttura
Tipi di enumerazione
Classi
Classi (Guida per programmatori C#)
28/01/2021 • 9 minutes to read • Edit Online

Tipi riferimento
Un tipo definito come classe è un tipo di riferimento. In fase di esecuzione, quando si dichiara una variabile di un
tipo riferimento, la variabile contiene il valore Null fino a quando non si crea in modo esplicito un'istanza della
classe usando l'operatore new o fino a quando non le viene assegnato un oggetto di un tipo compatibile creato
altrove, come illustrato nell'esempio seguente:

//Declaring an object of type MyClass.


MyClass mc = new MyClass();

//Declaring another object of the same type, assigning it the value of the first object.
MyClass mc2 = mc;

Quando viene creato l'oggetto, una quantità di memoria sufficiente viene allocata nell'heap gestito per l'oggetto
specifico e la variabile mantiene solo un riferimento al percorso dell'oggetto. I tipi nell'heap gestito richiedono
un overhead quando vengono allocati e recuperati dalla funzionalità di gestione automatica della memoria di
CLR, nota come Garbage Collection. La Garbage Collection, tuttavia, è anche altamente ottimizzata e, nella
maggior parte degli scenari, non genera un problema di prestazioni. Per altre informazioni sulla Garbage
Collection, vedere Gestione automatica della memoria e Garbage Collection.

Dichiarazione di classi
Le classi vengono dichiarate usando la parola chiave class seguita da un identificatore univoco, come illustrato
nell'esempio seguente:

//[access modifier] - [class] - [identifier]


public class Customer
{
// Fields, properties, methods and events go here...
}

La parola chiave class è preceduta dal livello di accesso. Poiché in questo caso viene usata la parola chiave
public, chiunque può creare istanze di questa classe. Il nome della classe segue la parola chiave class . Il nome
della classe deve essere un nome di identificatore C# valido. Il resto della definizione è il corpo della classe, in cui
vengono definiti il comportamento e i dati. I campi, le proprietà, i metodi e gli eventi in una classe vengono
collettivamente definiti membri della classe.

Creazione di oggetti
Anche se vengono talvolta usati in modo intercambiabile, una classe e un oggetto sono elementi diversi. Una
classe definisce un tipo di oggetto, ma non è un oggetto. Un oggetto è un'entità concreta ed è basato su una
classe. Talvolta si fa riferimento all'oggetto come istanza di una classe.
Gli oggetti possono essere creati tramite la parola chiave new seguita dal nome della classe su cui si baserà
l'oggetto, nel modo seguente:
Customer object1 = new Customer();

Quando viene creata un'istanza di una classe, viene passato al programmatore un riferimento all'oggetto.
Nell'esempio precedente, object1 è un riferimento a un oggetto basato su Customer . Questo riferimento indica
il nuovo oggetto, ma non contiene i dati dell'oggetto. Infatti, è possibile creare un riferimento all'oggetto senza
creare un oggetto:

Customer object2;

Non è consigliabile creare riferimenti a oggetti come questo che non fanno riferimento a un oggetto reale
perché il tentativo di accedere a un oggetto tramite tale riferimento avrà esito negativo in fase di esecuzione.
Tuttavia, è possibile fare riferimento a un oggetto in modo che faccia riferimento a un oggetto creando un nuovo
oggetto o assegnandogli un oggetto esistente, ad esempio:

Customer object3 = new Customer();


Customer object4 = object3;

Questo codice crea due riferimenti a oggetti che fanno entrambi riferimento allo stesso oggetto. Tutte le
modifiche effettuate all'oggetto tramite object3 si riflettono tuttavia nei successivi usi di object4 . Poiché gli
oggetti che si basano su classi vengono indicati tramite riferimenti, le classi sono note come tipi di riferimento.

Ereditarietà delle classi


Le classi supportano completamente l' ereditarietà , una caratteristica fondamentale nella programmazione
orientata a oggetti. Quando si crea una classe, è possibile ereditare da qualsiasi altra classe non definita come
sealede le altre classi possono ereditare dalla classe ed eseguire l'override dei metodi virtuali della classe.
Inoltre, è possibile implementare una o più interfacce.
L'ereditarietà si ottiene usando una derivazione , vale a dire che una classe viene dichiarata usando una classe di
base da cui eredita dati e comportamento. Una classe di base viene specificata tramite l'aggiunta di due punti e
il nome della classe di base dopo il nome della classe derivata, nel modo seguente:

public class Manager : Employee


{
// Employee fields, properties, methods and events are inherited
// New Manager fields, properties, methods and events go here...
}

Quando una classe dichiara una classe di base, eredita tutti i membri della classe di base, a eccezione dei
costruttori. Per altre informazioni, vedere Ereditarietà.
Diversamente da C++, una classe di C# può ereditare direttamente solo da una classe di base. Tuttavia, poiché
una classe di base può ereditare da un'altra classe, una classe può ereditare indirettamente più classi di base.
Una classe può inoltre implementare direttamente una o più interfacce. Per ulteriori informazioni, vedi
Interfacce.
Una classe può essere dichiarata come astratta. Una classe astratta contiene metodi astratti che hanno una
definizione di firma, ma senza implementazione. Non è possibile creare un'istanza di classi astratte. Le classi
astratte possono essere usate solo tramite classi derivate che implementano i metodi astratti. Al contrario, una
classe sealed non consente ad altre classi di derivare da tale classe. Per altre informazioni, vedere classi e
membri delle classi astratte e sealed.
Le definizioni di classe possono essere suddivise tra file di origine diversa. Per altre informazioni, vedere Classi e
metodi parziali.

Esempio
L'esempio seguente definisce una classe pubblica che contiene una proprietà implementata automaticamente,
un metodo e un metodo speciale denominato costruttore. Per altre informazioni, vedere gli argomenti Proprietà,
Metodi e Costruttori. Le istanze della classe vengono quindi create con la parola chiave new .

using System;

public class Person


{
// Constructor that takes no arguments:
public Person()
{
Name = "unknown";
}

// Constructor that takes one argument:


public Person(string name)
{
Name = name;
}

// Auto-implemented readonly property:


public string Name { get; }

// Method that overrides the base class (System.Object) implementation.


public override string ToString()
{
return Name;
}
}
class TestPerson
{
static void Main()
{
// Call the constructor that has no parameters.
var person1 = new Person();
Console.WriteLine(person1.Name);

// Call the constructor that has one parameter.


var person2 = new Person("Sarah Jones");
Console.WriteLine(person2.Name);
// Get the string representation of the person2 instance.
Console.WriteLine(person2);

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}
// Output:
// unknown
// Sarah Jones
// Sarah Jones

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Programmazione orientata a oggetti
Polimorfismo
Nomi di identificatore
Members
Metodi
Costruttori
Finalizzatori
Oggetti
Decostruzione di tuple e altri tipi
28/01/2021 • 16 minutes to read • Edit Online

Una tupla è un metodo semplice per recuperare più valori da una chiamata a un metodo. Tuttavia dopo aver
recuperato la tupla è necessario gestirne i singoli elementi. Se eseguita un elemento alla volta, questa
operazione può risultare molto laboriosa, come visualizzato nell'esempio seguente. Il metodo QueryCityData
restituisce una tupla con 3 elementi e ogni elemento viene assegnato a una variabile in un'operazione separata.

using System;

public class Example


{
public static void Main()
{
var result = QueryCityData("New York City");

var city = result.Item1;


var pop = result.Item2;
var size = result.Item3;

// Do something with the data.


}

private static (string, int, double) QueryCityData(string name)


{
if (name == "New York City")
return (name, 8175133, 468.48);

return ("", 0, 0);


}
}

Il recupero di più valori di campi e proprietà da un oggetto può essere altrettanto complesso: è necessario
assegnare un valore di campo o proprietà a una variabile, un membro alla volta.
A partire da C# 7.0 è possibile recuperare più elementi da una tupla o recuperare più valori di campi, proprietà e
valori calcolati da un oggetto in una singola operazione di decostruzione. Quando si decostruisce una tupla gli
elementi corrispondenti vengono assegnati a singole variabili. Quando si decostruisce un oggetto si assegnano
valori selezionati a singole variabili.

Decostruzione di una tupla


In C# è incluso il supporto per la decostruzione di tuple, che consente di decomprimere tutti gli elementi di una
tupla in un'unica operazione. La sintassi generale per la decostruzione di una tupla è simile alla sintassi per la
definizione della tupla: le variabili a cui va assegnato ogni elemento vengono racchiuse tra parentesi sul lato
sinistro di un'istruzione di assegnazione. Ad esempio l'istruzione seguente assegna gli elementi di una tupla con
4 elementi a quattro variabili distinte:

var (name, address, city, zip) = contact.GetAddressInfo();

Esistono tre modi per decostruire una tupla:


È possibile dichiarare in modo esplicito il tipo di ogni campo all'interno di parentesi. Nell'esempio
seguente viene usato questo approccio per decostruire la tupla con 3 elementi restituita dal metodo
QueryCityData .

public static void Main()


{
(string city, int population, double area) = QueryCityData("New York City");

// Do something with the data.


}

È possibile usare la parola chiave var in modo che C# deduca il tipo di ogni variabile. Posizionare la
parola chiave var all'esterno delle parentesi. Nell'esempio seguente viene usata l'inferenza del tipo
questo approccio per decostruire la tupla con 3 elementi restituita dal metodo QueryCityData .

public static void Main()


{
var (city, population, area) = QueryCityData("New York City");

// Do something with the data.


}

È anche possibile usare la parola chiave var individualmente con una o con tutte le dichiarazioni di
variabili all'interno delle parentesi.

public static void Main()


{
(string city, var population, var area) = QueryCityData("New York City");

// Do something with the data.


}

Questo approccio è eccessivamente complesso e non è consigliato.


Infine, è possibile decostruire la tupla in variabili che sono già state dichiarate.

public static void Main()


{
string city = "Raleigh";
int population = 458880;
double area = 144.8;

(city, population, area) = QueryCityData("New York City");

// Do something with the data.


}

Si noti che non è possibile usare un tipo specifico all'esterno delle parentesi, anche se ogni campo nella tupla
presenta lo stesso tipo. Questa operazione genera l'errore del compilatore CS8136 "Nel form di decostruzione
'var (...)' non è consentito un tipo specifico per 'var'".
Si noti anche che è necessario assegnare ogni elemento della tupla a una variabile. Se si omette un elemento, il
compilatore genera l'errore CS8132: "Non è possibile decostruire una tupla di 'x' elementi in 'y' variabili".
Si noti che non è possibile combinare le dichiarazioni e le assegnazioni a variabili esistenti sul lato sinistro di una
decostruzione. Questa operazione genera l'errore del compilatore CS8184: "Nella parte sinistra di una
decostruzione non è possibile combinare dichiarazioni ed espressioni" quando i membri includono variabili
esistenti e appena dichiarate.
Decostruzione degli elementi della tupla con variabili discard
Spesso quando si decostruisce una tupla si è interessati solo ai valori di alcuni elementi. A partire da C# 7.0 è
possibile avvalersi del supporto in C# delle variabili discard , variabili di sola scrittura delle quali si è scelto di
ignorare i valori. Una variabile discard è indicata da un carattere di sottolineatura ("_") in un'assegnazione. È
possibile rimuovere il numero di valori desiderato; tutti sono rappresentati dalla variabile discard singola _ .
L'esempio seguente illustra l'uso delle tuple con le variabili discard. Il metodo QueryCityDataForYears restituisce
una tupla con 6 elementi con il nome di una città, l'area della città, un anno, la popolazione della città per tale
anno, un secondo anno e la popolazione della città per tale anno. L'esempio visualizza la variazione della
popolazione tra questi due anni. Tra i dati resi disponibili dalla tupla non interessa l'area della città, mentre il
nome della città e le due date sono già noti in fase di progettazione. Di conseguenza interessano soltanto i due
valori di popolazione archiviati nella tupla, mentre gli altri valori possono essere gestiti come variabili discard.

using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");


}

private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;

if (name == "New York City")


{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}

return ("", 0, 0, 0, 0, 0);


}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149

Decostruzione dei tipi definiti dall'utente


C# non offre il supporto predefinito per la decostruzione di tipi diversi da tuple. Tuttavia l'autore di una classe,
uno struct o un'interfaccia può consentire la decostruzione di istanze del tipo implementando uno o più metodi
Deconstruct . Il metodo restituisce un valore void e ogni valore da decostruire è indicato da un parametro out
nella firma del metodo. Ad esempio il seguente metodo Deconstruct di una classe Person restituisce il nome, il
secondo nome e il cognome:
public void Deconstruct(out string fname, out string mname, out string lname)

È quindi possibile decostruire un'istanza della classe Person denominata p con un'assegnazione simile alla
seguente:

var (fName, mName, lName) = p;

L'esempio che segue implementa l'overload del metodo Deconstruct per restituire varie combinazioni di
proprietà di un oggetto Person . I singoli overload restituiscono:
Un nome e un cognome.
Il primo, il secondo e il cognome.
Un nome, un cognome, un nome di città e un nome di stato.
using System;

public class Person


{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }

public Person(string fname, string mname, string lname,


string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}

// Return the first and last name.


public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}

public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}

public void Deconstruct(out string fname, out string lname,


out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}

public class Example


{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

// Deconstruct the person object.


var (fName, lName, city, state) = p;
Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
}
}
// The example displays the following output:
// Hello John Adams of Boston, MA!

Poiché è possibile eseguire l'overload del metodo Deconstruct in modo da riflettere gruppi di dati estratti
comunemente da un oggetto, è importante definire metodi Deconstruct con firme uniche e non ambigue. Più
metodi Deconstruct che hanno lo stesso numero di parametri out o lo stesso numero e tipo di parametri out
in un ordine diverso possono provocare confusione.
Il metodo Deconstruct con overload dell'esempio seguente visualizza una possibile causa di confusione. Il
primo overload restituisce il nome, il secondo nome, il cognome e l'età di un oggetto Person in quest'ordine. Il
secondo overload restituisce solo le informazioni del nome oltre al reddito annuale, ma i dati relativi a nome,
secondo nome e cognome sono in un ordine diverso. In questo modo è più facile confondere l'ordine degli
argomenti quando si esegue la decostruzione di un'istanza di Person .

using System;

public class Person


{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public DateTime DateOfBirth { get; set; }
public Decimal AnnualIncome { get; set; }

public void Deconstruct(out string fname, out string mname, out string lname, out int age)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;

// calculate the person's age


var today = DateTime.Today;
age = today.Year - DateOfBirth.Year;
if (DateOfBirth.Date > today.AddYears(-age))
age--;
}

public void Deconstruct(out string lname, out string fname, out string mname, out decimal income)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
income = AnnualIncome;
}
}

Decostruzione di un tipo definito dall'utente con variabili discard


Come per le tuple, è possibile usare le variabili discard per ignorare elementi selezionati restituiti da un metodo
Deconstruct . Ogni variabile discard è definita da una variabile denominata _ e una singola operazione di
decostruzione può includere diverse variabili discard.
L'esempio seguente esegue la decostruzione di un oggetto Person in quattro stringhe (nome, cognome, città e
stato) ma rimuove il cognome e lo stato.

// Deconstruct the person object.


var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!

Decostruzione di un tipo definito dall'utente con un metodo di


estensione
Anche un utente che non ha creato una classe, uno struct o un'interfaccia può eseguire la decostruzione di
oggetti di questo tipo implementando uno o più Deconstruct metodi di estensione per restituire i valori che
risultano di interesse.
L'esempio riportato di seguito illustra due metodi di estensione Deconstruct per la classe
System.Reflection.PropertyInfo. Il primo metodo restituisce un set di valori che indicano le caratteristiche della
proprietà: il tipo, se è statica o di istanza, se è di sola lettura e se è indicizzata. Il secondo indica l'accessibilità
della proprietà. Dato che l'accessibilità delle funzioni di accesso get e set può essere diversa, i valori booleani
indicano se la proprietà ha funzioni di accesso get e set separate e in questo caso se tali funzioni presentano la
stessa accessibilità. Se è presente solo una funzione di accesso o se le funzioni di accesso get e set hanno la
stessa accessibilità, la variabile access indica l'accessibilità della proprietà nel suo complesso. In caso contrario
l'accessibilità delle funzioni di accesso get e set è indicata dalle variabili getAccess e setAccess .

using System;
using System.Collections.Generic;
using System.Reflection;

public static class ReflectionExtensions


{
public static void Deconstruct(this PropertyInfo p, out bool isStatic,
out bool isReadOnly, out bool isIndexed,
out Type propertyType)
{
var getter = p.GetMethod;

// Is the property read-only?


isReadOnly = ! p.CanWrite;

// Is the property instance or static?


isStatic = getter.IsStatic;

// Is the property indexed?


isIndexed = p.GetIndexParameters().Length > 0;

// Get the property type.


propertyType = p.PropertyType;
}

public static void Deconstruct(this PropertyInfo p, out bool hasGetAndSet,


out bool sameAccess, out string access,
out string getAccess, out string setAccess)
{
hasGetAndSet = sameAccess = false;
string getAccessTemp = null;
string setAccessTemp = null;

MethodInfo getter = null;


if (p.CanRead)
getter = p.GetMethod;

MethodInfo setter = null;


if (p.CanWrite)
setter = p.SetMethod;

if (setter != null && getter != null)


hasGetAndSet = true;

if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}
}

if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}

// Are the accessibility of the getter and setter the same?


if (setAccessTemp == getAccessTemp)
{
sameAccess = true;
access = getAccessTemp;
getAccess = setAccess = String.Empty;
}
else
{
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
}
}
}

public class Example


{
public static void Main()
{
Type dateType = typeof(DateTime);
PropertyInfo prop = dateType.GetProperty("Now");
var (isStatic, isRO, isIndexed, propType) = prop;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name} property:");
Console.WriteLine($" PropertyType: {propType.Name}");
Console.WriteLine($" Static: {isStatic}");
Console.WriteLine($" Read-only: {isRO}");
Console.WriteLine($" Indexed: {isIndexed}");

Type listType = typeof(List<>);


prop = listType.GetProperty("Item",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
BindingFlags.Static);
var (hasGetAndSet, sameAccess, accessibility, getAccessibility, setAccessibility) = prop;
Console.Write($"\nAccessibility of the {listType.FullName}.{prop.Name} property: ");

if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
//
// Accessibility of the System.Collections.Generic.List`1.Item property: public

Vedere anche
Variabili discard
Tipi tupla
Interfacce (Guida per programmatori C#)
02/11/2020 • 8 minutes to read • Edit Online

Un'interfaccia contiene le definizioni per un gruppo di funzionalità correlate che devono essere implementate da
una classe non astratta o da uno struct . Un'interfaccia può definire static metodi, che devono disporre di
un'implementazione di. A partire da C# 8,0, un'interfaccia può definire un'implementazione predefinita per i
membri. Un'interfaccia non può dichiarare i dati dell'istanza, ad esempio i campi, le proprietà implementate
automaticamente o gli eventi di tipo proprietà.
Usando le interfacce, è possibile, ad esempio, includere il comportamento di più origini in una classe. Tale
funzionalità è importante in C# perché il linguaggio non supporta l'ereditarietà multipla delle classi. Inoltre è
necessario usare un'interfaccia se si vuole simulare l'ereditarietà per le struct, perché non possono
effettivamente ereditare da un'altra struct o classe.
Per definire un'interfaccia, è possibile usare la parola chiave Interface , come illustrato nell'esempio seguente.

interface IEquatable<T>
{
bool Equals(T obj);
}

Il nome di un'interfaccia deve essere un nome di identificatoreC# valido. Per convenzione, i nomi di interfaccia
iniziano con una lettera I maiuscola.
Qualsiasi classe o struct che implementa l'interfaccia IEquatable<T> deve contenere una definizione per un
metodo Equals che corrisponde alla firma specificata dall'interfaccia. Di conseguenza, è possibile affidarsi a una
classe che implementa IEquatable<T> per contenere un metodo Equals con cui un'istanza della classe può
determinare se sia uguale a un'altra istanza della stessa classe.
La definizione di IEquatable<T> non fornisce un'implementazione per Equals . Una classe o uno struct può
implementare più interfacce, ma una classe può ereditare solo da una singola classe.
Per altre informazioni sulle classi astratte, vedere Classi e membri delle classi astratte e sealed.
Le interfacce possono contenere metodi di istanza, proprietà, eventi, indicizzatori o qualsiasi combinazione di
questi quattro tipi di membri. Le interfacce possono contenere costruttori statici, campi, costanti o operatori. Per
collegamenti a esempi, vedere Sezioni correlate. Un'interfaccia non può contenere campi di istanza, costruttori di
istanza o finalizzatori. I membri di interfaccia sono public per impostazione predefinita.
Per implementare un membro di interfaccia, il corrispondente membro della classe di implementazione deve
essere pubblico e non statico e avere lo stesso nome e la stessa firma del membro di interfaccia.
Quando una classe o uno struct implementa un'interfaccia, la classe o lo struct deve fornire un'implementazione
per tutti i membri dichiarati dall'interfaccia, ma non fornisce un'implementazione predefinita per. Tuttavia, se una
classe base implementa un'interfaccia, qualsiasi classe derivata dalla classe base eredita tale implementazione.
Nell'esempio seguente viene illustrata un'implementazione dell'interfaccia IEquatable<T>. La classe di
implementazione, Car , deve fornire un'implementazione del metodo Equals.
public class Car : IEquatable<Car>
{
public string Make {get; set;}
public string Model { get; set; }
public string Year { get; set; }

// Implementation of IEquatable<T> interface


public bool Equals(Car car)
{
return (this.Make, this.Model, this.Year) ==
(car.Make, car.Model, car.Year);
}
}

Le proprietà e gli indicizzatori di una classe possono definire altre funzioni di accesso per una proprietà o un
indicizzatore definito in un'interfaccia. Ad esempio, un'interfaccia può dichiarare una proprietà con una funzione
di accesso get. La classe che implementa l'interfaccia può dichiarare la stessa proprietà con una funzione di
accesso get o set. Tuttavia, se la proprietà o l'indicizzatore usa l'implementazione esplicita, le funzioni di
accesso devono corrispondere. Per altre informazioni sull'implementazione esplicita, vedere Implementazione
esplicita dell'interfaccia e Proprietà dell'interfaccia.
Le interfacce possono ereditare da una o più interfacce. L'interfaccia derivata eredita i membri dalle relative
interfacce di base. Una classe che implementa un'interfaccia derivata deve implementare tutti i membri
nell'interfaccia derivata, inclusi tutti i membri delle interfacce di base dell'interfaccia derivata. Tale classe può
essere convertita in modo implicito nell'interfaccia derivata o in una delle interfacce di base. Una classe può
includere un'interfaccia più volte tramite le classi di base ereditate o tramite le interfacce ereditate da altre
interfacce. Tuttavia, la classe può fornire un'implementazione di un'interfaccia solo una volta e solo se la classe
dichiara l'interfaccia durante la definizione della classe ( class ClassName : InterfaceName ). Se l'interfaccia viene
ereditata perché è stata ereditata una classe base che implementa l'interfaccia, la classe base fornisce
l'implementazione dei membri dell'interfaccia. Tuttavia, la classe derivata può reimplementare qualsiasi membro
dell'interfaccia invece di usare l'implementazione ereditata. Quando le interfacce dichiarano
un'implementazione predefinita di un metodo, qualsiasi classe che implementa tale interfaccia eredita tale
implementazione. Le implementazioni definite nelle interfacce sono virtuali e la classe di implementazione può
eseguire l'override dell'implementazione.
Una classe base può implementare anche i membri di interfaccia usando membri virtuali. In tal caso, una classe
derivata può modificare il comportamento dell'interfaccia eseguendo l'override dei membri virtuali. Per altre
informazioni su membri virtuali, vedere Polimorfismo.

Riepilogo delle interfacce


Un'interfaccia presenta le proprietà seguenti:
Un'interfaccia è in genere simile a una classe di base astratta con solo membri astratti. Qualsiasi classe o
struct che implementa l'interfaccia deve implementarne tutti i membri. Facoltativamente, un'interfaccia può
definire implementazioni predefinite per alcuni o tutti i relativi membri. Per altre informazioni, vedere metodi
di interfaccia predefiniti.
Non è possibile creare direttamente un'istanza di un'interfaccia. I membri vengono implementati da qualsiasi
classe o struct che implementa l'interfaccia.
Una classe o struct può implementare più interfacce. Una classe può ereditare una classe base e anche
implementare una o più interfacce.

Sezioni correlate
Proprietà dell'interfaccia
Indicizzatori nelle interfacce
Come implementare eventi di interfaccia
Classi e struct
Ereditarietà
Interfacce
Metodi
Polimorfismo
Classi e membri delle classi astratte e sealed
Proprietà
Eventi
Indicizzatori

Vedere anche
Guida per programmatori C#
Ereditarietà
Nomi di identificatore
Metodi in (C#)
28/01/2021 • 37 minutes to read • Edit Online

Un metodo è un blocco di codice che contiene una serie di istruzioni. Un programma fa in modo che le istruzioni
vengano eseguite chiamando il metodo e specificando gli argomenti del metodo obbligatori. In C#, ogni
istruzione eseguita viene attuata nel contesto di un metodo. Il metodo Main è il punto di ingresso per ogni
applicazione C# e viene chiamato da Common Language Runtime (CLR) quando viene avviato il programma.

NOTE
In questo argomento vengono descritti i metodi denominati. Per informazioni sulle funzioni anonime, vedere Funzioni
anonime.

Firme del metodo


I metodi vengono dichiarati in class o struct specificando:
Un livello di accesso facoltativo, ad esempio public o private . Il valore predefinito è private .
Modificatori facoltativi, ad esempio abstract o sealed .
Il valore restituito o void se il metodo non ha alcun valore.
Nome del metodo.
Tutti i parametri del metodo. I parametri del metodo vengono racchiusi tra parentesi e separati da virgole. Le
parentesi vuote indicano che il metodo non richiede parametri.
Queste parti costituiscono la firma del metodo.

IMPORTANT
Un tipo restituito di un metodo non fa parte della firma del metodo in caso di overload dei metodi. Fa tuttavia parte della
firma del metodo quando si determina la compatibilità tra un delegato e il metodo a cui fa riferimento.

L'esempio seguente definisce una classe denominata Motorcycle che contiene cinque metodi:

using System;

abstract class Motorcycle


{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }

// Only derived classes can call this.


protected void AddGas(int gallons) { /* Method statements here */ }

// Derived classes can override the base class implementation.


public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }

// Derived classes can override the base class implementation.


public virtual int Drive(TimeSpan time, int speed) { /* Method statements here */ return 0; }

// Derived classes must implement this.


public abstract double GetTopSpeed();
}
Si noti che la classe Motorcycle include un metodo sottoposto a overload, ovvero Drive . Due metodi hanno lo
stesso nome, ma devono essere differenziati in base ai relativi tipi di parametri.

Chiamata al metodo
I metodi possono essere di istanza o statici. Per chiamare un metodo di istanza è necessario creare un'istanza di
un oggetto e chiamare il metodo nell'oggetto; un metodo di istanza agisce sull'istanza e i relativi dati. La
chiamata a un metodo statico viene eseguita facendo riferimento al nome del tipo a cui appartiene il metodo; i
metodi statici non agiscono sui dati dell'istanza. Se si tenta di chiamare un metodo statico attraverso un'istanza
di un oggetto viene generato un errore del compilatore.
La chiamata a un metodo è un'operazione analoga all'accesso a un campo. Dopo il nome dell'oggetto (in una
chiamata a un metodo di istanza) o il nome del tipo (in una chiamata a un metodo static ), aggiungere un
punto, il nome del metodo e le parentesi. Gli argomenti sono elencati tra parentesi e sono separati da virgole.
La definizione del metodo specifica i nomi e i tipi di tutti i parametri obbligatori. Quando il chiamante chiama il
metodo, specifica valori concreti, chiamati argomenti, per ogni parametro. Gli argomenti devono essere
compatibili con il tipo di parametro, ma il nome dell'argomento, se usato nel codice chiamante, non deve essere
lo stesso del parametro denominato definito nel metodo. Nell'esempio seguente il metodo Square include un
singolo parametro di tipo int denominato i. La prima chiamata al metodo passa al metodo Square una
variabile di tipo int denominata num, la seconda passa una costante numerica e la terza passa un'espressione.

public class Example


{
public static void Main()
{
// Call with an int variable.
int num = 4;
int productA = Square(num);

// Call with an integer literal.


int productB = Square(12);

// Call with an expression that evaluates to int.


int productC = Square(productA * 3);
}

static int Square(int i)


{
// Store input argument in a local variable.
int input = i;
return input * input;
}
}

La forma più comune di chiamata al metodo usa argomenti posizionali; specifica gli argomenti nello stesso
ordine dei parametri del metodo. Per questa ragione, i metodi della classe Motorcycle possono essere chiamati
come nell'esempio seguente. La chiamata al metodo Drive , ad esempio, include due argomenti che
corrispondono ai due parametri nella sintassi del metodo. Il primo diventa il valore del parametro miles , il
secondo il valore del parametro speed .
class TestMotorcycle : Motorcycle
{
public override double GetTopSpeed()
{
return 108.4;
}

static void Main()


{

TestMotorcycle moto = new TestMotorcycle();

moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}

È anche possibile usare gli argomenti denominati anziché gli argomenti posizionali quando si richiama un
metodo. Quando si usano gli argomenti denominati, si specifica il nome del parametro seguito da due punti (":")
e l'argomento. Gli argomenti del metodo possono essere in qualsiasi ordine, a condizione che siano presenti
tutti gli argomenti necessari. L'esempio seguente usa gli argomenti denominati per richiamare il metodo
TestMotorcycle.Drive . In questo esempio gli argomenti denominati vengono passati nell'ordine inverso
dall'elenco di parametri del metodo.

using System;

class TestMotorcycle : Motorcycle


{
public override int Drive(int miles, int speed)
{
return (int) Math.Round( ((double)miles) / speed, 0);
}

public override double GetTopSpeed()


{
return 108.4;
}

static void Main()


{

TestMotorcycle moto = new TestMotorcycle();


moto.StartEngine();
moto.AddGas(15);
var travelTime = moto.Drive(speed: 60, miles: 170);
Console.WriteLine("Travel time: approx. {0} hours", travelTime);
}
}
// The example displays the following output:
// Travel time: approx. 3 hours

È possibile richiamare un metodo usando argomenti posizionali e denominati. Tuttavia, gli argomenti posizionali
possono seguire solo gli argomenti denominati quando gli argomenti denominati sono nelle posizioni corrette.
L'esempio seguente richiama il metodo TestMotorcycle.Drive dall'esempio precedente usando un argomento
posizionale e un argomento denominato.

var travelTime = moto.Drive(170, speed: 55);


Metodi ereditati e sottoposti a override
Oltre ai membri definiti in modo esplicito in un tipo, un tipo eredita i membri definiti nelle relative classi base.
Poiché tutti i tipi del sistema di tipi gestiti ereditano direttamente o indirettamente dalla classe Object, tutti i tipi
di ereditano i relativi membri, ad esempio Equals(Object), GetType() e ToString(). L'esempio seguente definisce
una classe Person , crea l'istanza di due oggetti Person e chiama il metodo Person.Equals per determinare se i
due oggetti sono uguali. Il metodo Equals , tuttavia, non è definito nella classe Person ; viene ereditato da
Object.

using System;

public class Person


{
public String FirstName;
}

public class Example


{
public static void Main()
{
var p1 = new Person();
p1.FirstName = "John";
var p2 = new Person();
p2.FirstName = "John";
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: False

I tipi possono eseguire l'override dei membri ereditati usando la parola chiave override e specificando
un'implementazione per il metodo sottoposto a override. La firma del metodo deve essere la stessa del metodo
sottoposto a override. L'esempio seguente è simile a quello precedente, ad eccezione del fatto che viene
eseguito l'override del metodo Equals(Object). Viene anche eseguito l'override del metodo GetHashCode()
poiché i due metodi devono fornire risultati coerenti.
using System;

public class Person


{
public String FirstName;

public override bool Equals(object obj)


{
var p2 = obj as Person;
if (p2 == null)
return false;
else
return FirstName.Equals(p2.FirstName);
}

public override int GetHashCode()


{
return FirstName.GetHashCode();
}
}

public class Example


{
public static void Main()
{
var p1 = new Person();
p1.FirstName = "John";
var p2 = new Person();
p2.FirstName = "John";
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: True

Passaggio di parametri
I tipi in C# sono tipi di valore o tipi di riferimento. Per un elenco dei tipi di valore predefiniti, vedere tipi. Per
impostazione predefinita, i tipi di valore e i tipi di riferimento vengono passati a un metodo per valore.
Passaggio di parametri per valore
Quando un tipo valore viene passato a un metodo per valore, viene passata al metodo una copia dell'oggetto
anziché l'oggetto stesso. Di conseguenza, le modifiche all'oggetto nel metodo chiamato non hanno effetto
sull'oggetto originale quando il controllo torna al chiamante.
L'esempio seguente passa un tipo di valore a un metodo per valore e il metodo chiamato tenta di modificare il
valore del tipo di valore. Definisce una variabile di tipo int , che è un tipo di valore, ne inizializza il valore a 20 e
la passa a un metodo denominato ModifyValue che modifica il valore della variabile in 30. Quando il metodo
viene restituito, tuttavia, il valore della variabile rimane invariato.
using System;

public class Example


{
public static void Main()
{
int value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(value);
Console.WriteLine("Back in Main, value = {0}", value);
}

static void ModifyValue(int i)


{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 20

Quando viene passato un oggetto di tipo riferimento a un metodo per valore, viene passato un riferimento
all'oggetto per valore, ovvero il metodo riceve un argomento che indica la posizione dell'oggetto, ma non
l'oggetto stesso. Se si modifica un membro dell'oggetto usando questo riferimento, la modifica si riflette
nell'oggetto quando il controllo torna al metodo chiamante. Tuttavia, la sostituzione dell'oggetto passato al
metodo non ha effetto sull'oggetto originale quando il controllo torna al chiamante.
L'esempio seguente definisce una classe (che è un tipo riferimento) denominata SampleRefType . Crea un'istanza
di un oggetto SampleRefType , assegna 44 al campo value e passa l'oggetto al metodo ModifyObject .
L'esempio è sostanzialmente uguale al precedente in quanto passa un argomento per valore a un metodo ma,
essendo usato un tipo riferimento, il risultato è diverso. La modifica eseguita in ModifyObject al campo
obj.value cambia anche il campo value dell'argomento rt nel metodo Main in 33, come mostra l'output
dell'esempio.

using System;

public class SampleRefType


{
public int value;
}

public class Example


{
public static void Main()
{
var rt = new SampleRefType();
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
}

static void ModifyObject(SampleRefType obj)


{
obj.value = 33;
}
}

Passaggio di parametri per riferimento


È necessario passare un parametro per riferimento quando si vuole modificare il valore di un argomento in un
metodo e questa modifica deve essere applicata quando il controllo torna al metodo chiamante. Per passare un
parametro per riferimento, usare la ref out parola chiave o. È anche possibile passare un valore per
riferimento per evitare la copia, ma impedire comunque modifiche tramite la in parola chiave.
L'esempio seguente è identico a quello precedente, ad eccezione del fatto che il valore viene passato per
riferimento al metodo ModifyValue . Quando il valore del parametro è modificato nel metodo ModifyValue , la
modifica del valore si riflette quando il controllo torna al chiamante.

using System;

public class Example


{
public static void Main()
{
int value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(ref value);
Console.WriteLine("Back in Main, value = {0}", value);
}

static void ModifyValue(ref int i)


{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 30

Un modello comune che usa parametri di riferimento implica lo scambio dei valori delle variabili. Si passano
due variabili a un metodo per riferimento e il metodo scambia il loro contenuto. L'esempio seguente scambia i
valori integer.

using System;

public class Example


{
static void Main()
{
int i = 2, j = 3;
System.Console.WriteLine("i = {0} j = {1}" , i, j);

Swap(ref i, ref j);

System.Console.WriteLine("i = {0} j = {1}" , i, j);


}

static void Swap(ref int x, ref int y)


{
int temp = x;
x = y;
y = temp;
}
}
// The example displays the following output:
// i = 2 j = 3
// i = 3 j = 2
Il passaggio di un parametro di tipo riferimento consente di modificare il valore del riferimento stesso anziché il
valore dei singoli elementi o campi.
Matrici di parametri
In alcuni casi, il requisito che richiede di specificare il numero esatto di argomenti per il metodo è limitativo.
Usando la parola chiave params per indicare che un parametro è una matrice di parametri, si consente la
chiamata al metodo con un numero variabile di argomenti. Il parametro contrassegnato con la parola chiave
params deve essere di tipo matrice e deve essere l'ultimo parametro dell'elenco di parametri del metodo.

Un chiamante può quindi richiamare il metodo in uno dei quattro modi seguenti:
Passando una matrice del tipo appropriato che contiene il numero desiderato di elementi.
Passando un elenco delimitato da virgole di singoli argomenti del tipo appropriato al metodo.
Passando null .
Non specificando un argomento nella matrice di parametri.
L'esempio seguente definisce un metodo denominato GetVowels che restituisce tutte le vocali da una matrice di
parametri. Il Main metodo illustra tutti e quattro i modi per richiamare il metodo. Non è necessario che i
chiamanti specifichino un argomento per i parametri che includono il modificatore params . In tal caso, il
parametro è una matrice vuota.

using System;
using System.Linq;

class Example
{
static void Main()
{
string fromArray = GetVowels(new[] { "apple", "banana", "pear" });
Console.WriteLine($"Vowels from array: '{fromArray}'");

string fromMultipleArguments = GetVowels("apple", "banana", "pear");


Console.WriteLine($"Vowels from multiple arguments: '{fromMultipleArguments}'");

string fromNull = GetVowels(null);


Console.WriteLine($"Vowels from null: '{fromNull}'");

string fromNoValue = GetVowels();


Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
}

static string GetVowels(params string[] input)


{
if (input == null || input.Length == 0)
{
return string.Empty;
}

var vowels = new char[] { 'A', 'E', 'I', 'O', 'U' };


return string.Concat(
input.SelectMany(
word => word.Where(letter => vowels.Contains(char.ToUpper(letter)))));
}
}

// The example displays the following output:


// Vowels from array: 'aeaaaea'
// Vowels from multiple arguments: 'aeaaaea'
// Vowels from null: ''
// Vowels from no value: ''
Parametri e argomenti facoltativi
La definizione di un metodo può specificare che i parametri sono obbligatori o facoltativi. I parametri sono
obbligatori per impostazione predefinita. I parametri facoltativi vengono specificati includendo il valore
predefinito del parametro nella definizione del metodo. Quando il metodo viene chiamato, se non sono
specificati argomenti per un parametro facoltativo, viene usato il valore predefinito.
Il valore predefinito del parametro deve essere assegnato da uno dei tipi di espressioni seguenti:
Una costante, ad esempio una stringa letterale o un numero.
Un'espressione del form new ValType() , dove ValType è un tipo di valore. Osservare come venga
richiamato il costruttore senza parametri implicito del tipo di valore, che non è un membro effettivo del tipo.
Un'espressione del form default(ValType) , dove ValType è un tipo di valore.

Se un metodo include parametri obbligatori e facoltativi, i parametri facoltativi sono definiti alla fine dell'elenco
di parametri, dopo tutti i parametri obbligatori.
L'esempio seguente definisce un metodo, ExampleMethod , con un parametro obbligatorio e due facoltativi.

using System;

public class Options


{
public void ExampleMethod(int required, int optionalInt = default(int),
string description = "Optional Description")
{
Console.WriteLine("{0}: {1} + {2} = {3}", description, required,
optionalInt, required + optionalInt);
}
}

Se viene richiamato un metodo con più argomenti facoltativi usando argomenti posizionali, il chiamante deve
specificare un argomento per tutti i parametri facoltativi, dal primo all'ultimo parametro per il quale è
specificato un argomento. Nel caso del metodo ExampleMethod , ad esempio, se il chiamante specifica un
argomento per il parametro description , deve specificarne uno anche per il parametro optionalInt .
opt.ExampleMethod(2, 2, "Addition of 2 and 2"); è una chiamata a un metodo valido;
opt.ExampleMethod(2, , "Addition of 2 and 0"); genera un errore di compilazione "Argomento mancante".

Se un metodo viene chiamato usando argomenti denominati o una combinazione di argomenti posizionali e
denominati, il chiamante può omettere tutti gli argomenti successivi all'ultimo argomento posizionale nella
chiamata al metodo.
L'esempio seguente esegue una chiamata al metodo ExampleMethod tre volte. Le prime due chiamate al metodo
usano argomenti posizionali. La prima omette entrambi gli argomenti facoltativi, mentre la seconda omette
l'ultimo argomento. La terza chiamata al metodo fornisce un argomento posizionale per il parametro
obbligatorio, ma usa un argomento denominato per fornire un valore al description parametro omettendo l'
optionalInt argomento.
public class Example
{
public static void Main()
{
var opt = new Options();
opt.ExampleMethod(10);
opt.ExampleMethod(10, 2);
opt.ExampleMethod(12, description: "Addition with zero:");
}
}
// The example displays the following output:
// Optional Description: 10 + 0 = 10
// Optional Description: 10 + 2 = 12
// Addition with zero:: 12 + 0 = 12

L'uso di parametri facoltativi ha effetto sulla risoluzione dell'overload o sulla modalità con la quale il
compilatore C# determina l'overload specifico da richiamare tramite una chiamata al metodo come descritto di
seguito:
Un metodo, un indicizzatore o un costruttore è un candidato per l'esecuzione se ogni parametro è facoltativo
o corrisponde, per nome o per posizione, a un solo argomento nell'istruzione chiamante e tale argomento
può essere convertito nel tipo del parametro.
Se è disponibile più di un candidato, agli argomenti specificati in modo esplicito vengono applicate le regole
di risoluzione dell'overload per le conversioni preferite. Gli argomenti omessi per i parametri facoltativi
vengono ignorati.
Se due candidati sono giudicati ugualmente validi, la preferenza va a un candidato che non ha parametri
facoltativi per i quali sono stati omessi gli argomenti nella chiamata. Si tratta di una conseguenza di una
preferenza generale nella risoluzione dell'overload per i candidati che hanno un numero di parametri
inferiore.

Valori restituiti
I metodi possono restituire un valore al chiamante. Se il tipo restituito, ovvero il tipo elencato prima del nome
del metodo, non è void , il metodo può restituire il valore usando la parola chiave return . Un'istruzione con la
parola chiave return seguita da una variabile, una costante o un'espressione corrispondente al tipo restituito
restituirà tale valore al chiamante del metodo. Per usare la parola chiave return per restituire un valore, sono
obbligatori metodi con un tipo restituito non void. La parola chiave return interrompe anche l'esecuzione del
metodo.
Se il tipo restituito è void , un'istruzione return senza un valore è tuttavia utile per interrompere l'esecuzione
del metodo. Senza la parola chiave return , l'esecuzione del metodo verrà interrotta quando verrà raggiunta la
fine del blocco di codice.
Ad esempio, questi due metodi usano la parola chiave return per restituire numeri interi:

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}

public int SquareANumber(int number)


{
return number * number;
}
}
Per usare un valore restituito da un metodo, il metodo chiamante può usare la chiamata al metodo stessa
ovunque è sufficiente un valore dello stesso tipo. È inoltre possibile assegnare il valore restituito a una variabile.
I due esempi seguenti di codice ottengono lo stesso risultato:

int result = obj.AddTwoNumbers(1, 2);


result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);

result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));


// The result is 9.
Console.WriteLine(result);

L'uso di una variabile locale, in questo caso result , per archiviare un valore è facoltativo. Potrebbe migliorare la
leggibilità del codice o potrebbe essere necessario se si desidera archiviare il valore originale dell'argomento
per l'intero ambito del metodo.
In alcuni casi, si vuole che il metodo restituisca più di un singolo valore. A questo scopo, a partire da C# 7.0 è
possibile usare tipi di tupla e letterali di tupla. Il tipo di tupla definisce i tipi di dati degli elementi della tupla. I
letterali di tupla specificano i valori effettivi della tupla restituita. Nell'esempio seguente
(string, string, string, int) definisce il tipo di tupla restituito dal metodo GetPersonalInfo . L'espressione
(per.FirstName, per.MiddleName, per.LastName, per.Age) è il letterale della tupla; il metodo restituisce il nome
iniziale, centrale e finale e la durata di un oggetto PersonInfo .

public (string, string, string, int) GetPersonalInfo(string id)


{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

Il chiamante può quindi usare la tupla restituita con codice simile al seguente:

var person = GetPersonalInfo("111111111")


Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");

È anche possibile assegnare i nomi agli elementi della tupla nella definizione del tipo della tupla. L'esempio
seguente mostra una versione alternativa del metodo GetPersonalInfo che usa elementi denominati:

public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

La chiamata precedente al metodo GetPersonInfo può quindi essere modificata come segue:

var person = GetPersonalInfo("111111111");


Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");

Se a un metodo viene passata una matrice come argomento e il metodo modifica il valore dei singoli elementi,
non è necessario che il metodo restituisca la matrice, anche se è possibile scegliere di effettuare questa
operazione per ragioni di stile o per il flusso funzionale di valori. L'operazione non è necessaria perché C# passa
tutti i tipi riferimento per valore e il valore di un riferimento a una matrice è il puntatore alla matrice.
Nell'esempio seguente le modifiche al contenuto della matrice values eseguite nel metodo DoubleValues sono
rilevabili da qualsiasi codice che include un riferimento alla matrice.

using System;

public class Example


{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8 };
DoubleValues(values);
foreach (var value in values)
Console.Write("{0} ", value);
}

public static void DoubleValues(int[] arr)


{
for (int ctr = 0; ctr <= arr.GetUpperBound(0); ctr++)
arr[ctr] = arr[ctr] * 2;
}
}
// The example displays the following output:
// 4 8 12 16

Metodi di estensione
In genere, è possibile aggiungere un metodo a un tipo esistente in due modi:
Modificare il codice sorgente per il tipo. Questa operazione non è possibile se non si è proprietari del codice
sorgente del tipo. Questa operazione diventa una modifica importante se si aggiungono anche tutti i campi
dati privati per supportare il metodo.
Definire il nuovo metodo in una classe derivata. Non è possibile aggiungere un metodo in questo modo
usando l'ereditarietà per gli altri tipi, ad esempio strutture ed enumerazioni. Questo modo non può essere
usato neanche per "aggiungere" un metodo a una classe sealed.
I metodi di estensione consentono di "aggiungere" un metodo a un tipo esistente senza modificare il tipo o
implementare il nuovo metodo in un tipo ereditato. È necessario anche che il metodo di estensione non si trovi
nello stesso assembly del tipo che estende. Un metodo di estensione viene chiamato come se fosse un membro
definito di un tipo.
Per altre informazioni, vedere Metodi di estensione.

Metodi asincroni
Tramite la funzionalità async, è possibile richiamare i metodi asincroni senza usare callback espliciti o
suddividere manualmente il codice in più metodi o espressioni lambda.
Se si contrassegna un metodo con il modificatore async, è possibile usare l'operatore await nel metodo. Quando
il controllo raggiunge un'espressione await nel metodo asincrono, il controllo torna al chiamante se l'attività
attesa non è stata completata e l'avanzamento nel metodo con la parola chiave await viene sospeso fino al
completamento dell'attività attesa. Una volta completata l'attività, l'esecuzione del metodo può riprendere.

NOTE
Un metodo asincrono restituisce al chiamante quando rileva il primo oggetto atteso che non è ancora completo o
raggiunge la fine del metodo asincrono, a seconda di quale si verifica per primo.
Un metodo asincrono può avere un tipo restituito Task<TResult>, Task o void . Il tipo restituito void viene
usato principalmente per definire i gestori eventi in cui è necessario un tipo restituito void . Un metodo
asincrono che restituisce void non può essere atteso e il chiamante di un metodo che restituisce void non può
intercettare le eccezioni generate dal metodo. A partire da C# 7.0, un metodo asincrono può avere qualsiasi tipo
restituito di tipo attività.
Nell'esempio seguente DelayAsync è un metodo asincrono con un'istruzione return che restituisce un valore
Integer. Poiché si tratta di un metodo asincrono, la dichiarazione del metodo deve avere un tipo restituito di
Task<int> . Poiché il tipo restituito è Task<int> , la valutazione dell'espressione await in DoSomethingAsync
genera un numero intero come dimostra l'istruzione int result = await delayTask seguente.

using System;
using System.Threading.Tasks;

class Program
{
static Task Main() => DoSomethingAsync();

static async Task DoSomethingAsync()


{
Task<int> delayTask = DelayAsync();
int result = await delayTask;

// The previous two statements may be combined into


// the following statement.
//int result = await DelayAsync();

Console.WriteLine($"Result: {result}");
}

static async Task<int> DelayAsync()


{
await Task.Delay(100);
return 5;
}
}
// Example output:
// Result: 5

Un metodo asincrono non può dichiarare parametri in, ref o out, ma può chiamare metodi che hanno tali
parametri.
Per altre informazioni sui metodi asincroni, vedere programmazione asincrona con i tipi Async e await e Async
restituiti.

Membri con corpo di espressione


È comune disporre di definizioni di metodo che semplicemente restituiscono subito il risultato di un'espressione
o che includono una singola istruzione come corpo del metodo. Esiste una sintassi breve per definire tali metodi
usando => :

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);

Se il metodo restituisce void o è un metodo asincrono, il corpo del metodo deve essere un'espressione di
istruzione (come per le espressioni lambda). Le proprietà e gli indicizzatori devono essere di sola lettura e non è
necessario usare la parola chiave della funzione di accesso get .

Iterators
Un iteratore esegue un'iterazione personalizzata su una raccolta, ad esempio un elenco o una matrice. Un
iteratore usa l'istruzione yield return per restituire un elemento alla volta. Quando viene raggiunta un'istruzione
yield return , viene memorizzata la posizione corrente in modo che il chiamante possa richiedere l'elemento
successivo della sequenza.
Il tipo restituito di un iteratore può essere IEnumerable, IEnumerable<T>, IEnumeratoro IEnumerator<T>.
Per ulteriori informazioni, vedere iteratori.

Vedi anche
Modificatori di accesso
Classi statiche e membri di classi statiche
Ereditarietà
Classi e membri delle classi astratte e sealed
params
out
ref
in
Passaggio di parametri
Proprietà
28/01/2021 • 16 minutes to read • Edit Online

Le proprietà sono elementi fondamentali in C#. Il linguaggio definisce la sintassi che consente agli sviluppatori
di scrivere codice che esprime in modo preciso la finalità della progettazione.
Quando viene eseguito l'accesso, le proprietà si comportano come i campi. Tuttavia, a differenza dei campi, le
proprietà vengono implementate con funzioni di accesso che definiscono le istruzioni eseguite al momento
dell'accesso e dell'assegnazione della proprietà.

Sintassi delle proprietà


La sintassi delle proprietà è un'estensione naturale dei campi. Un campo definisce una posizione di
archiviazione:

public class Person


{
public string FirstName;
// remaining implementation removed from listing
}

La definizione di una proprietà contiene le dichiarazioni di una funzione di accesso get e set che recupera e
assegna il valore della proprietà:

public class Person


{
public string FirstName { get; set; }

// remaining implementation removed from listing


}

La sintassi illustrata sopra è la sintassi della proprietà automatica. Il compilatore genera la posizione di
archiviazione per il campo che esegue il backup della proprietà. Il compilatore implementa anche il corpo delle
funzioni di accesso get e set .
In alcuni casi è necessario inizializzare una proprietà con un valore diverso da quello predefinito per il suo tipo. In
C# questa operazione è possibile impostando un valore dopo la parentesi graffa chiusa della proprietà. Per la
proprietà FirstName è preferibile usare come valore iniziale una stringa vuota anziché null . Ecco come
eseguire questa operazione:

public class Person


{
public string FirstName { get; set; } = string.Empty;

// remaining implementation removed from listing


}

L'inizializzazione specifica è particolarmente utile per le proprietà di sola lettura, come si vedrà più avanti in
questo articolo.
È anche possibile definire l'archiviazione manualmente, come illustrato di seguito:
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string firstName;
// remaining implementation removed from listing
}

Se l'implementazione di una proprietà corrisponde a un'espressione singola, è possibile usare membri con
corpo di espressione per il getter o il setter:

public class Person


{
public string FirstName
{
get => firstName;
set => firstName = value;
}
private string firstName;
// remaining implementation removed from listing
}

Questa sintassi semplificata verrà usata ovunque applicabile in questo articolo.


La definizione della proprietà illustrata sopra è una proprietà di lettura/scrittura. Si noti la parola chiave value
nella funzione di accesso impostata. La funzione di accesso set ha sempre un singolo parametro denominato
value . La funzione di accesso get deve restituire un valore che è convertibile nel tipo della proprietà (in
questo esempio string ).
Queste sono le nozioni di basi sulla sintassi. Esistono numerose varianti che supportano un'ampia gamma di
termini di progettazione diversi. Di seguito sono descritte le opzioni di sintassi per ogni termine.

Scenari
Gli esempi precedenti hanno illustrato uno dei casi più semplici di definizione delle proprietà, ovvero una
proprietà di lettura/scrittura senza convalida. Scrivendo il codice desiderato nelle funzioni di accesso get e
set è possibile creare scenari diversi.

Convalida
È possibile scrivere codice nella funzione di accesso set per assicurarsi che i valori rappresentati da una
proprietà siano sempre validi. Ad esempio, si supponga che una regola per la classe Person preveda che il
nome non può essere vuoto o uno spazio vuoto. Sarà necessario scrivere:
public class Person
{
public string FirstName
{
get => firstName;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("First name must not be blank");
firstName = value;
}
}
private string firstName;
// remaining implementation removed from listing
}

L'esempio precedente può essere semplificato usando un' throw espressione come parte della convalida del
setter di proprietà:

public class Person


{
public string FirstName
{
get => firstName;
set => firstName = (!string.IsNullOrWhiteSpace(value)) ? value : throw new ArgumentException("First
name must not be blank");
}
private string firstName;
// remaining implementation removed from listing
}

L'esempio precedente applica la regola che prevede che il nome non può essere vuoto o uno spazio vuoto. Se lo
sviluppatore scrive

hero.FirstName = "";

L'assegnazione genera ArgumentException . Poiché la funzione di accesso di un insieme di proprietà deve avere
un tipo restituito void, gli errori vengono segnalati nella funzione di accesso dell'insieme generando
un'eccezione.
La stessa sintassi può essere estesa a ogni elemento necessario nello scenario. È possibile controllare le relazioni
tra le diverse proprietà o eseguire la convalida in base a qualsiasi condizione esterna. Tutte le istruzioni C# valide
sono valide nella funzione di accesso di una proprietà.
Sola lettura
Le definizioni di proprietà descritte fino a questo punto si riferiscono a proprietà di lettura/scrittura con funzioni
di accesso pubbliche. Non si tratta tuttavia dell'unica accessibilità valida per le proprietà. È possibile creare
proprietà di sola lettura o assegnare un'accessibilità diversa alle funzioni di accesso set e get. Si supponga che la
classe Person debba consentire soltanto la modifica del valore della proprietà FirstName da altri metodi della
classe. È possibile assegnare alla funzione di accesso set l'accessibilità private anziché public :

public class Person


{
public string FirstName { get; private set; }

// remaining implementation removed from listing


}
L'accesso alla proprietà FirstName potrà quindi essere eseguito da qualsiasi codice, ma la proprietà potrà essere
assegnata soltanto da altro codice della classe Person .
È possibile aggiungere qualsiasi modificatore di accesso restrittivo alle funzioni di accesso set o get. Il
modificatore di accesso inserito nella singola funzione di accesso deve essere più restrittivo del modificatore di
accesso della definizione della proprietà. Il codice precedente è valido poiché la proprietà FirstName è public e
la funzione di accesso set è private . Non è possibile dichiarare una proprietà private con una funzione di
accesso public . Le dichiarazioni di proprietà possono anche essere dichiarate protected , internal ,
protected internal o anche private .

È anche consentito inserire il modificatore più restrittivo nella funzione di accesso get . Ad esempio, è possibile
avere una proprietà public e limitare la funzione di accesso get a private . Questo scenario viene usato
raramente.
È anche possibile limitare le modifiche a una proprietà, in modo che possa essere impostata solo in un
costruttore o in un inizializzatore di proprietà. È possibile modificare in tal senso la classe Person come segue:

public class Person


{
public Person(string firstName) => this.FirstName = firstName;

public string FirstName { get; }

// remaining implementation removed from listing


}

Questa funzionalità viene in genere usata per l'inizializzazione delle raccolte esposte come proprietà di sola
lettura:

public class Measurements


{
public ICollection<DataPoint> points { get; } = new List<DataPoint>();
}

Proprietà calcolate
Una proprietà non si limita a restituire il valore di un campo membro. È possibile creare proprietà che
restituiscono un valore calcolato. Espandere l'oggetto Person per restituire il nome completo, calcolato
concatenando il nome e il cognome:

public class Person


{
public string FirstName { get; set; }

public string LastName { get; set; }

public string FullName { get { return $"{FirstName} {LastName}"; } }


}

L'esempio precedente usa la funzionalità di interpolazione delle stringhe per creare la stringa formattata per il
nome completo.
È anche possibile usare un membro con corpo di espressione che consente di creare la proprietà calcolata
FullName in modo più conciso:
public class Person
{
public string FirstName { get; set; }

public string LastName { get; set; }

public string FullName => $"{FirstName} {LastName}";


}

I membri con corpo di espressione usano la sintassi delle espressioni lambda per definire i metodi che
contengono una singola espressione. In questo caso, l'espressione restituisce il nome completo per l'oggetto
person.
Proprietà con valutazione memorizzata nella cache
È possibile unire il concetto di proprietà calcolata al concetto di archiviazione e creare una proprietà con
valutazione memorizzata nella cache. Ad esempio, è possibile aggiornare la proprietà FullName in modo che
venga eseguita soltanto la formattazione della stringa al primo accesso:

public class Person


{
public string FirstName { get; set; }

public string LastName { get; set; }

private string fullName;


public string FullName
{
get
{
if (fullName == null)
fullName = $"{FirstName} {LastName}";
return fullName;
}
}
}

Il codice riportato sopra tuttavia contiene un bug. Se il codice aggiorna il valore della proprietà FirstName o
LastName , il campo fullName valutato in precedenza non è valido. Modificare le funzioni di accesso set della
proprietà FirstName e LastName in modo che il campo fullName venga calcolato nuovamente:
public class Person
{
private string firstName;
public string FirstName
{
get => firstName;
set
{
firstName = value;
fullName = null;
}
}

private string lastName;


public string LastName
{
get => lastName;
set
{
lastName = value;
fullName = null;
}
}

private string fullName;


public string FullName
{
get
{
if (fullName == null)
fullName = $"{FirstName} {LastName}";
return fullName;
}
}
}

La versione finale valuta la proprietà FullName solo quando necessario. Se è valida, viene usata la versione
calcolata in precedenza. Se un'altra modifica dello stato annulla la validità della versione calcolata in precedenza,
la versione verrà ricalcolata. Non è necessario che gli sviluppatori che usano questa classe siano a conoscenza
dei dettagli dell'implementazione. Nessuna di queste modifiche interne ha effetto sull'uso dell'oggetto Person.
Questo è il motivo principale dell'uso delle proprietà per l'esposizione dei membri dati di un oggetto.
Collegamento di attributi a proprietà implementate automaticamente
A partire da C# 7.3 è possibile collegare gli attributi campo al campo sottostante generato dal compilatore nelle
proprietà implementate automaticamente. Si consideri ad esempio una revisione della classe Person che
aggiunge una proprietà Id al valore intero univoco. È possibile scrivere la Id proprietà usando una proprietà
implementata automaticamente, ma la progettazione non chiama per la persistenza della Id Proprietà. La
classe NonSerializedAttribute può essere collegata soltanto a campi, non a proprietà. È possibile collegare la
classe NonSerializedAttribute al campo sottostante per la proprietà Id usando l'identificatore field:
dell'attributo, come illustrato nell'esempio seguente:
public class Person
{
public string FirstName { get; set; }

public string LastName { get; set; }

[field:NonSerialized]
public int Id { get; set; }

public string FullName => $"{FirstName} {LastName}";


}

Questa tecnica funziona con qualsiasi attributo collegato al campo sottostante nella proprietà implementate
automaticamente.
Implementazione di NotifyPropertyChanged
Uno scenario finale in cui è necessario scrivere codice nella funzione di accesso di una proprietà è quello
finalizzato al supporto dell'interfaccia INotifyPropertyChanged usata per inviare ai client di data binding la
notifica della modifica di un valore. Quando viene modificato il valore di una proprietà, l'oggetto genera l'evento
INotifyPropertyChanged.PropertyChanged per indicare la modifica. Le librerie di data binding aggiornano a loro
volta gli elementi visualizzati in base alla modifica. Il codice seguente illustra come implementare
INotifyPropertyChanged per la proprietà FirstName della classe person.

public class Person : INotifyPropertyChanged


{
public string FirstName
{
get => firstName;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("First name must not be blank");
if (value != firstName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(FirstName)));
}
firstName = value;
}
}
private string firstName;

public event PropertyChangedEventHandler PropertyChanged;


// remaining implementation removed from listing
}

L'operatore ?. è chiamato operatore condizionale Null. L'operatore cerca un riferimento Null prima di eseguire
la valutazione della parte destra dell'operatore. Se non vengono trovati sottoscrittori dell'evento
PropertyChanged , il codice che genera l'evento non viene eseguito. In questo caso, verrà generato
NullReferenceException senza eseguire il controllo. Per altre informazioni, vedere events . Questo esempio usa
anche il nuovo operatore nameof per convertire il simbolo del nome della proprietà nella rappresentazione di
testo. L'uso di nameof può ridurre gli errori nel caso in cui il nome della proprietà sia stato digitato
erroneamente.
L'implementazione di INotifyPropertyChanged è quindi un esempio di caso in cui è possibile scrivere codice
nelle funzioni di accesso per supportare gli scenari necessari.

Riepilogo
Le proprietà sono una forma di campi intelligenti in una classe o un oggetto. All'esterno dell'oggetto, vengono
visualizzate come campi dell'oggetto. Tuttavia, le proprietà possono essere implementate usando l'intera
gamma di funzionalità di C#. È possibile specificare la convalida, un'accessibilità diversa, la valutazione lazy o
tutti i requisiti necessari negli scenari.
Indicizzatori
02/11/2020 • 15 minutes to read • Edit Online

Gli indicizzatori sono simili alle proprietà e per molti aspetti si basano sulle stesse funzionalità del linguaggio
delle proprietà. Gli indicizzatori consentono proprietà indicizzate, ovvero proprietà a cui si fa riferimento tramite
uno o più argomenti, che forniscono un indice per una raccolta di valori.

Sintassi degli indicizzatori


È possibile accedere a un indicizzatore tramite un nome di variabile e parentesi quadre. Gli argomenti
dell'indicizzatore devono trovarsi all'interno delle parentesi:

var item = someObject["key"];


someObject["AnotherKey"] = item;

Per dichiarare un indicizzatore si usa la parola chiave this come nome di proprietà e si racchiudono gli
argomenti tra parentesi quadre. Questa dichiarazione corrisponde all'utilizzo illustrato nel paragrafo precedente:

public int this[string key]


{
get { return storage.Find(key); }
set { storage.SetAt(key, value); }
}

Da questo esempio iniziale è possibile rendersi conto della relazione tra la sintassi relativa alle proprietà e a
quella relativa agli indicizzatori. Questa analogia si estende alla maggior parte delle regole della sintassi degli
indicizzatori. Agli indicizzatori si possono applicare tutti i modificatori di accesso validi (public, protected
internal, protected, internal, private o private protected). Possono essere sealed, virtual o abstract. Come per le
proprietà, in un indicizzatore è possibile specificare modificatori di accesso diversi per le funzioni di accesso get
e set. È anche possibile specificare indicizzatori di sola lettura (omettendo la funzione di accesso set) o
indicizzatori di sola scrittura (omettendo la funzione di accesso get).
È possibile applicare agli indicizzatori quasi tutto ciò che si apprende dall'uso delle proprietà. L'unica eccezione a
tale regola è costituita dalle proprietà implementate automaticamente. Il compilatore non è sempre in grado di
generare l'archiviazione corretta per un indicizzatore.
La differenza tra indicizzatori e proprietà è costituita dalla presenza negli indicizzatori di argomenti che
consentono di fare riferimento a un elemento all'interno di un set di elementi. È possibile definire più
indicizzatori in un tipo, a condizione che gli elenchi di argomenti per ogni indicizzatore siano univoci. Verranno
ora esaminati diversi scenari in cui è possibile usare uno o più indicizzatori in una definizione di classe.

Scenari
Si definiscono indicizzatori in un tipo se l'API corrispondente modella una raccolta per cui si definiscono gli
argomenti. Gli indicizzatori possono essere mappati direttamente o meno ai tipi di raccolta che fanno parte di
.NET Framework Core. Oltre alla modellazione di una raccolta, il tipo può avere altre responsabilità. Gli
indicizzatori consentono di fornire l'API corrispondente all'astrazione del tipo senza esporre nei minimi dettagli
la modalità di archiviazione o di calcolo dei valori per tale astrazione.
Di seguito è riportata la descrizione dettagliata di alcuni scenari comuni per l'uso di indicizzatori. È possibile
accedere alla cartella degli esempi per gli indicizzatori. Per istruzioni sul download, vedere Esempi ed
esercitazioni.
Matrici e vettori
Uno degli scenari più comuni per la creazione di indicizzatori si presenta quando il tipo modella una matrice o
un vettore. È possibile creare un indicizzatore per modellare un elenco ordinato di dati.
Il vantaggio di creare un indicizzatore personalizzato è la possibilità di definire la modalità di archiviazione della
raccolta più adatta alle proprie esigenze. Si immagini uno scenario in cui il tipo debba modellare una quantità di
dati cronologici troppo grande perché sia possibile caricarla in memoria in una sola volta. È quindi necessario
caricare e scaricare sezioni della raccolta in base all'utilizzo. L'esempio seguente riproduce questo
comportamento. L'esempio indica il numero di punti dati esistenti, crea pagine in cui inserire sezioni di dati su
richiesta e rimuove le pagine dalla memoria per liberare spazio per le pagine relative alle richieste più recenti.

public class DataSamples


{
private class Page
{
private readonly List<Measurements> pageData = new List<Measurements>();
private readonly int startingIndex;
private readonly int length;
private bool dirty;
private DateTime lastAccess;

public Page(int startingIndex, int length)


{
this.startingIndex = startingIndex;
this.length = length;
lastAccess = DateTime.Now;

// This stays as random stuff:


var generator = new Random();
for(int i=0; i < length; i++)
{
var m = new Measurements
{
HiTemp = generator.Next(50, 95),
LoTemp = generator.Next(12, 49),
AirPressure = 28.0 + generator.NextDouble() * 4
};
pageData.Add(m);
}
}
public bool HasItem(int index) =>
((index >= startingIndex) &&
(index < startingIndex + length));

public Measurements this[int index]


{
get
{
lastAccess = DateTime.Now;
return pageData[index - startingIndex];
}
set
{
pageData[index - startingIndex] = value;
dirty = true;
lastAccess = DateTime.Now;
}
}

public bool Dirty => dirty;


public DateTime LastAccess => lastAccess;
}
private readonly int totalSize;
private readonly List<Page> pagesInMemory = new List<Page>();

public DataSamples(int totalSize)


{
this.totalSize = totalSize;
}

public Measurements this[int index]


{
get
{
if (index < 0)
throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= totalSize)
throw new IndexOutOfRangeException("Cannot index past the end of storage");

var page = updateCachedPagesForAccess(index);


return page[index];
}
set
{
if (index < 0)
throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= totalSize)
throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);

page[index] = value;
}
}

private Page updateCachedPagesForAccess(int index)


{
foreach (var p in pagesInMemory)
{
if (p.HasItem(index))
{
return p;
}
}
var startingIndex = (index / 1000) * 1000;
var newPage = new Page(startingIndex, 1000);
addPageToCache(newPage);
return newPage;
}

private void addPageToCache(Page p)


{
if (pagesInMemory.Count > 4)
{
// remove oldest non-dirty page:
var oldest = pagesInMemory
.Where(page => !page.Dirty)
.OrderBy(page => page.LastAccess)
.FirstOrDefault();
// Note that this may keep more than 5 pages in memory
// if too much is dirty
if (oldest != null)
pagesInMemory.Remove(oldest);
}
pagesInMemory.Add(p);
}
}

È possibile seguire questo schema di progettazione per modellare qualsiasi funzione di ordinamento di una
raccolta nei casi in cui per validi motivi non è possibile caricare l'intero set di dati in una raccolta in memoria. Si
noti che la classe Page è una classe annidata private che non fa parte dell'interfaccia public. Questi dettagli
sono nascosti agli utenti di questa classe.
Dizionari
Un altro scenario comune riguarda la necessità di modellare un dizionario o una mappa e si presenta quando il
tipo archivia i valori in base alla chiave, in genere chiavi di testo. Questo esempio crea un dizionario che esegue
il mapping di argomenti della riga di comando a espressioni lambda che gestiscono tali opzioni. Nell'esempio
seguente sono presenti due classi: una classe ArgsActions che esegue il mapping di un'opzione della riga di
comando a un delegato Action e una classe ArgsProcessor che usa ArgsActions per eseguire ogni Action
quando incontra tale opzione.

public class ArgsProcessor


{
private readonly ArgsActions actions;

public ArgsProcessor(ArgsActions actions)


{
this.actions = actions;
}

public void Process(string[] args)


{
foreach(var arg in args)
{
actions[arg]?.Invoke();
}
}

}
public class ArgsActions
{
readonly private Dictionary<string, Action> argsActions = new Dictionary<string, Action>();

public Action this[string s]


{
get
{
Action action;
Action defaultAction = () => {} ;
return argsActions.TryGetValue(s, out action) ? action : defaultAction;
}
}

public void SetOption(string s, Action a)


{
argsActions[s] = a;
}
}

In questo esempio la raccolta ArgsAction è strettamente mappata alla raccolta sottostante. La funzione get
determina se un'opzione specifica è stata configurata. In caso affermativo, viene restituito l'oggetto Action
associato a tale opzione. In caso contrario, restituisce un oggetto Action che non esegue alcuna operazione. La
funzione di accesso public non include una funzione di accesso set . Per l'impostazione di opzioni la
progettazione usa invece un metodo public.
Mappe multidimensionali
È possibile creare indicizzatori che usano più argomenti, che, in più, non devono necessariamente essere dello
stesso tipo. Di seguito sono riportati due esempi.
Il primo esempio illustra una classe che genera valori per un set di Mandelbrot. Per altre informazioni sulle
regole matematiche alla base di questo, leggere questo articolo. L'indicizzatore usa due valori double per
definire un punto del piano X, Y. La funzione di accesso get calcola il numero di iterazioni necessarie a stabilire
che un punto non appartiene al set. Se viene raggiunto il numero massimo di iterazioni, il punto si trova nel set
e viene restituito il valore maxIterations della classe. Nelle immagini generate tramite computer comunemente
note per il set di Mandelbrot sono definiti colori per il numero di iterazioni necessarie a determinare se un punto
è esterno al set.

public class Mandelbrot


{
readonly private int maxIterations;

public Mandelbrot(int maxIterations)


{
this.maxIterations = maxIterations;
}

public int this [double x, double y]


{
get
{
var iterations = 0;
var x0 = x;
var y0 = y;

while ((x*x + y * y < 4) &&


(iterations < maxIterations))
{
var newX = x * x - y * y + x0;
y = 2 * x * y + y0;
x = newX;
iterations++;
}
return iterations;
}
}
}

Il set di Mandelbrot definisce valori in corrispondenza di ogni coordinata (x, y) per valori di numeri reali. Ciò
consente di definire un dizionario che può contenere un numero infinito di valori. Pertanto, il set non prevede
alcuna archiviazione. Questa classe calcola invece un valore per ogni punto in cui il codice chiama la funzione di
accesso get . Non viene usata alcuna archiviazione sottostante.
Verrà ora illustrato l'ultimo caso di uso degli indicizzatori, in cui l'indicizzatore riceve più argomenti di tipi
diversi. Si consideri un programma per la gestione dei dati cronologici relativi alle temperature. Questo
indicizzatore imposta o riceve la temperatura massima e la temperatura minima di una determinata posizione in
base alla città corrispondente e alla data:
using DateMeasurements =
System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>;
using CityDataMeasurements =
System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<System.DateTime,
IndexersSamples.Common.Measurements>>;

public class HistoricalWeatherData


{
readonly CityDataMeasurements storage = new CityDataMeasurements();

public Measurements this[string city, DateTime date]


{
get
{
var cityData = default(DateMeasurements);

if (!storage.TryGetValue(city, out cityData))


throw new ArgumentOutOfRangeException(nameof(city), "City not found");

// strip out any time portion:


var index = date.Date;
var measure = default(Measurements);
if (cityData.TryGetValue(index, out measure))
return measure;
throw new ArgumentOutOfRangeException(nameof(date), "Date not found");
}
set
{
var cityData = default(DateMeasurements);

if (!storage.TryGetValue(city, out cityData))


{
cityData = new DateMeasurements();
storage.Add(city, cityData);
}

// Strip out any time portion:


var index = date.Date;
cityData[index] = value;
}
}
}

Questo esempio crea un indicizzatore che esegue il mapping di dati meteo a due diversi argomenti: una città
(rappresentata da un valore string ) e una data (rappresentata da un valore DateTime ). Per l'archiviazione
interna vengono usate due classi Dictionary che rappresentano il dizionario bidimensionale. L'API public non
rappresenta più l'archiviazione sottostante. Le funzionalità del linguaggio relative agli indicizzatori consentono
di creare un'interfaccia public che rappresenta l'astrazione, anche se l'archiviazione sottostante deve usare tipi di
raccolta principale diversi.
Due parti del codice potrebbero risultare poco chiare per alcuni sviluppatori. Queste due using direttive:

using DateMeasurements = System.Collections.Generic.Dictionary<System.DateTime,


IndexersSamples.Common.Measurements>;
using CityDataMeasurements = System.Collections.Generic.Dictionary<string,
System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>>;

creano un alias per un tipo generico costruito. Queste istruzioni consentono al codice successivo di usare i nomi
DateMeasurements e CityDateMeasurements , più descrittivi, anziché la costruzione generica di
Dictionary<DateTime, Measurements> e Dictionary<string, Dictionary<DateTime, Measurements> > . Questo
costrutto richiede però l'uso di nomi completi di tipo sul lato destro del segno = .
La seconda tecnica consiste nel rimuovere le parti relative all'ora di qualsiasi oggetto DateTime usato per
effettuare l'indicizzazione all'interno delle raccolte. .NET non include un tipo di solo data. Gli sviluppatori usano il
tipo DateTime , ma usano la proprietà Date per assicurarsi che tutti gli oggetti DateTime di quel giorno siano
uguali.

Conclusioni
È necessario creare indicizzatori ogni volta che all'interno di una classe è presente un elemento analogo a una
proprietà e tale proprietà rappresenta non un singolo valore, ma una raccolta di valori in cui ogni singolo
elemento è identificato da un set di argomenti. Tali argomenti consentono di identificare in modo univoco un
elemento della raccolta a cui fare riferimento. Gli indicizzatori estendono il concetto di Proprietà, in cui un
membro viene considerato come un elemento dati dall'esterno della classe, ma come un metodo all'interno. Gli
indicizzatori consentono agli argomenti di individuare un singolo elemento all'interno di una proprietà che
rappresenta un set di elementi.
Variabili discard - Guida di C#
28/01/2021 • 12 minutes to read • Edit Online

A partire da C# 7,0, C# supporta le variabili Discard, ovvero le variabili segnaposto che sono intenzionalmente
inutilizzate nel codice dell'applicazione. Le variabili discard sono equivalenti alle variabili non assegnate e non
hanno un valore. Dato che è presente un'unica variabile discard alla quale potrebbe non essere allocata nessuna
archiviazione, le variabili discard possono ridurre le allocazioni di memoria. Dato che rendono chiaro l'obiettivo
del codice, ne migliorano la leggibilità e la gestibilità.
Per indicare che una variabile è una variabile discard le si assegna come nome il carattere di sottolineatura ( _ ).
Ad esempio, la chiamata al metodo seguente restituisce una tupla con 3 elementi in cui il primo e il secondo
valore sono discard e area è una variabile dichiarata in precedenza da impostare in base al corrispondente terzo
componente restituito da GetCityInformation:

(_, _, area) = city.GetCityInformation(cityName);

In C# 7,0 e versioni successive, le variabili Discard sono supportate nelle assegnazioni nei contesti seguenti:
Decostruzione di deconstructionTuple e oggetti.
Criteri di ricerca con is e switch.
Chiamate a metodi con parametri out .
Un _ standalone quando l'ambito non include nessun _ .
A partire da C# 9,0, è possibile usare le variabili Discard per specificare i parametri di input non usati di
un'espressione lambda. Per altre informazioni, vedere la sezione parametri di input di un'espressione lambda
dell'articolo sulle espressioni lambda .
Quando _ è una variabile discard valida, se si prova a recuperarne il valore o a usarla in un'operazione di
assegnazione viene generato l'errore di compilazione CS0301, "Il nome '_' non esiste nel contesto corrente".
L'errore si verifica perché a _ non è assegnato nessun valore e potrebbe non essere assegnata nessuna
posizione di archiviazione. Se si trattasse di una vera variabile non sarebbe possibile rimuovere più di un valore,
come nell'esempio precedente.

Decostruzione di tuple e oggetti


Le variabili discard sono particolarmente utili quando si lavora con le tuple e il codice dell'applicazione usa
alcuni elementi di una tupla ma ne ignora altri. Ad esempio il metodo QueryCityDataForYears seguente
restituisce una tupla con 6 elementi con il nome di una città, l'area della città, un anno, la popolazione della città
per tale anno, un secondo anno e la popolazione della città per tale anno. L'esempio visualizza la variazione della
popolazione tra questi due anni. Tra i dati resi disponibili dalla tupla non interessa l'area della città, mentre il
nome della città e le due date sono già noti in fase di progettazione. Di conseguenza interessano soltanto i due
valori di popolazione archiviati nella tupla, mentre gli altri valori possono essere gestiti come variabili discard.
using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");


}

private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;

if (name == "New York City")


{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}

return ("", 0, 0, 0, 0, 0);


}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149

Per altre informazioni sulla decostruzione di tuple con le variabili discard, vedere Decostruzione di tuple e altri
tipi.
Anche il metodo Deconstruct di una classe, struttura o interfaccia consente di recuperare e decostruire un set di
dati specifico da un oggetto. È possibile usare le variabili quando risulta utile lavorare solo con un subset dei
valori decostruiti. L'esempio seguente esegue la decostruzione di un oggetto Person in quattro stringhe (nome,
cognome, città e stato), ma rimuove il cognome e lo stato.
using System;

public class Person


{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }

public Person(string fname, string mname, string lname,


string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}

// Return the first and last name.


public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}

public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}

public void Deconstruct(out string fname, out string lname,


out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}

public class Example


{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

// <Snippet1>
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
// </Snippet1>
}
}
// The example displays the following output:
// Hello John Adams of Boston, MA!

Per altre informazioni sulla decostruzione di tipi definiti dall'utente con le variabili discard, vedere Decostruzione
di tuple e altri tipi.
Criteri di ricerca con switch e is
Il criterio variabile discard può essere usato nei criteri di ricerca con le parole chiave is e switch. Ogni
espressione corrisponde sempre al criterio variabile discard.
L'esempio seguente definisce un metodo ProvidesFormatInfo che usa istruzioni is per determinare se un
oggetto include un'implementazione IFormatProvider e verifica se l'oggetto null . Usa anche il criterio variabile
discard per gestire gli oggetti non null di qualsiasi altro tipo.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
object[] objects = { CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
new ArgumentException(), null };
foreach (var obj in objects)
ProvidesFormatInfo(obj);
}

private static void ProvidesFormatInfo(object obj)


{
switch (obj)
{
case IFormatProvider fmt:
Console.WriteLine($"{fmt} object");
break;
case null:
Console.Write("A null object reference: ");
Console.WriteLine("Its use could result in a NullReferenceException");
break;
case object _:
Console.WriteLine("Some object type without format information");
break;
}
}
}
// The example displays the following output:
// en-US object
// System.Globalization.DateTimeFormatInfo object
// System.Globalization.NumberFormatInfo object
// Some object type without format information
// A null object reference: Its use could result in a NullReferenceException

Chiamate a metodi con parametri out


Quando si chiama il metodo Deconstruct per la decostruzione di un tipo definito dall'utente (un'istanza di una
classe, una struttura o un'interfaccia), è possibile rimuovere i valori di singoli argomenti out . Tuttavia è
possibile rimuovere il valore degli argomenti out anche quando si chiama qualsiasi metodo con un parametro
out.
Nel seguente esempio viene chiamato il metodo DateTime.TryParse(String, out DateTime) per determinare se la
rappresentazione stringa di una data è valida con le impostazioni cultura correnti. Dato che lo scopo
dell'esempio è solo quello di convalidare la stringa di data e non quello di analizzarla per estrarre la data,
l'argomento out del metodo è una variabile discard.
using System;

public class Example


{
public static void Main()
{
string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
"2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
"5/01/2018 14:57:32.80 -07:00",
"1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
"Fri, 15 May 2018 20:10:57 GMT" };
foreach (string dateString in dateStrings)
{
if (DateTime.TryParse(dateString, out _))
Console.WriteLine($"'{dateString}': valid");
else
Console.WriteLine($"'{dateString}': invalid");
}
}
}
// The example displays output like the following:
// '05/01/2018 14:57:32.8': valid
// '2018-05-01 14:57:32.8': valid
// '2018-05-01T14:57:32.8375298-04:00': valid
// '5/01/2018': valid
// '5/01/2018 14:57:32.80 -07:00': valid
// '1 May 2018 2:57:32.8 PM': valid
// '16-05-2018 1:00:32 PM': invalid
// 'Fri, 15 May 2018 20:10:57 GMT': invalid

Una variabile discard standalone


È possibile usare una variabile discard standalone per indicare qualsiasi variabile che si è deciso di ignorare.
Nell'esempio seguente viene usata una variabile discard standalone per ignorare l'oggetto Task restituito da
un'operazione asincrona. Di conseguenza viene eliminata l'eccezione che viene attivata dall'operazione poco
prima del completamento.
using System;
using System.Threading.Tasks;

public class Example


{
public static async Task Main(string[] args)
{
await ExecuteAsyncMethods();
}

private static async Task ExecuteAsyncMethods()


{
Console.WriteLine("About to launch a task...");
_ = Task.Run(() => { var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
}
}
// The example displays output like the following:
// About to launch a task...
// Completed looping operation...
// Exiting after 5 second delay

Si noti che anche _ è un identificatore valido. Quando viene usata fuori da un contesto supportato, _ non
viene considerata come una variabile discard ma come una variabile valida. Se un identificatore con nome _ è
già incluso nell'ambito, l'uso di _ come variabile discard standalone può causare:
La modifica accidentale della variabile _ dell'ambito, alla quale viene assegnato il valore della variabile
discard prevista. Ad esempio:

private static void ShowValue(int _)


{
byte[] arr = { 0, 0, 1, 2 };
_ = BitConverter.ToInt32(arr, 0);
Console.WriteLine(_);
}
// The example displays the following output:
// 33619968

Un errore del compilatore per la violazione dell'indipendenza dai tipi. Ad esempio:

private static bool RoundTrips(int _)


{
string value = _.ToString();
int newValue = 0;
_ = Int32.TryParse(value, out newValue);
return _ == newValue;
}
// The example displays the following compiler error:
// error CS0029: Cannot implicitly convert type 'bool' to 'int'

Errore del compilatore CS0136: "Non è possibile dichiarare in questo ambito una variabile locale o un
parametro denominato "_" perché tale nome viene usato in un ambito locale di inclusione per definire
una variabile locale o un parametro". Ad esempio:
public void DoSomething(int _)
{
var _ = GetValue(); // Error: cannot declare local _ when one is already in scope
}
// The example displays the following compiler error:
// error CS0136:
// A local or parameter named '_' cannot be declared in this scope
// because that name is used in an enclosing local scope
// to define a local or parameter

Vedere anche
Decostruzione di tuple e altri tipi
is parola chiave
switch parola chiave
Generics (Guida per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

I generics introducono il concetto di parametri di tipo in .NET, che rendono possibile la progettazione di classi e
metodi che rinviano la specifica di uno o più tipi fino a quando la classe o il metodo non viene dichiarato e ne
viene creata un'istanza dal codice client. Usando, ad esempio, un parametro di tipo generico T , è possibile
scrivere una singola classe che può essere usata da un altro codice client senza sostenere il costo o il rischio di
cast di runtime o di operazioni di conversione boxing, come illustrato di seguito:

// Declare the generic class.


public class GenericList<T>
{
public void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
// Declare a list of type int.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);

// Declare a list of type string.


GenericList<string> list2 = new GenericList<string>();
list2.Add("");

// Declare a list of type ExampleClass.


GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
}
}

Classi e metodi generici combinano riusabilità, indipendenza dai tipi ed efficienza in modo che non possano
essere presenti controparti non generiche. I generics sono in genere usati con le raccolte e i metodi che operano
su di essi. Lo System.Collections.Generic spazio dei nomi contiene diverse classi di raccolta basate su generiche.
Le raccolte non generiche, ad esempio, ArrayList non sono consigliate e vengono mantenute per motivi di
compatibilità. Per altre informazioni, vedere Generics in .NET.
Naturalmente, è anche possibile creare tipi e metodi generici personalizzati per offrire le proprie soluzioni e
schemi progettuali generalizzati che sono indipendenti dai tipi ed efficienti. Nell'esempio di codice riportato di
seguito viene illustrata una classe di elenco collegato generica semplice a scopo dimostrativo. Nella maggior
parte dei casi, è consigliabile usare la List<T> classe fornita da .NET anziché crearne una personalizzata. Il
parametro di tipo T viene usato in diverse posizioni in cui un tipo concreto viene normalmente usato per
indicare il tipo di elemento archiviato nell'elenco. In particolare, viene usato nei seguenti modi:
Come tipo di un parametro del metodo nel metodo AddHead .
Come tipo restituito della proprietà Data nella classe Node annidata.
Come tipo del membro privato data nella classe annidata.
Si noti che T è disponibile per la Node classe annidata. Quando si crea un'istanza di GenericList<T> con un
tipo concreto, ad esempio GenericList<int> , ogni occorrenza di T verrà sostituita con int .
// type parameter T in angle brackets
public class GenericList<T>
{
// The nested class is also generic on T.
private class Node
{
// T used in non-generic constructor.
public Node(T t)
{
next = null;
data = t;
}

private Node next;


public Node Next
{
get { return next; }
set { next = value; }
}

// T as private member data type.


private T data;

// T as return type of property.


public T Data
{
get { return data; }
set { data = value; }
}
}

private Node head;

// constructor
public GenericList()
{
head = null;
}

// T as method parameter type:


public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}

public IEnumerator<T> GetEnumerator()


{
Node current = head;

while (current != null)


{
yield return current.Data;
current = current.Next;
}
}
}

Nell'esempio di codice riportato di seguito viene illustrato come il codice client usa la classe generica
GenericList<T> per creare un elenco di interi. Cambiando semplicemente l'argomento relativo al tipo, è
possibile modificare il codice riportato di seguito per creare elenchi di stringhe o di qualsiasi altro tipo
personalizzato:
class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();

for (int x = 0; x < 10; x++)


{
list.AddHead(x);
}

foreach (int i in list)


{
System.Console.Write(i + " ");
}
System.Console.WriteLine("\nDone");
}
}

Cenni preliminari sui generics


Usare i tipi generici per ottimizzare il riutilizzo del codice, l'indipendenza dai tipi e le prestazioni.
L'uso più comune dei generics consiste nel creare classi di raccolte.
La libreria di classi .NET contiene varie classi di raccolte generiche nello System.Collections.Generic spazio dei
nomi. Queste classi devono essere usate ogni volta che sia possibile al posto di classi come ArrayList nello
spazio dei nomi System.Collections.
È possibile creare interfacce, classi, metodi, eventi e delegati generici.
Le classi generiche possono essere limitate in modo da abilitare l'accesso ai metodi per particolari tipi di dati.
Le informazioni sui tipi usati in un tipo di dati generico possono essere ottenute usando la reflection in fase
di esecuzione.

Sezioni correlate
Parametri di tipo generico
Vincoli sui parametri di tipo
Classi generiche
Interfacce generiche
Metodi generici
Delegati generici
Differenze tra modelli C++ e generics C#
Generics e reflection
Generics in fase di esecuzione

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#.

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Tipi
<typeparam>
<typeparamref>
Generics in .NET
Iterators
02/11/2020 • 10 minutes to read • Edit Online

Quasi tutti i programmi che vengono scritti avranno la necessità di eseguire un'iterazione in una raccolta. Si
scriverà il codice che esamina ogni elemento in una raccolta.
Si creeranno anche metodi iteratori che sono metodi che producono un iteratore (ovvero un oggetto che
attraversa un contenitore, in particolare gli elenchi) per gli elementi di tale classe. Essi possono essere usati per
le azioni seguenti:
Esecuzione di un'azione su ogni elemento in una raccolta.
Enumerazione di una raccolta personalizzata.
Estensione in LINQ o in altre librerie.
Creazione di una pipeline di dati dove i dati fluiscono in modo efficace tramite i metodi iteratori.
Il linguaggio C# offre funzionalità per entrambi gli scenari. In questo articolo viene offerta una panoramica di
tali funzionalità.
Questa esercitazione prevede diversi passaggi. Dopo ogni passaggio, è possibile eseguire l'applicazione e
verificare lo stato di avanzamento. È anche possibile visualizzare o scaricare l'esempio completato per questo
argomento. Per istruzioni sul download, vedere Esempi ed esercitazioni.

Iterazione con foreach


L'enumerazione di una raccolta è semplice: la parola chiave foreach enumera una raccolta eseguendo
l'istruzione incorporata una volta per ogni elemento nella raccolta:

foreach (var item in collection)


{
Console.WriteLine(item.ToString());
}

Tutto qui. Per eseguire l'iterazione in tutto il contenuto di una raccolta, è sufficiente l'istruzione foreach .
L'istruzione foreach non è tuttavia magica. Si basa su due interfacce generiche definite nella libreria di base di
.NET per generare il codice necessario per eseguire l'iterazione di una raccolta: IEnumerable<T> e
IEnumerator<T> . Questo meccanismo è illustrato più dettagliatamente nel seguito di questo articolo.

Queste due interfacce dispongono anche di controparti non generiche: IEnumerable e IEnumerator . Le versioni
generic sono preferite per il codice moderno.

Origini di enumerazione con metodi iteratori


Un'altra eccezionale funzionalità del linguaggio C# consente di creare metodi che creano un'origine per
un'enumerazione. Essi sono denominati metodi iteratori. Un metodo iteratore definisce come generare gli
oggetti in una sequenza quando richiesto. Usare le parole chiave contestuali yield return per definire un
metodo iteratore.
È possibile scrivere questo metodo per produrre la sequenza di numeri interi da 0 a 9:
public IEnumerable<int> GetSingleDigitNumbers()
{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}

Il codice sopra riportato indica istruzioni yield return distinte per evidenziare che è possibile usare più
istruzioni yield return discrete in un metodo iteratore. È possibile (come capita spesso) usare altri costrutti di
linguaggio per semplificare il codice di un metodo iteratore. La definizione di metodo seguente produce la
stessa identica sequenza di numeri:

public IEnumerable<int> GetSingleDigitNumbers()


{
int index = 0;
while (index < 10)
yield return index++;
}

Non è necessario scegliere l'uno o l'altro. È possibile specificare qualsiasi numero di istruzioni yield return
necessarie per soddisfare le esigenze del metodo:

public IEnumerable<int> GetSingleDigitNumbers()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

index = 100;
while (index < 110)
yield return index++;
}

Questa è la sintassi di base. Si consideri un esempio reale in cui si vuole scrivere un metodo iteratore. Si
supponga di lavorare su un progetto IoT e che i sensori dispositivo generino un flusso di dati molto grande. Per
ottenere un'idea dei dati, è possibile scrivere un metodo che campioni ogni elemento dati n. Questo piccolo
metodo iteratore serve allo scopo:

public static IEnumerable<T> Sample(this IEnumerable<T> sourceSequence, int interval)


{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}

I metodi iteratori presentano un'importante restrizione: non è possibile avere sia un'istruzione return che
un'istruzione yield return nello stesso metodo. Il codice seguente non verrà compilato:

public IEnumerable<int> GetSingleDigitNumbers()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

// generates a compile time error:


var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
return items;
}

Questa restrizione non costituisce in genere un problema. È possibile scegliere di usare yield return in tutto il
metodo o di separare il metodo originale in più metodi, alcuni con l'uso di return e altri con l'uso di
yield return .

È possibile modificare leggermente l'ultimo metodo in modo da usare yield return ovunque:

public IEnumerable<int> GetSingleDigitNumbers()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
foreach (var item in items)
yield return item;
}

In alcuni casi, la risposta giusta è la suddivisione di un metodo iteratore in due metodi diversi: uno che usa
return e un altro che usa yield return . Si consideri una situazione in cui si vuole restituire una raccolta vuota
o i primi 5 numeri dispari, in base a un argomento booleano. Ciò può essere scritto con i due metodi seguenti:

public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)


{
if (getCollection == false)
return new int[0];
else
return IteratorMethod();
}

private IEnumerable<int> IteratorMethod()


{
int index = 0;
while (index < 10)
{
if (index % 2 == 1)
yield return index;
index++;
}
}

Esaminare i metodi descritti precedentemente. Il primo usa l'istruzione return standard per restituire una
raccolta vuota o l'iteratore creato dal secondo metodo. Il secondo metodo usa l'istruzione yield return per
creare la sequenza richiesta.
Approfondimento di foreach
L'istruzione foreach si espande in un termine standard che usa le interfacce IEnumerable<T> e IEnumerator<T>
per eseguire l'iterazione tra tutti gli elementi di una raccolta. Riduce al minimo gli errori commessi dagli
sviluppatori a causa di una gestione non corretta delle risorse.
Il compilatore traduce il ciclo foreach illustrato nel primo esempio in un risultato simile a questo costrutto:

IEnumerator<int> enumerator = collection.GetEnumerator();


while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}

Il costrutto sopra rappresenta il codice generato dal compilatore C# a partire dalla versione 5 e successive.
Prima della versione 5, la variabile item aveva un ambito diverso:

// C# versions 1 through 4:
IEnumerator<int> enumerator = collection.GetEnumerator();
int item = default(int);
while (enumerator.MoveNext())
{
item = enumerator.Current;
Console.WriteLine(item.ToString());
}

La modifica è stata apportata perché il comportamento precedente avrebbe potuto causare bug gravi e difficili
da diagnosticare relativi alle espressioni lambda. Per altre informazioni sulle espressioni lambda, vedere
Espressioni lambda.
Il codice esatto generato dal compilatore è leggermente più complesso e gestisce situazioni in cui l'oggetto
restituito da GetEnumerator() implementa l'interfaccia IDisposable . L'espansione completa genera codice più
simile al seguente:

{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
} finally
{
// dispose of enumerator.
}
}

Il modo in cui l'enumeratore viene eliminato dipende dalle caratteristiche del tipo di enumerator . Nel caso
generale, la clausola finally si espande a:

finally
{
(enumerator as IDisposable)?.Dispose();
}
Tuttavia, se il tipo di enumerator è un tipo sealed e non esiste alcuna conversione implicita dal tipo di
enumerator a IDisposable , la clausola finally si espande in un blocco vuoto:

finally
{
}

Se esiste una conversione implicita dal tipo di enumerator a IDisposable e enumerator è un tipo di valore non
nullable, la finally si clausola espande a:

finally
{
((IDisposable)enumerator).Dispose();
}

Fortunatamente, non è necessario ricordare tutti questi dettagli. L'istruzione foreach gestisce tutte queste
sfumature. Il compilatore genererà il codice corretto per tutti questi costrutti.
Introduzione ai delegati
18/03/2020 • 4 minutes to read • Edit Online

I delegati offrono un meccanismo di associazione tardiva in .NET. L'associazione tardiva indica la creazione di un
algoritmo in cui il chiamante specifica almeno un metodo che implementa parte dell'algoritmo.
Ad esempio, si consideri l'ordinamento di un elenco di stelle in un'applicazione di astronomia. È possibile
scegliere di ordinare le stelle in base alla distanza da terra, alla grandezza o alla luminosità percepita.
In tutti i casi, il metodo Sort() esegue essenzialmente la stessa operazione, ovvero ordina gli elementi nell'elenco
in base a un confronto. Il codice che esegue il confronto di due stelle è diverso per ognuno degli ordinamenti.
Questi tipi di soluzioni sono stati usati nel software per mezzo secolo. Il concetto di delegato del linguaggio C#
offre un supporto del linguaggio di prima classe e indipendenza dai tipi.
Come descritto più avanti, il codice C# scritto per questo tipo di algoritmi è indipendente dai tipi e usa il
linguaggio e il compilatore per verificare che i tipi corrispondano per gli argomenti e i tipi restituiti.

Obiettivi della progettazione del linguaggio per i delegati


I designer del linguaggio hanno enumerato diversi obiettivi per la funzionalità che in seguito è diventata la
funzionalità dei delegati.
Il team voleva un costrutto di linguaggio comune che potesse essere usato per i successivi algoritmi di
associazione tardiva. Ciò consente agli sviluppatori di apprendere un solo concetto e di usare lo stesso concetto
in diversi problemi software.
In secondo luogo, il team voleva supportare le chiamate al metodo sia singole che multicast. I delegati multicast
sono i delegati che concatenano più chiamate al metodo. Alcuni esempi sono disponibili più avanti in questa
serie.
Il team voleva che i delegati supportassero la stessa indipendenza dai tipi stesso che gli sviluppatori si aspettano
da tutti i costrutti C#.
Infine il team ha riconosciuto che un modello di evento è un modello specifico, in cui i delegati o qualsiasi
algoritmo di associazione tardiva risultano molto utili. Il team voleva assicurarsi che il codice per i delegati
potesse offrire la base per il modello di evento di .NET.
Il risultato di tutto il lavoro sono stati il delegato e il supporto degli eventi di C# e .NET. Gli articoli rimanenti di
questa sezione descrivono le funzionalità del linguaggio, il supporto delle librerie e i termini comuni usati per i
delegati.
Verranno fornite informazioni sulla parola chiave delegate e sul codice generato dalla parola chiave. Verranno
fornite informazioni sulle funzionalità nella classe System.Delegate e sul loro utilizzo. Si apprenderà come
creare delegati indipendenti dai tipi e come creare metodi che possono essere richiamati tramite i delegati. Si
apprenderà anche come usare i delegati e gli eventi tramite le espressioni lambda. Sarà possibile osservare il
punto nel quale i delegati diventano uno dei blocchi predefiniti per LINQ. Si apprenderà come delegati
costituiscono la base per lo schema di eventi .NET e come sono diversi.
In generale, sarà possibile osservare in che modo i delegati sono parte integrante della programmazione in .NET
e dell'uso con le API del framework.
È ora possibile iniziare.
Avanti
System.Delegate e la parola chiave delegate
02/11/2020 • 12 minutes to read • Edit Online

Indietro
In questo articolo vengono illustrate le classi di .NET che supportano i delegati e il modo in cui vengono mappati
alla delegate parola chiave.

Definire i tipi di delegati


Iniziamo con la parola chiave "delegate" che è essenzialmente quello che si userà per lavorare con i delegati. Il
codice che il compilatore genera quando si usa la parola chiave delegate eseguirà il mapping alle chiamate ai
metodi che richiamano i membri delle classi Delegate e MulticastDelegate.
Per definire un tipo delegato si usa una sintassi simile alla definizione di una firma di metodo. È sufficiente
aggiungere la parola chiave delegate alla definizione.
Si continuerà a usare il metodo List.Sort() come esempio. Il primo passaggio consiste nel creare un tipo per il
delegato di confronto:

// From the .NET Core library

// Define the delegate type:


public delegate int Comparison<in T>(T left, T right);

Il compilatore genera una classe, derivata da System.Delegate che corrisponde alla firma usata (in questo caso,
un metodo che restituisce un valore integer e ha due argomenti). Il tipo di quel delegato è Comparison . Il tipo
delegato Comparison è un tipo generico. Per informazioni dettagliate sui generics, vedere qui.
Si noti che può sembrare che la sintassi dichiari una variabile, ma in effetti viene dichiarato un tipo. È possibile
definire tipi delegato all'interno di classi, direttamente all'interno di spazi dei nomi o anche nello spazio dei nomi
globale.

NOTE
La dichiarazione di tipi delegato (o altri tipi) direttamente nello spazio dei nomi globale non è consigliata.

Il compilatore genera anche gestori di aggiunta e rimozione per questo nuovo tipo in modo che i client di questa
classe siano in grado di aggiungere e rimuovere metodi dall'elenco delle chiamate di un'istanza. Il compilatore
impone che la firma del metodo aggiunto o rimosso corrisponda alla firma usata per la dichiarazione del
metodo.

Dichiarare istanze di delegati


Dopo aver definito il delegato è possibile creare un'istanza di quel tipo. Come per tutte le variabili in C#, non è
possibile dichiarare le istanze di delegato direttamente in uno spazio dei nomi o nello spazio dei nomi globale.
// inside a class definition:

// Declare an instance of that type:


public Comparison<T> comparator;

Il tipo della variabile è Comparison<T> , il tipo delegato definito in precedenza. Il nome della variabile è
comparator .

Il frammento di codice riportato sopra dichiara una variabile membro all'interno di una classe. È anche possibile
dichiarare variabili delegato che sono variabili locali o argomenti per i metodi.

Richiama delegati
Per richiamare i metodi inclusi nell'elenco chiamate di un delegato, chiamare quel delegato. All'interno del
metodo Sort() il codice chiamerà il metodo di confronto per determinare l'ordine in cui inserire gli oggetti:

int result = comparator(left, right);

Nella riga precedente il codice richiama il metodo associato al delegato. La variabile viene trattata come nome di
metodo e per richiamarla viene usata usando la sintassi di chiamata di metodo normale.
Quella riga di codice presuppone che non esista alcuna garanzia che una destinazione sia stata aggiunta al
delegato. Se non sono state associate destinazioni, la riga precedente causerebbe la generazione di
NullReferenceException . Gli idiomi usati per risolvere questo problema sono più complessi rispetto a un
semplice controllo null e sono trattati più avanti in questa serie.

Assegnare, aggiungere e rimuovere destinazioni di chiamata


Questo è il modo in cui viene definito un tipo delegato e in cui le istanze dei delegati vengono dichiarate e
richiamate.
Gli sviluppatori che vogliono usare il metodo List.Sort() devono definire un metodo la cui firma corrisponda
alla definizione di tipo delegato e assegnare il metodo al delegato usato dal metodo di ordinamento. Questa
assegnazione consente di aggiungere il metodo all'elenco delle chiamate dell'oggetto delegato.
Si supponga di voler ordinare un elenco di stringhe in base alla lunghezza. La funzione di confronto potrebbe
essere la seguente:

private static int CompareLength(string left, string right) =>


left.Length.CompareTo(right.Length);

Il metodo viene dichiarato come metodo privato. Va bene. Può essere opportuno evitare che questo metodo sia
parte dell'interfaccia pubblica. Può comunque essere usato come metodo di confronto se collegato a un
delegato. Il codice chiamante avrà questo metodo associato all'elenco di destinazione dell'oggetto delegato e
potrà accedere usando quel delegato.
La relazione si crea passando tale metodo al metodo List.Sort() :

phrases.Sort(CompareLength);

Si noti che viene usato il nome del metodo, senza parentesi. L'uso del metodo come argomento indica al
compilatore di convertire il riferimento al metodo in un riferimento che possa essere usato come destinazione
della chiamata al delegato e di associare tale metodo come destinazione di chiamata.
Si può anche procedere in modo esplicito dichiarando una variabile di tipo Comparison<string> ed eseguendo
un'assegnazione:

Comparison<string> comparer = CompareLength;


phrases.Sort(comparer);

Quando il metodo usato come destinazione di delegato è un metodo piccolo spesso si usa la sintassi
dell'espressione lambda per eseguire l'assegnazione:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);


phrases.Sort(comparer);

L'uso di espressioni lambda per le destinazioni dei delegati è più approfondito in una sezione successiva.
L'esempio Sort() di solito associa un singolo metodo di destinazione al delegato. Tuttavia, gli oggetti delegati
supportano gli elenchi delle chiamate con più metodi di destinazione associati a un oggetto delegato.

Classi Delegate e MulticastDelegate


Il supporto del linguaggio descritto in precedenza offre le funzionalità e il supporto in genere necessari quando
si lavora con i delegati. Tali funzionalità si basano su due classi di .NET Core Framework: Delegate e
MulticastDelegate.
La System.Delegate classe e la relativa singola sottoclasse diretta System.MulticastDelegate forniscono il
supporto del Framework per la creazione di delegati, la registrazione di metodi come destinazioni delegate e la
chiamata di tutti i metodi registrati come destinazione del delegato.
È interessante notare che le classi System.Delegate e System.MulticastDelegate non sono tipi di delegati.
Offrono la base per tutti i tipi di delegati specifici. Lo stesso processo di progettazione del linguaggio stabilisce
che non è possibile dichiarare una classe che derivi da Delegate o MulticastDelegate . Le regole del linguaggio
C# lo proibiscono.
Al contrario, il compilatore C# crea istanze di una classe derivata da MulticastDelegate quando si usa la parola
chiave del linguaggio C# per dichiarare i tipi di delegati.
Questa progettazione ha le sue radici nella prima versione di C# e .NET. Uno degli obiettivi per il team di
progettazione era assicurarsi che il linguaggio applicasse l'indipendenza dai tipi nell'uso dei delegati. Ciò
significava garantire che i delegati venissero richiamati con il tipo e il numero di argomenti corretti. E che ogni
tipo restituito fosse correttamente indicato in fase di compilazione. I delegati facevano parte della versione 1.0 di
.NET, precedente ai generics.
Il modo migliore di applicare l'indipendenza dai tipi per il compilatore era creare le classi delegate concrete che
rappresentavano la firma del metodo in uso.
Anche se non è possibile creare direttamente le classi derivate, vengono usati i metodi definiti per queste classi.
Vediamo quali sono i metodi più comuni da usare quando si lavora con i delegati.
Il primo è più importante aspetto da ricordare è che ogni delegato con cui si lavora è derivato da
MulticastDelegate . Un delegato multicast significa che si possono richiamare più destinazioni di metodo
quando la chiamata è effettuata attraverso un delegato. Nella progettazione originale si è ritenuto utile
distinguere i delegati in cui era possibile associare e richiamare un solo metodo di destinazione dai delegati in
cui era possibile associare e richiamare più metodi di destinazione. Tale distinzione si è rivelata in pratica meno
utile del previsto. Le due classi differenti erano già state create e sono rimaste nel framework dalla versione
pubblica iniziale.
I metodi che si usano più di frequente con i delegati sono Invoke() e BeginInvoke() / EndInvoke() . Invoke()
richiamerà tutti i metodi che sono stati associati a un'istanza particolare del delegato. Come osservato in
precedenza, in genere i delegati vengono richiamati usando la sintassi di chiamata di metodo per la variabile
delegato. Come si vedrà più avanti in questa serie, sono disponibili modelli che funzionano direttamente con
questi metodi.
Ora che è stata esaminata la sintassi del linguaggio e le classi che supportano i delegati, esaminiamo il modo in
cui vengono usati, creati e richiamati i delegati fortemente tipizzati.
Avanti
Delegati fortemente tipizzati
18/03/2020 • 5 minutes to read • Edit Online

Indietro
Nell'articolo precedente è stata descritta la creazione di tipi di delegato specifici tramite la parola chiave
delegate .

La classe Delegate astratta offre l'infrastruttura per l'accoppiamento libero e la chiamata. I tipi delegati concreti
diventano più utili includendo e imponendo l'indipendenza dai tipi per i metodi aggiunti all'elenco di chiamate
per un oggetto delegato. Quando si usa la parola chiave delegate e si definisce un tipo delegato concreto, il
compilatore genera tali metodi.
In pratica, verranno creati nuovi tipi delegati ogni volta che è necessaria una firma del metodo diversa. Questa
operazione potrebbe risultare tediosa dopo un periodo di tempo. Ogni nuova funzionalità richiede nuovi tipi
delegati.
Fortunatamente, questo non è necessario. Il framework .NET Core include diversi tipi che è possibile riutilizzare
quando sono necessari tipi delegati. Poiché si tratta di definizioni generiche è possibile dichiarare
personalizzazioni quando sono necessarie nuove dichiarazioni di metodi.
Il primo di questi tipi è il tipo Action e diverse variazioni:

public delegate void Action();


public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// Other variations removed for brevity.

Il modificatore in nell'argomento di tipo generico è descritto nell'articolo sulla covarianza.


Sono disponibili variazioni del delegato Action che contengono fino a 16 argomenti, ad esempio
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>. È importante che queste definizioni usino
argomenti generici diversi per ogni argomento di delegato in modo da offrire la massima flessibilità. Gli
argomenti del metodo possono essere dello stesso tipo, ma non devono esserlo necessariamente.
Usare uno dei tipi Action per ogni tipo delegato con tipo restituito void.
Il framework include anche diversi tipi delegati generici che è possibile usare per i tipi delegati che restituiscono
valori:

public delegate TResult Func<out TResult>();


public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// Other variations removed for brevity

Il modificatore out nell'argomento di tipo generico del risultato è descritto nell'articolo sulla covarianza.
Sono disponibili variazioni del delegato Func che contengono fino a 16 argomenti di input, ad esempio
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>. Per convenzione, il tipo del risultato è
sempre l'ultimo parametro di tipo in tutte le dichiarazioni Func .
Usare uno dei tipi Func per ogni tipo delegato che restituisce un valore.
C'è anche un specializzatoPredicate<T> tipo per un delegato che restituisce un test su un singolo valore:

public delegate bool Predicate<in T>(T obj);

È possibile notare che per ogni tipo Predicate , esiste un tipo Func strutturalmente equivalente, ad esempio:

Func<string, bool> TestForString;


Predicate<string> AnotherTestForString;

Si potrebbe pensare che questi due tipi siano equivalenti. Ma non lo sono. Queste due variabili non possono
essere usate indifferentemente. A una variabile di un tipo non è possibile assegnare un altro tipo. Il sistema dei
tipi di C# usa i nomi dei tipi definiti, non la struttura.
Tutte queste definizioni di tipi delegati nella libreria .NET Core dovrebbero eliminare la necessità di definire un
nuovo tipo delegato per ogni nuova funzionalità creata che richiede delegati. Queste definizioni generiche
dovrebbero offrire tutti i tipi delegati necessari nella maggior parte delle situazioni. È possibile creare
semplicemente un'istanza di uno di questi tipi con i parametri di tipo richiesti. Nel caso di algoritmi che possono
essere definiti come generici, questi delegati possono essere usati come tipi generici.
Ciò dovrebbe consentire di risparmiare tempo e di ridurre il numero di nuovi tipi da creare per usare i delegati.
Nel articolo successivo sono descritti diversi modelli comuni per l'uso pratico dei delegati.
Avanti
Modelli comuni per i delegati
02/11/2020 • 15 minutes to read • Edit Online

Indietro
I delegati offrono un meccanismo che consente progettazioni software che comportano un accoppiamento
minimo tra i componenti.
Un esempio eccellente di questo tipo di progettazione è LINQ. Tutte le funzionalità del modello di espressione di
query LINQ sono basate sui delegati. Considerare il semplice esempio seguente:

var smallNumbers = numbers.Where(n => n < 10);

La sequenza di numeri viene filtrata mantenendo solo i numeri inferiori al valore 10. Il metodo Where usa un
delegato che determina quali elementi di un filtro passano il filtro. Quando si crea una query LINQ, si specifica
l'implementazione del delegato per questo scopo specifico.
Il prototipo del metodo Where è:

public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource, bool>


predicate);

Questo esempio viene ripetuto con tutti i metodi che fanno parte di LINQ. Tutti i metodi si basano su delegati
per il codice che gestisce la query specifica. Lo schema progettuale di questa API è molto utile per apprendere e
comprendere.
Questo semplice esempio illustrato come i delegati richiedono pochissimo accoppiamento tra i componenti.
Non è necessario creare una classe che deriva da una determinata classe base. Non è necessario implementare
un'interfaccia specifica. L'unico requisito è fornire l'implementazione di un metodo che è fondamentale per
l'attività in questione.

Creazione di componenti personalizzati con i delegati


Continuando con lo stesso esempio si crea un componente usando una progettazione basata sui delegati.
Definire un componente che può essere usato per i messaggi di registro in un sistema di grandi dimensioni. I
componenti di libreria possono essere usati in molti ambienti diversi, su più piattaforme. Sono disponibili molte
funzionalità comuni nel componente che gestisce i registri. È necessario che vengano accettati i messaggi da
qualsiasi componente nel sistema. I messaggi avranno priorità diverse che possono essere gestite dal
componente principale. I messaggi devono avere timestamp nel formato archiviato finale. Per gli scenari più
avanzati, è possibile filtrare i messaggi in base al componente di origine.
La posizione in cui vengono scritti i messaggi è uno degli aspetti della funzionalità che verrà modificato spesso.
In alcuni ambienti possono essere scritti nella console degli errori. In altri casi, in un file. Le altre possibilità
includono l'archiviazione database, i log eventi del sistema operativo o un'altra archiviazione di documenti.
Esistono anche combinazioni di output che possono essere usate in scenari diversi. È possibile scrivere i
messaggi nella console e in un file.
Una progettazione basata sui delegati offre molta flessibilità e semplifica il supporto di meccanismi di
archiviazione che possono essere aggiunti in futuro.
In questa progettazione, il componente del log primario può essere una classe non virtuale e persino sealed. È
possibile collegare qualsiasi set di delegati per scrivere i messaggi in diversi supporti di archiviazione. Il
supporto incorporato per i delegati multicast semplifica il supporto di scenari in cui i messaggi devono essere
scritti in più posizioni (un file e una console).

Prima implementazione
Per iniziare in modo semplice, l'implementazione iniziale accetterà i nuovi messaggi e li scriverà usando
qualsiasi delegato associato. È possibile iniziare con un solo delegato che scrive i messaggi nella console.

public static class Logger


{
public static Action<string> WriteMessage;

public static void LogMessage(string msg)


{
WriteMessage(msg);
}
}

La classe statica precedente è l'elemento più semplice in grado di funzionare. È necessario scrivere la singola
implementazione per il metodo che scrive i messaggi nella console:

public static class LoggingMethods


{
public static void LogToConsole(string message)
{
Console.Error.WriteLine(message);
}
}

Infine, è necessario collegare il delegato associandolo al delegato WriteMessage dichiarato nel logger:

Logger.WriteMessage += LoggingMethods.LogToConsole;

Procedure consigliate
L'esempio è abbastanza semplice ma illustra alcune importanti linee guida per le progettazioni che usano i
delegati.
L'uso dei tipi delegati definiti nel framework principale semplifica l'uso dei delegati da parte degli utenti. Non è
necessario definire nuovi tipi e gli sviluppatori che usano la libreria non devono conoscere tipi delegati nuovi e
specializzati.
Le interfacce usate sono essenziali e offrono la massima flessibilità: per creare un nuovo logger di output è
necessario creare un solo metodo. Il metodo creato può essere statico o di istanza e avere qualsiasi accesso.

Formattazione dell'output
Creare una prima versione più affidabile e altri meccanismi di registrazione.
Successivamente, aggiungere alcuni argomenti al metodo LogMessage() in modo che la classe del log crei
messaggi più strutturati:
public enum Severity
{
Verbose,
Trace,
Information,
Warning,
Error,
Critical
}

public static class Logger


{
public static Action<string> WriteMessage;

public static void LogMessage(Severity s, string component, string msg)


{
var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
WriteMessage(outputMsg);
}
}

Usare quindi l'argomento Severity per filtrare i messaggi inviati all'output del log.

public static class Logger


{
public static Action<string> WriteMessage;

public static Severity LogLevel {get;set;} = Severity.Warning;

public static void LogMessage(Severity s, string component, string msg)


{
if (s < LogLevel)
return;

var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";


WriteMessage(outputMsg);
}
}

Procedure consigliate
Sono state aggiunte nuove funzionalità all'infrastruttura di registrazione. Poiché il componente di logger è
accoppiato molto genericamente a qualsiasi meccanismo di output, è possibile aggiungere queste nuove
funzionalità senza alcun impatto sul codice che implementa il delegato del logger.
Durante la compilazione, si noteranno ulteriori esempi di come questo accoppiamento generico offra una
maggiore flessibilità per l'aggiornamento di parti del sito senza modifiche in altre posizioni. In un'applicazione
di dimensioni maggiori, infatti, le classi di output del logger potrebbero trovarsi in un assembly diverso e non
richiedere alcuna ricompilazione.

Creazione di un secondo modulo di output


Il componente di log è stato migliorato. Aggiungere un modulo di output che registra i messaggi in un file.
Questo modulo di output sarà leggermente più complesso. Sarà costituito da una classe che incapsula le
operazioni di file e garantisce che il file venga sempre chiuso dopo ogni scrittura. Questo garantisce che tutti i
dati vengano scaricati su disco dopo la generazione di ogni messaggio.
Il logger basato su file è il seguente:
public class FileLogger
{
private readonly string logPath;
public FileLogger(string path)
{
logPath = path;
Logger.WriteMessage += LogMessage;
}

public void DetachLog() => Logger.WriteMessage -= LogMessage;


// make sure this can't throw.
private void LogMessage(string msg)
{
try
{
using (var log = File.AppendText(logPath))
{
log.WriteLine(msg);
log.Flush();
}
}
catch (Exception)
{
// Hmm. We caught an exception while
// logging. We can't really log the
// problem (since it's the log that's failing).
// So, while normally, catching an exception
// and doing nothing isn't wise, it's really the
// only reasonable option here.
}
}
}

Dopo aver creato questa classe, è possibile crearne un'istanza che associa il relativo metodo LogMessage al
componente Logger:

var file = new FileLogger("log.txt");

Le due operazioni non si escludono a vicenda. È possibile associare entrambi i metodi di log e generare
messaggi nella console e in un file:

var fileOutput = new FileLogger("log.txt");


Logger.WriteMessage += LoggingMethods.LogToConsole; // LoggingMethods is the static class we utilized
earlier

Successivamente, anche nella stessa applicazione, è possibile rimuovere uno dei delegati senza causare
problemi nel sistema:

Logger.WriteMessage -= LoggingMethods.LogToConsole;

Procedure consigliate
A questo punto, è stato aggiunto un secondo gestore di output per il sottosistema di registrazione. Questo
gestore richiede maggiore infrastruttura per supportare correttamente il file system. Il delegato è un metodo di
istanza ed è anche un metodo privato. Non è necessaria una maggiore accessibilità poiché l'infrastruttura dei
delegato è in grado di connettere i delegati.
Inoltre, la progettazione basata sui delegati offre più metodi di output senza codice aggiuntivo. Non è necessario
compilare un'infrastruttura aggiuntiva per supportare più metodi di output. I metodo vanno semplicemente ad
aggiungersi all'elenco chiamate.
Prestare particolare attenzione al codice nel metodo di output di registrazione file. Viene codificato per
assicurarsi che non venga generata alcuna eccezione. Sebbene questa operazione non sia sempre strettamente
necessaria, è spesso consigliabile. Se uno dei metodi delegati genera un'eccezione, i delegati rimanenti non
verranno chiamati.
Infine tenere presente che il logger di file deve gestire le risorse aprendo e chiudendo il file per ogni messaggio
di log. È possibile scegliere di mantenere aperto il file e implementare IDisposable per chiudere il file al termine.
Entrambe le opzioni presentano vantaggi e svantaggi. Entrambe creano maggiore accoppiamento tra le classi.
Nessun codice nella classe Logger dovrà essere aggiornato per supportare gli scenari.

Gestione dei delegati Null


Aggiornare infine il metodo LogMessage in modo che risulti affidabile per i casi in cui non viene selezionato
alcun meccanismo di output. L'implementazione corrente genererà NullReferenceException se non è stato
associato alcun elenco chiamate al delegato WriteMessage . È possibile che si preferisca una progettazione che
continua automaticamente quando non sono stati associati metodi. Questa operazione risulta semplice usando
l'operatore condizionale Null insieme al metodo Delegate.Invoke() :

public static void LogMessage(string msg)


{
WriteMessage?.Invoke(msg);
}

L'operatore condizionale Null ( ?. ) provoca un corto circuito quando l'operando sinistro (in questo caso
WriteMessage ) è Null, ovvero non viene eseguito alcun tentativo di registrare un messaggio.

Il metodo Invoke() non apparirà nella documentazione per System.Delegate o System.MulticastDelegate . Il


compilatore genera un metodo Invoke indipendente dai tipi per tutti i tipi delegati dichiarati. In questo esempio
ciò significa che Invoke accetta un singolo argomento string e ha un tipo restituito void.

Riepilogo delle procedure consigliate


Sono state descritte le prime fasi di un componente di log che può essere espanso con altri writer e altre
funzionalità. Se vengono usati i delegati nella progettazione, questi componenti diversi risultano accoppiati
molto genericamente. Quest'aspetto offre numerosi vantaggi. È molto semplice creare nuovi meccanismi di
output e associarli al sistema. Questi meccanismi richiedono un solo metodo, ovvero il metodo che scrive il
messaggio di log. Si tratta di una progettazione che risulta molto flessibile quando vengono aggiunte nuove
funzionalità. Al writer viene richiesto di implementare un solo metodo. Il metodo può essere statico o di istanza.
Può avere un accesso pubblico, privato o un altro tipo di accesso valido.
La classe Logger può apportare qualsiasi numero di miglioramenti o modifiche senza causare modifiche
sostanziali. Come qualsiasi altra classe, non è possibile modificare l'API pubblica senza il rischio di modifiche di
rilievo. Tuttavia, poiché l'accoppiamento tra il logger e i moduli di output avviene solo tramite il delegato, non
vengono usati altri tipi, ad esempio interfacce o classi base. L'accoppiamento è ridotto al minimo.
Avanti
Introduzione agli eventi
02/11/2020 • 6 minutes to read • Edit Online

Indietro
Analogamente ai delegati, gli eventi sono un meccanismo di associazione tardiva. In effetti, gli eventi hanno alla
base il supporto del linguaggio per i delegati.
Gli eventi rappresentano il modo in cui un oggetto comunica a tutti i componenti del sistema interessati che è
avvenuto qualcosa. Qualsiasi altro componente può sottoscrivere l'evento e ricevere una notifica quando tale
evento viene generato.
Probabilmente in altri programmi creati in passato gli eventi sono già stati usati. Molti sistemi grafici
dispongono di un modello di eventi per segnalare l'interazione dell'utente. Questi eventi segnalano il
movimento del mouse, la pressione di pulsanti e interazioni analoghe. Questo è uno degli scenari più comuni di
uso degli eventi, ma sicuramente non l'unico.
È possibile definire gli eventi che devono essere generati per le classi. Una considerazione importante per l'uso
degli eventi è che per un evento specifico potrebbe non essere registrato alcun oggetto. È necessario scrivere il
codice in modo che vengano generati eventi solo se è configurato un listener.
La sottoscrizione di un evento crea anche un accoppiamento tra due oggetti, l'origine evento e il sink di evento.
È necessario assicurarsi che il sink di evento annulli la sottoscrizione dall'origine evento quando non è più
interessato agli eventi.

Obiettivi di progettazione per il supporto degli eventi


La progettazione del linguaggio per gli eventi è destinata a questi obiettivi:
Abilita l'accoppiamento minimo tra un'origine evento e un sink di evento. È possibile che questi due
componenti non siano stati scritti dalla stessa organizzazione. È persino possibile che l'aggiornamento di
questi componenti avvenga secondo pianificazioni completamente diverse.
Dovrebbe essere molto semplice sottoscrivere un evento e annullare la sottoscrizione dello stesso evento.
Le origini eventi devono supportare più sottoscrittori di eventi. nonché la completa mancanza di
sottoscrittori associati.
Come si può notare, gli obiettivi per gli eventi sono molto simili agli obiettivi per i delegati. Ecco perché il
supporto del linguaggio per gli eventi si basa sul supporto del linguaggio per i delegati.

Supporto delle lingue per gli eventi


La sintassi per la definizione di eventi e la sottoscrizione o l'annullamento della sottoscrizione di eventi è
un'estensione della sintassi per i delegati.
Per definire un evento si usa la parola chiave event :

public event EventHandler<FileListArgs> Progress;

Il tipo di evento ( EventHandler<FileListArgs> in questo esempio) deve essere un tipo di delegato. Quando si
dichiara un evento, è necessario seguire un certo numero di convenzioni. In genere, il tipo di delegato di un
evento restituisce void. Le dichiarazioni di eventi devono essere un verbo o una frase verbale. Usare il tempo
passato quando l'evento segnala qualcosa che si è verificato. Usare il presente (ad esempio, Closing ) per
segnalare qualcosa che sta per verificarsi. L'uso del presente indica spesso che la classe supporta un
determinato tipo di comportamento di personalizzazione. Uno degli scenari più comuni riguarda il supporto
dell'annullamento. Un evento Closing , ad esempio, può includere un argomento che indica se l'operazione di
chiusura deve continuare o meno. Altri scenari possono consentire ai chiamanti di modificare il comportamento
tramite l'aggiornamento delle proprietà degli argomenti dell'evento. È possibile generare un evento per
proporre l'azione successiva che un algoritmo deve eseguire. Il gestore eventi può imporre un'azione diversa
modificando le proprietà dell'argomento dell'evento.
Quando si vuole generare l'evento, è possibile chiamare i gestori eventi tramite la sintassi di chiamata dei
delegati:

Progress?.Invoke(this, new FileListArgs(file));

Come descritto nella sezione sui delegati, l'operatore ?. assicura in modo semplice che non venga generato un
evento se questo non ha sottoscrittori.
Per sottoscrivere un evento si usa l'operatore += :

EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>


Console.WriteLine(eventArgs.FoundFile);

fileLister.Progress += onProgress;

Il metodo del gestore ha in genere il prefisso ' on ' seguito dal nome dell'evento, come illustrato in precedenza.
Per annullare la sottoscrizione si usa l'operatore -= :

fileLister.Progress -= onProgress;

È importante dichiarare una variabile locale per l'espressione che rappresenta il gestore eventi. Questo
garantisce che l'annullamento della sottoscrizione rimuova il gestore. Se invece si usa il corpo dell'espressione
lambda, si tenta di rimuovere un gestore che non è mai stato associato e non viene quindi eseguita alcuna
operazione.
Il prossimo articolo offre altre informazioni sui criteri tipici degli eventi, oltre a diverse varianti di questo
esempio.
Avanti
Schemi di eventi .NET standard
18/03/2020 • 15 minutes to read • Edit Online

Indietro
Gli eventi .NET seguono in genere alcuni schemi noti. Questi schemi standard facilitano il lavoro agli sviluppatori
in quanto possono essere applicati a qualsiasi programma che usa eventi .NET.
Esamineremo questi schemi standard presentando tutte le informazioni necessarie per creare origini evento
standard e per sottoscrivere ed elaborare gli eventi standard nel codice.

Firme di delegati di eventi


La firma standard per un delegato di evento .NET è:

void OnEventRaised(object sender, EventArgs args);

Il tipo restituito è void. Gli eventi sono basati su delegati e sono delegati multicast. Sono supportati più
sottoscrittori per ogni origine evento. Il singolo valore restituito da un metodo non viene ridimensionato per più
sottoscrittori di eventi. Qual è il valore restituito che l'origine evento vede dopo avere generato un evento? Più
avanti in questo articolo si vedrà come creare protocolli di evento che supportano sottoscrittori di eventi che
segnalano informazioni all'origine evento.
Gli argomenti sono due: mittente e argomenti evento. Il tipo di sender della fase di compilazione è
System.Object , anche se si conosce probabilmente un tipo più derivato che va sempre bene. Per convenzione si
usa object .
Il secondo argomento era in genere un tipo derivato da System.EventArgs . Nella sezione successiva non verrà
più applicata questa convenzione. Se il tipo di evento non richiede argomenti aggiuntivi, verranno comunque
forniti entrambi gli argomenti. È previsto un valore speciale, EventArgs.Empty , che si deve usare per indicare che
l'evento non contiene informazioni aggiuntive.
Creare ora una classe in cui sono elencati i file di una directory o di una delle sue sottodirectory che seguono
uno schema. Questo componente genera un evento per ogni file individuato che corrisponde allo schema.
L'uso di un modello di eventi offre alcuni vantaggi di progettazione. È possibile creare più listener di evento che
eseguono azioni diverse quando viene trovato uno dei file cercati. Combinando i diversi listener è possibile
creare algoritmi più affidabili.
Ecco la dichiarazione iniziale di argomenti evento per trovare un file:

public class FileFoundArgs : EventArgs


{
public string FoundFile { get; }

public FileFoundArgs(string fileName)


{
FoundFile = fileName;
}
}

Anche se questo sembra un tipo di piccole dimensioni e di soli dati, si deve seguire la convenzione e renderlo un
tipo riferimento ( class ). Questo significa che l'oggetto argomento sarà passato per riferimento e gli eventuali
aggiornamenti ai dati saranno visti da tutti i sottoscrittori. La prima versione è un oggetto non modificabile. È
preferibile rendere non modificabili le proprietà nel tipo argomenti evento. In questo modo un sottoscrittore
non può modificare i valori prima che un altro sottoscrittore li veda. Esistono però alcune eccezioni, come si
vedrà di seguito.
Ora è necessario creare la dichiarazione di evento nella classe FileSearcher. Sfruttare il tipo EventHandler<T>
significa non dover creare un'altra definizione di tipo. Si usa semplicemente una specializzazione generica.
Si compila la classe FileSearcher per cercare i file che corrispondono a uno schema e si genera l'evento corretto
quando viene individuata una corrispondenza.

public class FileSearcher


{
public event EventHandler<FileFoundArgs> FileFound;

public void Search(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
FileFound?.Invoke(this, new FileFoundArgs(file));
}
}
}

Definizione e generazione di eventi campo


Il modo più semplice per aggiungere un evento alla classe consiste nel dichiarare l'evento come campo
pubblico, come illustrato nell'esempio precedente:

public event EventHandler<FileFoundArgs> FileFound;

In questo esempio sembra che venga dichiarato un campo pubblico, una prassi di programmazione orientata a
oggetti non corretta. Si desidera proteggere l'accesso ai dati tramite le proprietà o i metodi. Nonostante questa
possa sembrare una prassi non corretta, il codice generato dal compilatore crea wrapper che rendono gli oggetti
evento accessibili solo in modalità provvisoria. Le uniche operazioni disponibili in un evento simile a un campo
sono aggiungere gestori:

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>


{
Console.WriteLine(eventArgs.FoundFile);
filesFound++;
};

fileLister.FileFound += onFileFound;

e rimuovere gestori:

fileLister.FileFound -= onFileFound;

Si noti che esiste una variabile locale per il gestore. Se si usasse il corpo della funzione lambda, il metodo
remove non funzionerebbe correttamente. Sarebbe un'altra istanza del delegato e tacitamente non farebbe
nulla.
Il codice all'esterno della classe non può generare l'evento né eseguire altre operazioni.
Restituzione di valori da sottoscrittori di evento
La versione semplice funziona correttamente. Si aggiungerà un'altra funzionalità: l'annullamento.
Quando si genera l'evento found, i listener devono essere in grado di arrestare l'ulteriore elaborazione se
questo file è l'ultimo che è stato cercato.
I gestori di eventi non restituiscono un valore, pertanto è necessario comunicarlo in un altro modo. Lo schema
di evento standard usa l'oggetto EventArgs per includere i campi che i sottoscrittori di evento possono usare per
comunicare l'annullamento.
Esistono due schemi diversi che potrebbero essere usati, in base alla semantica del contratto di annullamento. In
entrambi i casi si aggiungerà un campo booleano a EventArguments per l'evento found file.
Uno degli schemi consente a qualsiasi sottoscrittore di annullare l'operazione. Per questo schema il nuovo
campo viene inizializzato su false . Qualsiasi sottoscrittore può cambiarlo in true . Dopo che tutti i
sottoscrittori hanno visto l'evento generato, il componente FileSearcher esamina il valore booleano e interviene.
Il secondo schema annullerebbe l'operazione solo se tutti i sottoscrittori volessero annullare l'operazione. In
questo schema viene inizializzato il nuovo campo per indicare che l'operazione deve essere annullata e qualsiasi
sottoscrittore può cambiarlo per indicare che l'operazione deve continuare. Dopo che tutti i sottoscrittori hanno
visto l'evento che è stato generato, il componente FileSearcher esamina il valore booleano e interviene. Questo
schema comporta un passaggio aggiuntivo: il componente deve sapere se i sottoscrittori hanno visto l'evento.
Se non sono presenti sottoscrittori, il campo indicherà erroneamente un annullamento.
Ora si implementerà la prima versione per questo esempio. È necessario aggiungere un campo booleano
denominato CancelRequested al tipo FileFoundArgs :

public class FileFoundArgs : EventArgs


{
public string FoundFile { get; }
public bool CancelRequested { get; set;}

public FileFoundArgs(string fileName)


{
FoundFile = fileName;
}
}

Il nuovo campo viene automaticamente inizializzato su false , il valore predefinito per un campo booleano, in
modo che l'utente non lo annulli accidentalmente. L'unica modifica ulteriore apportata al componente è
controllare il flag dopo la generazione dell'evento per vedere se qualcuno dei sottoscrittori ha richiesto un
annullamento:

public void List(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
if (args.CancelRequested)
break;
}
}

Un vantaggio di questo schema è che non si tratta di una modifica sostanziale. Nessuno dei sottoscrittori ha
richiesto un annullamento in precedenza e non lo stanno richiedendo neanche adesso. Il codice dei sottoscrittori
non richiede alcun aggiornamento, a meno che non si voglia supportare il nuovo protocollo di annullamento.
L'accoppiamento è molto debole.
Aggiorniamo il server di sottoscrizione in modo che richieda un annullamento dopo aver trovato il primo
eseguibile:

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>


{
Console.WriteLine(eventArgs.FoundFile);
eventArgs.CancelRequested = true;
};

Aggiunta di un'altra dichiarazione di evento


Aggiungeremo ora un'altra funzionalità e dimostreremo altri idiomi del linguaggio per gli eventi. Aggiungiamo
un overload del metodo Search che attraversa tutte le sottodirectory alla ricerca dei file.
L'operazione potrebbe rivelarsi piuttosto lunga in una directory con molte sottodirectory. Aggiungiamo un
evento che viene generato quando inizia la ricerca in una nuova directory. Questo consente ai sottoscrittori di
tenere traccia dell'avanzamento e di aggiornare l'utente sullo stato dell'avanzamento. Tutti gli esempi creati
finora sono pubblici. Rendiamo questo evento interno. Ciò significa che è possibile rendere interni anche i tipi
utilizzati per gli argomenti.
Si inizierà creando la nuova classe EventArgs derivata per la segnalazione della nuova directory e
dell'avanzamento.

internal class SearchDirectoryArgs : EventArgs


{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs)


{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}

Anche in questo caso è possibile seguire le indicazioni per creare un tipo di riferimento non modificabile per
argomenti evento.
Ora definire l'evento. Questa volta si userà una sintassi diversa. Oltre a usare la sintassi di campo, è possibile
creare la proprietà in modo esplicito con gestori di aggiunta e rimozione. Questo esempio non richiede codice
aggiuntivo in tali gestori ma illustra come crearli.

internal event EventHandler<SearchDirectoryArgs> DirectoryChanged


{
add { directoryChanged += value; }
remove { directoryChanged -= value; }
}
private EventHandler<SearchDirectoryArgs> directoryChanged;

Il codice che si scrive qui rispecchia per molti aspetti quello generato dal compilatore per le definizioni di evento
di campo viste in precedenza. Si crea l'evento usando una sintassi molto simile a quella impiegata per le
proprietà. Si noti che i gestori hanno nomi diversi: add e remove . Questi vengono chiamati per attivare la
sottoscrizione all'evento o annullarla. Si noti che è necessario anche dichiarare un campo di backup privato per
archiviare la variabile di evento. Viene inizializzata su null.
Quindi, si aggiunge l'overload del metodo Search che attraversa le sottodirectory e genera entrambi gli eventi.
Il modo più semplice per eseguire questa operazione è usare un argomento predefinito per specificare che si
desidera cercare in tutte le directory:

public void Search(string directory, string searchPattern, bool searchSubDirs = false)


{
if (searchSubDirs)
{
var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
var completedDirs = 0;
var totalDirs = allDirectories.Length + 1;
foreach (var dir in allDirectories)
{
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(dir, totalDirs, completedDirs++));
// Search 'dir' and its subdirectories for files that match the search pattern:
SearchDirectory(dir, searchPattern);
}
// Include the Current Directory:
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(directory, totalDirs, completedDirs++));
SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}

private void SearchDirectory(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
if (args.CancelRequested)
break;
}
}

A questo punto è possibile eseguire l'applicazione chiamando l'overload per cercare in tutte le sottodirectory.
Non sono presenti sottoscrittori per il nuovo evento ChangeDirectory , ma l'uso dell'idioma ?.Invoke()
garantisce il funzionamento corretto.
Aggiungiamo un gestore per scrivere una riga che mostra l'avanzamento nella finestra della console.

fileLister.DirectoryChanged += (sender, eventArgs) =>


{
Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
};

Sono stati esaminati schemi che vengono seguiti in tutto l'ecosistema .NET. Conoscendo questi schemi e
convenzioni è possibile scrivere velocemente codice C# e .NET idiomatico.
Ora si vedranno alcune modifiche di questi schemi nella versione più recente di .NET.
Avanti
Schema di eventi .NET Core aggiornato
18/03/2020 • 7 minutes to read • Edit Online

Indietro
L'articolo precedente descriveva gli schemi di eventi più comuni. .NET Core ha uno schema più flessibile. In
questa versione la definizione EventHandler<TEventArgs> non ha più il vincolo che prevede che TEventArgs deve
essere una classe derivata da System.EventArgs .
Di conseguenza, la flessibilità è maggiore e viene offerta la compatibilità con le versioni precedenti. Si consideri
innanzitutto la flessibilità. La classe System.EventArgs introduce un solo metodo: MemberwiseClone() che crea
una copia superficiale dell'oggetto. Il metodo deve usare la reflection per implementare la funzionalità per tutte
le classi derivate da EventArgs . Tale funzionalità risulta più semplice da creare in una classe derivata specifica.
Ciò significa che la derivazione da System.EventArgs è un vincolo che limita la progettazione e non offre altri
vantaggi. Infatti, è possibile modificare le definizioni di FileFoundArgs e SearchDirectoryArgs in modo che non
derivino da EventArgs . Il programma funzionerà esattamente allo stesso modo.
È anche possibile modificare SearchDirectoryArgs in uno struct se si esegue una modifica aggiuntiva:

internal struct SearchDirectoryArgs


{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()


{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}

La modifica aggiuntiva consiste nell'effettuare una chiamata al costruttore senza parametri prima di specificare
il costruttore che inizializza tutti i campi. Senza questa aggiunta, le regole del linguaggio C# segnalano un
accesso alle proprietà precedente alla loro assegnazione.
Non modificare FileFoundArgs da classe (tipo riferimento) a struct (tipo valore) poiché il protocollo di gestione
dell'annullamento richiede che gli argomenti degli eventi vengano passati per riferimento. Se è stata apportata
la stessa modifica, la classe di ricerca file non potrà mai osservare le modifiche apportate da un sottoscrittore di
eventi. Per ogni sottoscrittore verrà usata una nuova copia della struttura che sarà una copia diversa da quella
vista dall'oggetto di ricerca file.
Si consideri quindi come questa modifica può essere compatibile con le versioni precedenti. La rimozione del
vincolo non ha effetto sul codice esistente. I tipi di argomento degli eventi esistenti derivano ancora da
System.EventArgs . La compatibilità con le versioni precedenti è uno dei motivi principali per cui continueranno a
derivare da System.EventArgs . Tutti i sottoscrittori di eventi esistenti saranno sottoscrittori di un evento basato
sul modello classico.
In base a una logica simile, tutti i tipi di argomento degli eventi creati non avranno sottoscrittori in alcuna
codebase esistente. I nuovi tipi di evento che non derivano da System.EventArgs non interromperanno le
codebase.
Eventi con i sottoscrittori asincroni
L'ultimo modello da conoscere consiste nell'apprendere come scrivere sottoscrittori di eventi che effettuano
chiamate a codice asincrono. Questa operazione è descritta nell'articolo relativo ad async e await. Sebbene i
metodi asincroni possano avere un tipo restituito void, questa procedura è fortemente sconsigliata. Quando il
codice del sottoscrittore di eventi chiama un metodo asincrono, l'unica opzione consiste nel creare un metodo
async void . Il metodo è necessario per la firma del gestore eventi.

Queste richieste contrastanti devono essere conciliate. In qualche modo, è necessario creare un metodo
async void sicuro. I principi di base del modello da implementare sono i seguenti:

worker.StartWorking += async (sender, eventArgs) =>


{
try
{
await DoWorkAsync();
}
catch (Exception e)
{
//Some form of logging.
Console.WriteLine($"Async task failure: {e.ToString()}");
// Consider gracefully, and quickly exiting.
}
};

Si noti innanzitutto che il gestore è contrassegnato come gestore asincrono. Poiché viene assegnato al tipo
delegato di un gestore eventi, avrà un tipo restituito void. Per questa ragione è necessario seguire il modello
indicato nel gestore e non consentire la generazione di eccezioni dal contesto del gestore asincrono. Poiché non
restituisce attività, non sarà presente alcuna attività che segnala l'errore attivando lo stato di errore. Poiché è
asincrono, il metodo non può generare l'eccezione. (Il metodo chiamante ha async continuato l'esecuzione
perché è .) Il comportamento di runtime effettivo verrà definito in modo diverso per ambienti diversi. Può
terminare il thread o il processo proprietario del thread o lasciare il processo in uno stato indeterminato. Tutti
questi potenziali risultati sono altamente indesiderabili.
Per questo motivo è necessario includere l'istruzione await per l'attività asincrona nel proprio blocco try. Se
causa un'attività non riuscita, è possibile registrare l'errore. Se si tratta di un errore dal quale non è possibile
ripristinare l'applicazione, è possibile uscire rapidamente dal programma.
Questi sono gli aggiornamenti più importanti per il modello di eventi .NET. Nelle librerie usate sono disponibili
numerosi esempi delle versioni precedenti. È necessario tuttavia riconoscere i modelli più recenti.
L'articolo successivo descrive come distinguere l'uso di delegates e events nelle progettazioni. Si tratta di
concetti simili e l'articolo risulterà utile per effettuare la scelta più adatta ai propri programmi.
Avanti
Distinzione di delegati ed eventi
02/11/2020 • 7 minutes to read • Edit Online

Indietro
Gli sviluppatori che non hanno familiarità con la piattaforma .NET Core spesso sono in difficoltà quando devono
scegliere tra due tipi di progettazione, uno basato su delegates e l'altro su events . La scelta di delegati o eventi
è spesso difficile, perché le due funzionalità del linguaggio sono simili. È persino possibile creare eventi usando
il supporto del linguaggio per i delegati.
Entrambi i tipi di progettazione consentono di usare scenari di associazione tardiva, in altre parole scenari in cui
un componente comunica chiamando un metodo noto solo in runtime. Entrambi supportano metodi per
sottoscrittori singoli e multipli. Questa funzionalità è nota anche come supporto singlecast e multicast. Entrambi
i tipi di progettazione usano una sintassi simile per l'aggiunta e la rimozione di gestori. Per la chiamata dei
metodi di generazione di eventi e di chiamata di delegati, infine, entrambi i tipi di progettazione usano
esattamente la stessa sintassi. Supportano persino la stessa sintassi del metodo Invoke() con l'operatore ?. .
Con tutte queste analogie, è comprensibile avere difficoltà nel determinare quando usare ognuno dei due tipi di
progettazione.

L'ascolto di eventi è facoltativo


La considerazione più importante per determinare quale funzionalità del linguaggio usare è la necessità di un
sottoscrittore associato. Se il codice deve chiamare il codice fornito dal Sottoscrittore, è necessario usare una
progettazione basata sui delegati quando è necessario implementare il callback. Se il codice è in grado di
eseguire tutte le operazioni contenute senza chiamare alcun sottoscrittore, è necessario usare una progettazione
basata su eventi.
Si considerino gli esempi compilati nel corso di questa sezione. Perché il codice con List.Sort() compilato sia
in grado di ordinare correttamente gli elementi, deve essere dotato di una funzione di confronto. Perché le
query LINQ siano in grado di determinare quali elementi restituire, devono essere dotate di delegati. In
entrambi i casi è stata usata una progettazione compilata con delegati.
Si consideri l'evento Progress , che segnala lo stato di un'attività. L'attività continua indipendentemente dal fatto
che siano presenti listener o meno. Un altro esempio è rappresentato da FileSearcher , che continua a cercare e
trovare tutti i file specificati anche senza sottoscrittori di eventi associati. I controlli UX continuano a funzionare
correttamente, anche se non sono presenti sottoscrittori in ascolto degli eventi. Entrambi gli esempi usano
progettazioni basate su eventi.

Per la restituzione di valori sono necessari delegati


Un'altra considerazione riguarda il prototipo che si vuole usare per il metodo del delegato. Come si è visto, per i
delegati usati per gli eventi il tipo restituito è void. Si è visto anche che alcuni termini per la creazione di gestori
eventi, invece, restituiscono informazioni alle origini eventi tramite la modifica di proprietà dell'oggetto
argomento dell'evento. Questi termini sono efficaci, ma non sono altrettanto naturali della restituzione di un
valore da parte di un metodo.
Si tenga presente che spesso questi due tipi di euristica sono presenti entrambi: se il metodo del delegato
restituisce un valore, è probabile che in qualche modo influisca sull'algoritmo.

Eventi con chiamata privata


Classi diverse da quelle in cui è contenuto un evento possono solo aggiungere e rimuovere listener di eventi;
solo la classe che contiene l'evento può richiamare l'evento. Gli eventi sono in genere membri di classi
pubbliche. Per confronto, i delegati vengono spesso passati come parametri e archiviati come membri della
classe privata, se vengono archiviati.

I listener di eventi hanno spesso una durata maggiore


I listener di eventi hanno una durata maggiore è una motivazione leggermente più debole. È tuttavia possibile
che le progettazioni basate su eventi siano più naturali se l'origine degli eventi genera eventi per un periodo di
tempo duraturo. In molti sistemi è possibile vedere esempi di progettazione basata su eventi per i controlli UX.
Dopo la sottoscrizione di un evento, l'origine di questo può generare eventi per tutta la durata del programma. È
possibile annullare la sottoscrizione di eventi quando questi non sono più necessari.
Ciò contrasta con molte progettazioni basate su delegati, in cui un delegato viene usato come argomento per un
metodo e non viene più usato dopo che il metodo ritorna.

Valutare con attenzione


Le considerazioni precedenti non rappresentano regole ferree ma informazioni aggiuntive che consentono di
stabilire la scelta più appropriata per il proprio caso specifico. Date le analogie, è anche possibile eseguire un
prototipo per entrambi i tipi di progettazione, per verificare quale risulterebbe più naturale usare. Entrambi
consentono di gestire in modo efficiente gli scenari di associazione tardiva. Usare quello che comunica meglio
con il proprio progetto.
LINQ (Language-Integrated Query)
02/11/2020 • 6 minutes to read • Edit Online

LINQ (Language-Integrated Query) è il nome di un set di tecnologie basate sull'integrazione delle funzionalità di
query direttamente nel linguaggio C#. In genere, le query sui dati vengono espresse come stringhe semplici
senza il controllo dei tipi in fase di compilazione o il supporto IntelliSense. È anche necessario imparare un
linguaggio di query diverso per ogni tipo di origine dati: database SQL, documenti XML, svariati servizi Web e
così via. Con LINQ, una query è un costrutto del linguaggio di prima classe, come le classi, i metodi e gli eventi.
Per uno sviluppatore che scrive query, la parte integrata nel linguaggio più visibile di LINQ è l'espressione di
query. Le espressioni di query vengono scritte con una sintassi di query dichiarativa. Tramite la sintassi di query
è possibile eseguire operazioni di filtro, ordinamento e raggruppamento sulle origini dati usando una quantità
minima di codice. Vengono usati gli stessi modelli di espressioni di query di base per eseguire una query e
trasformare i dati in database SQL, set di dati ADO .NET, documenti e flussi XML e raccolte .NET.
L'esempio seguente mostra l'operazione di query completa. L'operazione completa include la creazione di
un'origine dati, la definizione dell'espressione di query e l'esecuzione della query in un'istruzione foreach .

class LINQQueryExpressions
{
static void Main()
{

// Specify the data source.


int[] scores = new int[] { 97, 92, 81, 60 };

// Define the query expression.


IEnumerable<int> scoreQuery =
from score in scores
where score > 80
select score;

// Execute the query.


foreach (int i in scoreQuery)
{
Console.Write(i + " ");
}
}
}
// Output: 97 92 81

Panoramica sulle espressioni di query


Le espressioni di query possono essere usate per eseguire una query e trasformare dati da qualsiasi
origine dati abilitata per LINQ. Una sola query, ad esempio, è in grado di recuperare dati da un database
SQL e di produrre un flusso XML come output.
Le espressioni di query sono facili da gestire perché usano molti costrutti di linguaggio C# di uso
comune.
Le variabili presenti in un'espressione di query sono tutte fortemente tipizzate, anche se in molti casi non
è necessario specificare il tipo in modo esplicito perché il compilatore è in grado di dedurlo. Per altre
informazioni, vedere Relazioni tra i tipi nelle operazioni di query LINQ.
Una query non viene eseguita finché non si esegue l'iterazione della variabile di query, ad esempio in
un'istruzione foreach . Per altre informazioni, vedere Introduzione alle query LINQ.
In fase di compilazione, le espressioni di query vengono convertite in chiamate al metodo dell'operatore
query standard secondo le regole definite nella specifica C#. Le query che possono essere espresse
usando la sintassi di query possono essere espresse anche usando la sintassi dei metodi. Nella maggior
parte dei casi, tuttavia, la sintassi di query è più leggibile e concisa. Per altre informazioni, vedere
Specifiche del linguaggio C# e Panoramica degli operatori di query standard.
Come regola di scrittura delle query LINQ, è consigliabile usare la sintassi di query quando possibile e la
sintassi dei metodi quando necessario. Tra le due diverse forme non esiste differenza semantica o a livello
di prestazioni. Le espressioni di query sono spesso più leggibili delle espressioni equivalenti scritte nella
sintassi dei metodi.
Per alcune operazioni di query, ad esempio Count o Max, non è presente una clausola dell'espressione di
query equivalente. Tali espressioni devono quindi essere espresse come chiamata di metodo. La sintassi
dei metodi può essere combinata con la sintassi di query in diversi modi. Per altre informazioni, vedere
sintassi di query e sintassi di metodi in LINQ.
Le espressioni di query possono essere compilate in alberi delle espressioni o in delegati, a seconda del
tipo al quale viene applicata la query. Le query IEnumerable<T> vengono compilate in delegati. Le query
IQueryable e IQueryable<T> vengono compilate in alberi delle espressioni. Per altre informazioni, vedere
Alberi delle espressioni.

Passaggi successivi
Per altre informazioni dettagliate su LINQ, iniziare ad acquisire dimestichezza con alcuni concetti di base nella
sezione introduttiva Nozioni fondamentali sulle espressioni di query e quindi leggere la documentazione per la
tecnologia LINQ a cui si è interessati:
Documenti XML: LINQ to XML
ADO.NET Entity Framework: LINQ to Entities
Raccolte, file e stringhe .NET: LINQ to Objects
Per approfondire LINQ in generale, vedere LINQ in C#.
Per iniziare a utilizzare LINQ in C#, vedere l'esercitazione Uso di LINQ.
Nozioni fondamentali sulle espressioni di query
18/03/2020 • 22 minutes to read • Edit Online

Questo articolo presenta i concetti di base relativi alle espressioni di query nel linguaggio C#.

Che cos'è una query e cosa fa?


Una query è un set di istruzioni che descrive i dati da recuperare da una determinata origine (o più origini) dati e
indica quale forma e organizzazione devono avere i dati restituiti. Una query è distinta dai risultati che produce.
In genere, i dati di origine vengono organizzati logicamente come una sequenza di elementi dello stesso tipo. Ad
esempio, una tabella di database SQL contiene una sequenza di righe. In un file XML è presente una "sequenza"
di elementi XML, anche se questi sono organizzati gerarchicamente in una struttura ad albero. Una raccolta in
memoria contiene una sequenza di oggetti.
Dal punto di vista di un'applicazione, il tipo e la struttura specifici dei dati di origine non è importante.
L'applicazione considera sempre i dati di origine come raccolta IEnumerable<T> o IQueryable<T>. In LINQ to
XML, ad esempio, i dati di origine sono resi visibili come oggetto IEnumerable <XElement>.
Data questa sequenza di origine, una query può eseguire una delle tre operazioni seguenti:
Recuperare un subset di elementi per produrre una nuova sequenza senza modificare i singoli elementi.
La query può quindi ordinare o raggruppare la sequenza restituita in vari modi, come illustrato
nell'esempio seguente (si presuppone che scores sia un elemento int[] ):

IEnumerable<int> highScoresQuery =
from score in scores
where score > 80
orderby score descending
select score;

Recuperare una sequenza di elementi come nell'esempio precedente, ma trasformandoli in un nuovo tipo
di oggetto. Ad esempio, una query può recuperare solo i cognomi da determinati record cliente in
un'origine dati. Oppure può recuperare il record completo e quindi usarlo per creare un altro tipo di
oggetto in memoria o anche dati XML prima di generare la sequenza di risultati finale. L'esempio
seguente illustra una proiezione da int a string . Si noti il nuovo tipo di highScoresQuery .

IEnumerable<string> highScoresQuery2 =
from score in scores
where score > 80
orderby score descending
select $"The score is {score}";

Recuperare un valore singleton sui dati di origine, ad esempio:


Il numero di elementi che corrispondono a una determinata condizione.
L'elemento con il valore massimo o minimo.
Il primo elemento che corrisponde a una condizione o la somma di particolari valori in un set
specificato di elementi. Ad esempio, la query seguente restituisce il numero di punteggi superiori a
80 dalla matrice di interi scores :
int highScoreCount =
(from score in scores
where score > 80
select score)
.Count();

Nell'esempio precedente si noti l'uso delle parentesi attorno all'espressione di query prima della
chiamata al metodo Count . Un'espressione analoga è usare una nuova variabile per archiviare il
risultato concreto. Questa tecnica è più leggibile perché mantiene la variabile che contiene la query
separata dalla query che archivia un risultato.

IEnumerable<int> highScoresQuery3 =
from score in scores
where score > 80
select score;

int scoreCount = highScoresQuery3.Count();

Nell'esempio precedente la query viene eseguita nella chiamata a Count , poiché Count deve eseguire
l'iterazione dei risultati per determinare il numero di elementi restituiti da highScoresQuery .

Che cos'è un'espressione di query?


Un'espressione di query è una query espressa nella sintassi delle query. È un costrutto di linguaggio di prima
classe. È esattamente come qualsiasi altra espressione e può essere usata in qualsiasi contesto in cui
un'espressione C# è valida. Un'espressione di query consiste in un set di clausole scritte in una sintassi
dichiarativa simile a SQL o XQuery. Ogni clausola contiene a sua volta una o più espressioni C# e queste
espressioni possono essere espressioni di query o contenere un'espressione di query.
Un'espressione di query deve iniziare con una clausola from e terminare con una clausola select o group. Tra la
prima clausola from e l'ultima clausola select o group può contenere una o più delle seguenti clausole
facoltative: where, orderby, join, let e anche clausole from aggiuntive. È anche possibile usare la parola chiave
into per consentire al risultato di una clausola join o group di funzionare come origine per le clausole di
query aggiuntive nella stessa espressione di query.
Variabile di query
In LINQ una variabile di query è qualsiasi variabile che archivia una query anziché i risultati di una query. In
particolare, una variabile di query è sempre un tipo enumerabile che genera una sequenza di elementi quando
viene iterato in un'istruzione foreach o una chiamata diretta al relativo metodo IEnumerator.MoveNext .
L'esempio di codice seguente illustra un'espressione di query semplice con un'origine dati, una clausola di filtro,
una clausola di ordinamento e nessuna trasformazione degli elementi di origine. La clausola select termina la
query.
static void Main()
{
// Data source.
int[] scores = { 90, 71, 82, 93, 75, 82 };

// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group

// Execute the query to produce the results


foreach (int testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
}
// Outputs: 93 90 82 82

Nell'esempio precedente scoreQuery è una variabile di query, che a volte viene definita semplicemente query.
La variabile di query non archivia dati sul risultato effettivo, che vengono generati nel ciclo foreach . E quando
viene eseguita l'istruzione foreach i risultati della query non vengono restituiti attraverso la variabile di query
scoreQuery . Vengono piuttosto restituiti attraverso la variabile di iterazione testScore . La variabile scoreQuery
può essere iterata in un secondo ciclo foreach . Verranno generati gli stessi risultati purché non siano state
modificate né la variabile né l'origine dati.
Una variabile di query può archiviare una query espressa nella sintassi di query, nella sintassi di metodo o in
una combinazione delle due. Negli esempi seguenti sia queryMajorCities che queryMajorCities2 sono variabili
di query:

//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 100000
select city;

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

I due esempi seguenti illustrano invece le variabili che non sono variabili di query anche se ognuna viene
inizializzata con una query. Non sono variabili di query perché archiviano i risultati:
int highestScore =
(from score in scores
select score)
.Max();

// or split the expression


IEnumerable<int> scoreQuery =
from score in scores
select score;

int highScore = scoreQuery.Max();


// the following returns the same result
int highScore = scores.Max();

List<City> largeCitiesList =
(from country in countries
from city in country.Cities
where city.Population > 10000
select city)
.ToList();

// or split the expression


IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

Per altre informazioni sui diversi modi di esprimere le query, vedere Sintassi di query e sintassi di metodi in
LINQ.
Tipizzazione esplicita e implicita delle variabili di query
In questa documentazione viene usato in genere il tipo esplicito della variabile di query allo scopo di evidenziare
la relazione di tipo tra la variabile di query e la clausola select. Tuttavia, è possibile usare anche la parola chiave
var per indicare al compilatore di dedurre il tipo di una variabile di query, o qualsiasi altra variabile locale, in fase
di compilazione. L'esempio di query illustrato in precedenza in questo argomento può essere espresso anche
usando la tipizzazione implicita:

// Use of var is optional here and in all queries.


// queryCities is an IEnumerable<City> just as
// when it is explicitly typed.
var queryCities =
from city in cities
where city.Population > 100000
select city;

Per altre informazioni, vedere Variabili locali tipizzate in modo implicito e Relazioni tra i tipi nelle operazioni di
query LINQ.
Avviare un'espressione di query
Un'espressione di query deve iniziare con una clausola from . Specifica un'origine dati insieme a una variabile di
intervallo. La variabile di intervallo rappresenta ogni elemento successivo nella sequenza di origine man mano
che si attraversa la sequenza di origine. La variabile di intervallo è fortemente tipizzata in base al tipo di
elementi nell'origine dati. Nell'esempio seguente, poiché countries è una matrice di oggetti Country , anche la
variabile di intervallo è tipizzata come Country . Poiché la variabile di intervallo è fortemente tipizzata, è
possibile usare l'operatore punto per accedere ai membri disponibili del tipo.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 500000 //sq km
select country;

La variabile di intervallo è nell'ambito finché la query viene terminata con un punto e virgola o con una clausola
continuation.
Un'espressione di query può contenere più clausole from . Usare clausole from aggiuntive quando ogni
elemento nella sequenza di origine è a sua volta una raccolta o contiene una raccolta. Ad esempio, si supponga
di avere una raccolta di oggetti Country , ognuna delle quali contiene una raccolta di oggetti City denominata
Cities . Per eseguire query sugli oggetti City in ogni Country , usare due clausole from come illustrato di
seguito:

IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;

Per altre informazioni, vedere Clausola from.


Terminare un'espressione di query
Un'espressione di query deve terminare con una clausola group o una clausola select .
Clausola group
Usare la clausola group per produrre una sequenza di gruppi organizzata in base a una chiave specificata. La
chiave può essere qualsiasi tipo di dati. Ad esempio, la query seguente crea una sequenza di gruppi che contiene
uno o più oggetti Country e la cui chiave è un valore char .

var queryCountryGroups =
from country in countries
group country by country.Name[0];

Per altre informazioni sul raggruppamento, vedere Clausola group.


Clausola select
Usare la clausola select per creare tutti gli altri tipi di sequenze. Una clausola select semplice produce una
sequenza usando lo stesso tipo di oggetti dell'origine dati. In questo esempio l'origine dati contiene oggetti
Country . La clausola orderby si limita a ordinare gli elementi in base a un nuovo ordine e la clausola select
produce una sequenza degli oggetti Country riordinati.

IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;

La clausola select può essere usata per trasformare i dati di origine in sequenze di nuovi tipi. Questa
trasformazione è detta anche proiezione. Nell'esempio seguente la clausola select proietta una sequenza di tipi
anonimi che contiene solo un subset dei campi dell'elemento originale. Si noti che i nuovi oggetti vengono
inizializzati usando un inizializzatore di oggetto.
// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
from country in countries
select new { Name = country.Name, Pop = country.Population };

Per altre informazioni su tutti i modi in cui una clausola select può essere usata per trasformare i dati di
origine, vedere Clausola select.
Continuazioni con "into"
È possibile usare la parola chiave into in una clausola select o group per creare un identificatore
temporaneo che archivia una query. Eseguire questa operazione quando è necessario eseguire altre operazioni
di query per una query dopo un'operazione di raggruppamento o selezione. Nell'esempio seguente countries
indica i paesi raggruppati in base alla popolazione in intervalli di 10 milioni. Dopo avere creato questi gruppi, le
clausole aggiuntive escludono alcuni gruppi, quindi ordinano i gruppi in ordine crescente. Per eseguire le
operazioni aggiuntive, è richiesta la continuazione rappresentata da countryGroup .

// percentileQuery is an IEnumerable<IGrouping<int, Country>>


var percentileQuery =
from country in countries
let percentile = (int) country.Population / 10_000_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;

// grouping is an IGrouping<int, Country>


foreach (var grouping in percentileQuery)
{
Console.WriteLine(grouping.Key);
foreach (var country in grouping)
Console.WriteLine(country.Name + ":" + country.Population);
}

Per altre informazioni, vedere into.


Filtro, ordinamento e join
Tra la clausola iniziale from e la clausola finale select o group , tutte le altre clausole ( where , join , orderby ,
from , let ) sono facoltative. Qualsiasi clausola facoltativa può essere usata zero o più volte nel corpo di una
query.
Clausola where
Usare la clausola where per escludere gli elementi dai dati di origine in base a una o più espressioni del
predicato. La clausola where nell'esempio seguente include un predicato con due condizioni.

IEnumerable<City> queryCityPop =
from city in cities
where city.Population < 200000 && city.Population > 100000
select city;

Per ulteriori informazioni, vedere clausola where.


Clausola orderby
Usare la clausola orderby per ordinare i risultati in ordine crescente o decrescente. È anche possibile specificare
gli ordinamenti secondari. Nell'esempio seguente viene eseguito un ordinamento primario per gli oggetti
country usando la proprietà Area . Viene quindi eseguito un ordinamento secondario usando la proprietà
Population .
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;

La parola chiave ascending è facoltativa, ma consente l'ordinamento predefinito se non viene specificato alcun
ordine. Per altre informazioni, vedere Clausola orderby.
Clausola join
Usare la clausola join per associare e/o combinare gli elementi di un'origine dati con gli elementi di un'altra
origine dati in base a un confronto di uguaglianza tra le chiavi specificate in ogni elemento. In LINQ le
operazioni di join vengono eseguite su sequenze di oggetti i cui elementi sono tipi diversi. Dopo avere unito due
sequenze, è necessario usare un'istruzione select o group per specificare l'elemento da archiviare nella
sequenza di output. È anche possibile usare un tipo anonimo per combinare le proprietà da ogni set di elementi
associati in un nuovo tipo per la sequenza di output. L'esempio seguente associa oggetti prod la cui proprietà
Category corrisponde a una delle categorie nella matrice di stringhe categories . I Category prodotti che non
categories corrispondono ad alcuna stringa in vengono filtrati. L'istruzione select proietta un nuovo tipo
cat le prod cui proprietà sono tratte da entrambi e .

var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new { Category = cat, Name = prod.Name };

È anche possibile creare un join di gruppo archiviando i risultati dell'operazione join in una variabile
temporanea usando la parola chiave into. Per ulteriori informazioni, vedere Clausola join.
Clausola let
Usare la clausola let per archiviare il risultato di un'espressione, ad esempio una chiamata al metodo, in una
nuova variabile di intervallo. Nell'esempio seguente la variabile di intervallo firstName archivia il primo
elemento della matrice di stringhe restituita da Split .

string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;

foreach (string s in queryFirstNames)


Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Per altre informazioni, vedere Clausola let.


Sottoquery in un'espressione di query
Una clausola di query può contenere un'espressione di query, a volte detta sottoquery. Ogni sottoquery inizia
con la propria clausola from che non fa necessariamente riferimento alla stessa origine dati nella prima
clausola from . Ad esempio, la query seguente rappresenta un'espressione di query usate nell'istruzione select
per recuperare i risultati di un'operazione di raggruppamento.
var queryGroupMax =
from student in students
group student by student.GradeLevel into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore =
(from student2 in studentGroup
select student2.Scores.Average())
.Max()
};

Per ulteriori informazioni, vedere Eseguire una sottoquery su un'operazione di raggruppamento.

Vedere anche
Guida alla programmazione in C
Language Integrated Query (LINQ)
Parole chiave di query (LINQ)
Standard query operators overview (Panoramica degli operatori di query standard)
LINQ in C#
18/03/2020 • 2 minutes to read • Edit Online

Questa sezione contiene i collegamenti ad argomenti che offrono informazioni più dettagliate su LINQ.

Contenuto della sezione


Introduzione alle query LINQ
Vengono descritte le tre parti dell'operazione di query LINQ di base che sono comuni a tutti i linguaggi e a tutte
le origini dati.
LINQ e tipi generici
Vengono presentati brevemente i tipi generici usati in LINQ.
Trasformazioni dei dati con LINQData transformations with LINQ
Vengono descritte le diverse modalità con le quali trasformare i dati recuperati in query.
Relazioni tra i tipi nelle operazioni di query LINQ
Viene descritto come mantenere e/o trasformare i tipi nelle tre parti di un'operazione di query LINQ
Sintassi di query e sintassi di metodi in LINQ
Vengono confrontate la sintassi del metodo e la sintassi di query come due modi per esprimere una query LINQ.
Funzionalità di C# che supportano LINQ
Vengono descritti i costrutti di linguaggio in C# che supportano LINQ.

Sezioni correlate
Espressioni di query LINQ
Contiene una panoramica delle query in LINQ e collegamenti a risorse aggiuntive.
Standard query operators overview (Panoramica degli operatori di query standard)
Vengono presentati i metodi standard usati in LINQ.
Scrivere query LINQ in C#
02/11/2020 • 7 minutes to read • Edit Online

Questo articolo illustra i tre modi in cui è possibile scrivere una query LINQ in C#:
1. Usare la sintassi di query.
2. Usare la sintassi di metodo.
3. Usare una combinazione di sintassi di query e sintassi di metodo.
Gli esempi seguenti illustrano alcune semplici query LINQ usando ogni approccio indicato sopra. In generale, la
regola è usare (1) ogni volta che è possibile e usare (2) e (3) ogni volta che è necessario.

NOTE
Queste query operano su raccolte in memoria semplici, tuttavia, la sintassi di base è identica a quella usata in LINQ to
Entities e LINQ to XML.

Esempio - Sintassi di query


Il metodo consigliato per scrivere la maggior parte delle query è usare la sintassi di query per creare espressioni
di query. Nell'esempio seguente sono riportate tre espressioni di query. La prima espressione di query dimostra
in che modo si filtrano o si limitano i risultati applicando le condizioni con una clausola where . Restituisce tutti
gli elementi nella sequenza di origine i cui valori sono maggiori di 7 o minori di 3. La seconda espressione
illustra come ordinare i risultati restituiti. La terza espressione illustra come raggruppare i risultati in base a una
chiave. Questa query restituisce due gruppi in base alla prima lettera della parola.

// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// The query variable can also be implicitly typed by using var


IEnumerable<int> filteringQuery =
from num in numbers
where num < 3 || num > 7
select num;

// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num < 3 || num > 7
orderby num ascending
select num;

// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];

Si noti che il tipo delle query è IEnumerable<T>. Tutte queste query potrebbero essere scritte usando var come
indicato nell'esempio seguente:
var query = from num in numbers...
In ognuno degli esempi precedenti le query non vengono effettivamente eseguite finché non si esegue
l'iterazione della variabile di query in un'istruzione foreach o un'altra istruzione. Per altre informazioni, vedere
Introduzione alle query LINQ.

Esempio - Sintassi del metodo


Alcune operazioni di query devono essere espresse come una chiamata al metodo. I più comuni di tali metodi
sono quelli che restituiscono valori numerici singleton, ad esempio Sum, Max, Min, Average e così via. Questi
metodi devono sempre essere chiamati per ultimi in una query poiché rappresentano solo un singolo valore e
non possono essere usati come origine per un'operazione di query aggiuntiva. Nell'esempio seguente viene
illustrata una chiamata al metodo in un'espressione di query:

List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };


List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Se il metodo usa i parametri Action o Func, questi vengono specificati sotto forma di espressione lambda, come
illustrato nell'esempio seguente:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Nelle query precedenti solo la query n. 4 viene immediatamente eseguita. Ciò è dovuto al fatto che viene
restituito un valore singolo invece di una raccolta IEnumerable<T> generica. Il metodo stesso deve usare
foreach per calcolare il proprio valore.

Ognuna delle query precedenti può essere scritta usando la tipizzazione implicita con var, come illustrato
nell'esempio seguente:

// var is used for convenience in these queries


var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Esempio - Sintassi di query e di metodo mista


In questo esempio viene illustrato come usare la sintassi di metodo per i risultati di una clausola di query. È
sufficiente racchiudere l'espressione di query tra parentesi e quindi applicare l'operatore punto e chiamare il
metodo. Nell'esempio seguente la query n. 7 restituisce un conteggio dei numeri il cui valore è compreso tra 3 e
7. In generale, tuttavia, è preferibile usare una seconda variabile per archiviare il risultato della chiamata al
metodo. In questo modo è meno probabile che si crei confusione con i risultati della query.
// Query #7.

// Using a query expression with method syntax


int numCount1 =
(from num in numbers1
where num < 3 || num > 7
select num).Count();

// Better: Create a new variable to store


// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num < 3 || num > 7
select num;

int numCount2 = numbersQuery.Count();

Poiché la query n. 7 restituisce un singolo valore e non una raccolta, la query viene eseguita immediatamente.
La query precedente può essere scritta usando la tipizzazione implicita con var , come segue:

var numCount = (from num in numbers...

Può essere scritta nella sintassi di metodo come indicato di seguito:

var numCount = numbers.Where(n => n < 3 || n > 7).Count();

Può essere scritta usando la tipizzazione esplicita, come indicato di seguito:

int numCount = numbers.Where(n => n < 3 || n > 7).Count();

Vedere anche
Procedura dettagliata: scrittura di query in C#
LINQ (Language-Integrated Query)
clausola WHERE
Eseguire query in una raccolta di oggetti
18/03/2020 • 3 minutes to read • Edit Online

In questo esempio viene illustrato come eseguire una query semplice su un elenco di oggetti Student . Ogni
oggetto Student contiene informazioni di base sullo studente e un elenco che rappresenta i voti dello studente
in quattro esami.
Questa applicazione viene usata come framework per molti altri esempi di questa sezione che usano la stessa
origine dati students .

Esempio
La query seguente restituisce gli studenti che hanno ricevuto un punteggio pari o superiore a 90 nel primo
esame.

public class Student


{
#region data
public enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };

public string FirstName { get; set; }


public string LastName { get; set; }
public int Id { get; set; }
public GradeLevel Year;
public List<int> ExamScores;

protected static List<Student> students = new List<Student>


{
new Student {FirstName = "Terry", LastName = "Adams", Id = 120,
Year = GradeLevel.SecondYear,
ExamScores = new List<int> { 99, 82, 81, 79}},
new Student {FirstName = "Fadi", LastName = "Fakhouri", Id = 116,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int> { 99, 86, 90, 94}},
new Student {FirstName = "Hanying", LastName = "Feng", Id = 117,
Year = GradeLevel.FirstYear,
ExamScores = new List<int> { 93, 92, 80, 87}},
new Student {FirstName = "Cesar", LastName = "Garcia", Id = 114,
Year = GradeLevel.FourthYear,
ExamScores = new List<int> { 97, 89, 85, 82}},
new Student {FirstName = "Debra", LastName = "Garcia", Id = 115,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int> { 35, 72, 91, 70}},
new Student {FirstName = "Hugo", LastName = "Garcia", Id = 118,
Year = GradeLevel.SecondYear,
ExamScores = new List<int> { 92, 90, 83, 78}},
new Student {FirstName = "Sven", LastName = "Mortensen", Id = 113,
Year = GradeLevel.FirstYear,
ExamScores = new List<int> { 88, 94, 65, 91}},
new Student {FirstName = "Claire", LastName = "O'Donnell", Id = 112,
Year = GradeLevel.FourthYear,
ExamScores = new List<int> { 75, 84, 91, 39}},
new Student {FirstName = "Svetlana", LastName = "Omelchenko", Id = 111,
Year = GradeLevel.SecondYear,
ExamScores = new List<int> { 97, 92, 81, 60}},
new Student {FirstName = "Lance", LastName = "Tucker", Id = 119,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int> { 68, 79, 88, 92}},
new Student {FirstName = "Michael", LastName = "Tucker", Id = 122,
Year = GradeLevel.FirstYear,
Year = GradeLevel.FirstYear,
ExamScores = new List<int> { 94, 92, 91, 91}},
new Student {FirstName = "Eugene", LastName = "Zabokritski", Id = 121,
Year = GradeLevel.FourthYear,
ExamScores = new List<int> { 96, 85, 91, 60}}
};
#endregion

// Helper method, used in GroupByRange.


protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

public static void QueryHighScores(int exam, int score)


{
var highScores = from student in students
where student.ExamScores[exam] > score
select new {Name = student.FirstName, Score = student.ExamScores[exam]};

foreach (var item in highScores)


{
Console.WriteLine($"{item.Name,-15}{item.Score}");
}
}
}

public class Program


{
public static void Main()
{
Student.QueryHighScores(1, 90);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}

Questa query è volutamente semplice per consentire la sperimentazione. Ad esempio, è possibile provare altre
condizioni nella clausola where oppure usare una clausola orderby per ordinare i risultati.

Vedere anche
Language Integrated Query (LINQ)
Interpolazione di stringhe
Come restituire una query da un metodo (Guida
per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo esempio viene illustrato come ottenere una query da un metodo come valore restituito e come
parametro out .
Gli oggetti di query sono componibili, vale a dire che è possibile ottenere una query da un metodo. Gli oggetti
che rappresentano le query non memorizzano la raccolta risultante, ma piuttosto i passaggi da eseguire per
ottenere i risultati quando necessario. Il vantaggio di ottenere oggetti query dai metodi è che gli oggetti
possono essere ulteriormente composti o modificati. Di conseguenza, qualsiasi valore restituito o parametro
out di un metodo che restituisce una query deve contenere anche quel tipo. Se un metodo materializza una
query in un oggetto List<T> concreto o un tipo Array, si ritiene che restituisca i risultati della query anziché la
query stessa. Una variabile di query che viene restituita da un metodo può ancora essere composta o
modificata.

Esempio
Nell'esempio seguente il primo metodo restituisce una query come valore restituito e il secondo metodo
restituisce una query come parametro out . Notare che in entrambi i casi è una query che viene restituita, non i
risultati della query.

class MQ
{
// QueryMethhod1 returns a query as its value.
IEnumerable<string> QueryMethod1(ref int[] ints)
{
var intsToStrings = from i in ints
where i > 4
select i.ToString();
return intsToStrings;
}

// QueryMethod2 returns a query as the value of parameter returnQ.


void QueryMethod2(ref int[] ints, out IEnumerable<string> returnQ)
{
var intsToStrings = from i in ints
where i < 4
select i.ToString();
returnQ = intsToStrings;
}

static void Main()


{
MQ app = new MQ();

int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// QueryMethod1 returns a query as the value of the method.


var myQuery1 = app.QueryMethod1(ref nums);

// Query myQuery1 is executed in the following foreach loop.


Console.WriteLine("Results of executing myQuery1:");
// Rest the mouse pointer over myQuery1 to see its type.
foreach (string s in myQuery1)
{
Console.WriteLine(s);
Console.WriteLine(s);
}

// You also can execute the query returned from QueryMethod1


// directly, without using myQuery1.
Console.WriteLine("\nResults of executing myQuery1 directly:");
// Rest the mouse pointer over the call to QueryMethod1 to see its
// return type.
foreach (string s in app.QueryMethod1(ref nums))
{
Console.WriteLine(s);
}

IEnumerable<string> myQuery2;
// QueryMethod2 returns a query as the value of its out parameter.
app.QueryMethod2(ref nums, out myQuery2);

// Execute the returned query.


Console.WriteLine("\nResults of executing myQuery2:");
foreach (string s in myQuery2)
{
Console.WriteLine(s);
}

// You can modify a query by using query composition. A saved query


// is nested inside a new query definition that revises the results
// of the first query.
myQuery1 = from item in myQuery1
orderby item descending
select item;

// Execute the modified query.


Console.WriteLine("\nResults of executing modified myQuery1:");
foreach (string s in myQuery1)
{
Console.WriteLine(s);
}

// Keep console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

Vedere anche
LINQ (Language-Integrated Query)
Archiviare i risultati di una query in memoria
18/03/2020 • 2 minutes to read • Edit Online

Una query è fondamentalmente un set di istruzioni per il recupero e l'organizzazione dei dati. Le query vengono
eseguite in modalità lazy poiché viene richiesto ogni elemento successivo nel risultato. Quando si usa foreach
per scorrere i risultati, gli elementi vengono restituiti quando ne viene eseguito l'accesso. Per valutare una query
e archiviare i risultati senza eseguire un ciclo di foreach , è sufficiente chiamare uno dei seguenti metodi sulla
variabile di query:
ToList
ToArray
ToDictionary
ToLookup
Quando si archiviano i risultati della query, assegnare l'oggetto Collection restituito a una nuova variabile, come
illustrato nell'esempio seguente:

Esempio
class StoreQueryResults
{
static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
static void Main()
{

IEnumerable<int> queryFactorsOfFour =
from num in numbers
where num % 4 == 0
select num;

// Store the results in a new variable


// without executing a foreach loop.
List<int> factorsofFourList = queryFactorsOfFour.ToList();

// Iterate the list just to prove it holds data.


Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key");
Console.ReadKey();
}
}

Vedere anche
Language Integrated Query (LINQ)
Raggruppare i risultati di una query
18/03/2020 • 10 minutes to read • Edit Online

Il raggruppamento è una delle funzionalità più efficace di LINQ. Negli esempi seguenti viene illustrato come
raggruppare i dati in vari modi:
In base a una singola proprietà.
In base alla prima lettera di una proprietà stringa.
In base a un intervallo numerico calcolato.
In base a un predicato booleano o un'altra espressione.
In base a una chiave composta.
Le ultime due query proiettano i risultati in un tipo anonimo nuovo che contiene solo il nome e il cognome dello
studente. Per altre informazioni, vedere Clausola group.

Esempio
In tutti gli esempi di questo argomento vengono usate le classi di supporto e le origini dati seguenti.

public class StudentClass


{
#region data
protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
protected class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public GradeLevel Year;
public List<int> ExamScores;
}

protected static List<Student> students = new List<Student>


{
new Student {FirstName = "Terry", LastName = "Adams", ID = 120,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 99, 82, 81, 79}},
new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 99, 86, 90, 94}},
new Student {FirstName = "Hanying", LastName = "Feng", ID = 117,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 93, 92, 80, 87}},
new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 97, 89, 85, 82}},
new Student {FirstName = "Debra", LastName = "Garcia", ID = 115,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 35, 72, 91, 70}},
new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 92, 90, 83, 78}},
new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 88, 94, 65, 91}},
new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 75, 84, 91, 39}},
new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 97, 92, 81, 60}},
new Student {FirstName = "Lance", LastName = "Tucker", ID = 119,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 68, 79, 88, 92}},
new Student {FirstName = "Michael", LastName = "Tucker", ID = 122,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 94, 92, 91, 91}},
new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 96, 85, 91, 60}}
};
#endregion

//Helper method, used in GroupByRange.


protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

public void QueryHighScores(int exam, int score)


{
var highScores = from student in students
where student.ExamScores[exam] > score
select new {Name = student.FirstName, Score = student.ExamScores[exam]};

foreach (var item in highScores)


{
Console.WriteLine($"{item.Name,-15}{item.Score}");
}
}
}

public class Program


{
public static void Main()
{
StudentClass sc = new StudentClass();
sc.QueryHighScores(1, 90);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}

Esempio
Nell'esempio seguente viene illustrato come raggruppare gli elementi di origine usando una sola proprietà
dell'elemento come chiave di gruppo. In questo caso la chiave è un oggetto string , il cognome dello studente.
È anche possibile usare una sottostringa per la chiave. L'operazione di raggruppamento usa l'operatore di
confronto di uguaglianza predefinito per il tipo.
Incollare il metodo seguente nella classe StudentClass . Modificare l'istruzione di chiamata nel metodo Main in
sc.GroupBySingleProperty() .
public void GroupBySingleProperty()
{
Console.WriteLine("Group by a single property in an object:");

// Variable queryLastNames is an IEnumerable<IGrouping<string,


// DataClass.Student>>.
var queryLastNames =
from student in students
group student by student.LastName into newGroup
orderby newGroup.Key
select newGroup;

foreach (var nameGroup in queryLastNames)


{
Console.WriteLine($"Key: {nameGroup.Key}");
foreach (var student in nameGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}
}
/* Output:
Group by a single property in an object:
Key: Adams
Adams, Terry
Key: Fakhouri
Fakhouri, Fadi
Key: Feng
Feng, Hanying
Key: Garcia
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: Mortensen
Mortensen, Sven
Key: O'Donnell
O'Donnell, Claire
Key: Omelchenko
Omelchenko, Svetlana
Key: Tucker
Tucker, Lance
Tucker, Michael
Key: Zabokritski
Zabokritski, Eugene
*/

Esempio
Nell'esempio seguente viene illustrato come raggruppare gli elementi di origine usando un elemento diverso da
una proprietà dell'oggetto per la chiave di gruppo. In questo esempio la chiave è la prima lettera del cognome
dello studente.
Incollare il metodo seguente nella classe StudentClass . Modificare l'istruzione di chiamata nel metodo Main in
sc.GroupBySubstring() .
public void GroupBySubstring()
{
Console.WriteLine("\r\nGroup by something other than a property of the object:");

var queryFirstLetters =
from student in students
group student by student.LastName[0];

foreach (var studentGroup in queryFirstLetters)


{
Console.WriteLine($"Key: {studentGroup.Key}");
// Nested foreach is required to access group items.
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}
}
/* Output:
Group by something other than a property of the object:
Key: A
Adams, Terry
Key: F
Fakhouri, Fadi
Feng, Hanying
Key: G
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: M
Mortensen, Sven
Key: O
O'Donnell, Claire
Omelchenko, Svetlana
Key: T
Tucker, Lance
Tucker, Michael
Key: Z
Zabokritski, Eugene
*/

Esempio
Nell'esempio seguente viene illustrato come raggruppare gli elementi di origine usando un intervallo numerico
come chiave di gruppo. La query proietta i risultati in un tipo anonimo che contiene solo il nome e il cognome e
l'intervallo percentile al quale appartiene lo studente. Viene usato un tipo anonimo perché non è necessario
usare l'oggetto Student completo per visualizzare i risultati. GetPercentile è una funzione di supporto che
consente di calcolare un percentile in base al voto medio dello studente. Il metodo restituisce un numero intero
compreso tra 0 e 10.

//Helper method, used in GroupByRange.


protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

Incollare il metodo seguente nella classe StudentClass . Modificare l'istruzione di chiamata nel metodo Main in
sc.GroupByRange() .
public void GroupByRange()
{
Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");

var queryNumericRange =
from student in students
let percentile = GetPercentile(student)
group new { student.FirstName, student.LastName } by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;

// Nested foreach required to iterate over groups and group items.


foreach (var studentGroup in queryNumericRange)
{
Console.WriteLine($"Key: {studentGroup.Key * 10}");
foreach (var item in studentGroup)
{
Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
}
}
}
/* Output:
Group by numeric range and project into a new anonymous type:
Key: 60
Garcia, Debra
Key: 70
O'Donnell, Claire
Key: 80
Adams, Terry
Feng, Hanying
Garcia, Cesar
Garcia, Hugo
Mortensen, Sven
Omelchenko, Svetlana
Tucker, Lance
Zabokritski, Eugene
Key: 90
Fakhouri, Fadi
Tucker, Michael
*/

Esempio
Nell'esempio seguente viene illustrato come raggruppare elementi di origine usando un'espressione di
confronto booleana. In questo esempio l'espressione booleana consente di verificare se il voto medio degli
esami di uno studente è maggiore di 75. Come negli esempi precedenti, i risultati vengono proiettati in un tipo
anonimo perché l'elemento di origine completo non è necessario. Si noti che le proprietà nel tipo anonimo
diventano proprietà sul membro Key e sono accessibili tramite il nome quando la query viene eseguita.
Incollare il metodo seguente nella classe StudentClass . Modificare l'istruzione di chiamata nel metodo Main in
sc.GroupByBoolean() .
public void GroupByBoolean()
{
Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
var queryGroupByAverages = from student in students
group new { student.FirstName, student.LastName }
by student.ExamScores.Average() > 75 into studentGroup
select studentGroup;

foreach (var studentGroup in queryGroupByAverages)


{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup)
Console.WriteLine($"\t{student.FirstName} {student.LastName}");
}
}
/* Output:
Group by a Boolean into two groups with string keys
"True" and "False" and project into a new anonymous type:
Key: True
Terry Adams
Fadi Fakhouri
Hanying Feng
Cesar Garcia
Hugo Garcia
Sven Mortensen
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
Key: False
Debra Garcia
Claire O'Donnell
*/

Esempio
Nell'esempio seguente viene illustrato come usare un tipo anonimo per incapsulare una chiave che contiene più
valori. In questo esempio il primo valore della chiave è la prima lettera del cognome dello studente. Il secondo
valore della chiave è un valore booleano che specifica se lo studente ha ottenuto un voto superiore a 85 nel
primo esame. È possibile ordinare i gruppi in base a qualsiasi proprietà nella chiave.
Incollare il metodo seguente nella classe StudentClass . Modificare l'istruzione di chiamata nel metodo Main in
sc.GroupByCompositeKey() .
public void GroupByCompositeKey()
{
var queryHighScoreGroups =
from student in students
group student by new { FirstLetter = student.LastName[0],
Score = student.ExamScores[0] > 85 } into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;

Console.WriteLine("\r\nGroup and order by a compound key:");


foreach (var scoreGroup in queryHighScoreGroups)
{
string s = scoreGroup.Key.Score == true ? "more than" : "less than";
Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetter} who scored {s} 85");
foreach (var item in scoreGroup)
{
Console.WriteLine($"\t{item.FirstName} {item.LastName}");
}
}
}

/* Output:
Group and order by a compound key:
Name starts with A who scored more than 85
Terry Adams
Name starts with F who scored more than 85
Fadi Fakhouri
Hanying Feng
Name starts with G who scored more than 85
Cesar Garcia
Hugo Garcia
Name starts with G who scored less than 85
Debra Garcia
Name starts with M who scored more than 85
Sven Mortensen
Name starts with O who scored less than 85
Claire O'Donnell
Name starts with O who scored more than 85
Svetlana Omelchenko
Name starts with T who scored less than 85
Lance Tucker
Name starts with T who scored more than 85
Michael Tucker
Name starts with Z who scored more than 85
Eugene Zabokritski
*/

Vedere anche
GroupBy
IGrouping<TKey,TElement>
Language Integrated Query (LINQ)
Clausola group
Tipi anonimi
Eseguire una sottoquery su un'operazione di raggruppamento
Creare un gruppo annidato
Raggruppamento dei dati
Creare un gruppo annidato
18/03/2020 • 2 minutes to read • Edit Online

Nell'esempio seguente viene illustrato come creare gruppi annidati in un'espressione di query LINQ. Ogni
gruppo creato in base all'anno o al livello degli studenti viene ulteriormente suddiviso in gruppi in base ai nomi
delle persone.

Esempio
NOTE
Questo esempio contiene riferimenti a oggetti definiti nel codice di esempio in Eseguire una query su una raccolta di
oggetti.
public void QueryNestedGroups()
{
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;

// Three nested foreach loops are required to iterate


// over all elements of a grouped group. Hover the mouse
// cursor over the iteration variables to see their actual type.
foreach (var outerGroup in queryNestedGroups)
{
Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
foreach (var innerGroup in outerGroup)
{
Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
foreach (var innerGroupElement in innerGroup)
{
Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
}
}
}
}
/*
Output:
DataClass.Student Level = SecondYear
Names that begin with: Adams
Adams Terry
Names that begin with: Garcia
Garcia Hugo
Names that begin with: Omelchenko
Omelchenko Svetlana
DataClass.Student Level = ThirdYear
Names that begin with: Fakhouri
Fakhouri Fadi
Names that begin with: Garcia
Garcia Debra
Names that begin with: Tucker
Tucker Lance
DataClass.Student Level = FirstYear
Names that begin with: Feng
Feng Hanying
Names that begin with: Mortensen
Mortensen Sven
Names that begin with: Tucker
Tucker Michael
DataClass.Student Level = FourthYear
Names that begin with: Garcia
Garcia Cesar
Names that begin with: O'Donnell
O'Donnell Claire
Names that begin with: Zabokritski
Zabokritski Eugene
*/

Si noti che sono necessari tre cicli foreach annidati per eseguire l'iterazione degli elementi interni di un gruppo
annidato.

Vedere anche
Language Integrated Query (LINQ)
Eseguire una sottoquery su un'operazione di
raggruppamento
18/03/2020 • 2 minutes to read • Edit Online

Questo articolo descrive due diversi modi per creare una query che ordina i dati di origine in gruppi e quindi
esegue una sottoquery separatamente su ogni gruppo. La tecnica di base in ogni esempio consiste nel
raggruppare gli elementi di origine usando una continuazione denominata newGroup e quindi generando una
nuova sottoquery su newGroup . La sottoquery viene eseguita su ogni nuovo gruppo creato dalla query esterna.
Si noti che in questo esempio l'output finale non è un gruppo, ma una sequenza semplice di tipi anonimi.
Per altre informazioni sul raggruppamento, vedere Clausola group.
Per altre informazioni sulle continuazioni, vedere into. L'esempio seguente usa una struttura dati in memoria
come origine dati, ma gli stessi principi si applicano a qualsiasi tipo di origine dati LINQ.

Esempio
NOTE
Questo esempio contiene riferimenti a oggetti definiti nel codice di esempio in Eseguire una query su una raccolta di
oggetti.

public void QueryMax()


{
var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore =
(from student2 in studentGroup
select student2.ExamScores.Average()).Max()
};

int count = queryGroupMax.Count();


Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)


{
Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
}
}

La query nel frammento di codice precedente può essere scritta anche usando la sintassi del metodo. Il
frammento di codice seguente è una query semanticamente equivalente scritta usando la sintassi del metodo.
public void QueryMaxUsingMethodSyntax()
{
var queryGroupMax = students
.GroupBy(student => student.Year)
.Select(studentGroup => new
{
Level = studentGroup.Key,
HighestScore = studentGroup.Select(student2 => student2.ExamScores.Average()).Max()
});

int count = queryGroupMax.Count();


Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)


{
Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
}
}

Vedere anche
Language Integrated Query (LINQ)
Raggruppare i risultati per chiavi contigue
18/03/2020 • 8 minutes to read • Edit Online

Nell'esempio seguente viene illustrato come raggruppare elementi in blocchi che rappresentano sottosequenze
di chiavi contigue. Si supponga di avere ad esempio la sequenza di coppie chiave-valore riportata di seguito:

C H IAVE VA LO RE

Una Me

Una think

Una that

b Linq

C is

Una really

b cool

b !

Verranno creati i gruppi seguenti in questo ordine:


1. We, think, that
2. Linq
3. is
4. really
5. cool, !
La soluzione viene implementata come metodo di estensione thread-safe che restituisce i risultati in modalità di
flusso. In altre parole, i gruppi vengono prodotti man mano che ci si sposta lungo la sequenza di origine. A
differenza degli operatori group o orderby , questa soluzione può iniziare a restituire i gruppi al chiamante
prima che sia stata letta tutta la sequenza.
La caratteristica thread-safe viene ottenuta effettuando una copia di ogni gruppo o blocco nel corso
dell'iterazione della sequenza di origine, come spiegato nei commenti del codice sorgente. Se la sequenza di
origine include una sequenza di grandi dimensioni di elementi contigui, Common Language Runtime potrebbe
generare un'eccezione OutOfMemoryException.

Esempio
L'esempio seguente illustra sia il metodo di estensione che il codice client usato:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;

namespace ChunkIt
{
// Static class to contain the extension methods.
public static class MyExtensions
{
public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource>
source, Func<TSource, TKey> keySelector)
{
return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
}

public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource>


source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
// Flag to signal end of source sequence.
const bool noMoreSourceElements = true;

// Auto-generated iterator for the source array.


var enumerator = source.GetEnumerator();

// Move to the first element in the source sequence.


if (!enumerator.MoveNext()) yield break;

// Iterate through source sequence and create a copy of each Chunk.


// On each pass, the iterator advances to the first element of the next "Chunk"
// in the source sequence. This loop corresponds to the outer foreach loop that
// executes the query.
Chunk<TKey, TSource> current = null;
while (true)
{
// Get the key for the current Chunk. The source iterator will churn through
// the source sequence until it finds an element with a key that doesn't match.
var key = keySelector(enumerator.Current);

// Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the
current source element.
current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key,
keySelector(value)));

// Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the
ChunkBy method.
// At this point the Chunk only has the first element in its source sequence. The remaining
elements will be
// returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for
more info.
yield return current;

// Check to see whether (a) the chunk has made a copy of all its source elements or
// (b) the iterator has reached the end of the source sequence. If the caller uses an inner
// foreach loop to iterate the chunk items, and that loop ran to completion,
// then the Chunk.GetEnumerator method will already have made
// copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not
// enumerate all elements in the chunk, we need to do it here to avoid corrupting the
iterator
// for clients that may be calling us on a separate thread.
if (current.CopyAllChunkElements() == noMoreSourceElements)
{
yield break;
}
}
}

// A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk
// has a key and a list of ChunkItem objects, which are copies of the elements in the source
sequence.
class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
{
// INVARIANT: DoneCopyingChunk == true ||
// INVARIANT: DoneCopyingChunk == true ||
// (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)

// A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk.
Each ChunkItem
// has a reference to the next ChunkItem in the list.
class ChunkItem
{
public ChunkItem(TSource value)
{
Value = value;
}
public readonly TSource Value;
public ChunkItem Next = null;
}

// The value that is used to determine matching elements


private readonly TKey key;

// Stores a reference to the enumerator for the source sequence


private IEnumerator<TSource> enumerator;

// A reference to the predicate that is used to compare keys.


private Func<TSource, bool> predicate;

// Stores the contents of the first source element that


// belongs with this chunk.
private readonly ChunkItem head;

// End of the list. It is repositioned each time a new


// ChunkItem is added.
private ChunkItem tail;

// Flag to indicate the source iterator has reached the end of the source sequence.
internal bool isLastSourceElement = false;

// Private object for thread syncronization


private object m_Lock;

// REQUIRES: enumerator != null && predicate != null


public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
{
this.key = key;
this.enumerator = enumerator;
this.predicate = predicate;

// A Chunk always contains at least one element.


head = new ChunkItem(enumerator.Current);

// The end and beginning are the same until the list contains > 1 elements.
tail = head;

m_Lock = new object();


}

// Indicates that all chunk elements have been copied to the list of ChunkItems,
// and the source enumerator is either at the end, or else on an element with a new key.
// the tail of the linked list is set to null in the CopyNextChunkElement method if the
// key of the next element does not match the current chunk's key, or there are no more elements
in the source.
private bool DoneCopyingChunk => tail == null;

// Adds one ChunkItem to the current group


// REQUIRES: !DoneCopyingChunk && lock(this)
private void CopyNextChunkElement()
{
// Try to advance the iterator on the source sequence.
// If MoveNext returns false we are at the end, and isLastSourceElement is set to true
isLastSourceElement = !enumerator.MoveNext();
// If we are (a) at the end of the source, or (b) at the end of the current chunk
// then null out the enumerator and predicate for reuse with the next chunk.
if (isLastSourceElement || !predicate(enumerator.Current))
{
enumerator = null;
predicate = null;
}
else
{
tail.Next = new ChunkItem(enumerator.Current);
}

// tail will be null if we are at the end of the chunk elements


// This check is made in DoneCopyingChunk.
tail = tail.Next;
}

// Called after the end of the last chunk was reached. It first checks whether
// there are more elements in the source sequence. If there are, it
// Returns true if enumerator for this chunk was exhausted.
internal bool CopyAllChunkElements()
{
while (true)
{
lock (m_Lock)
{
if (DoneCopyingChunk)
{
// If isLastSourceElement is false,
// it signals to the outer iterator
// to continue iterating.
return isLastSourceElement;
}
else
{
CopyNextChunkElement();
}
}
}
}

public TKey Key => key;

// Invoked by the inner foreach loop. This method stays just one step ahead
// of the client requests. It adds the next element of the chunk only after
// the clients requests the last element in the list so far.
public IEnumerator<TSource> GetEnumerator()
{
//Specify the initial element to enumerate.
ChunkItem current = head;

// There should always be at least one ChunkItem in a Chunk.


while (current != null)
{
// Yield the current item in the list.
yield return current.Value;

// Copy the next item from the source sequence,


// if we are at the end of our local list.
lock (m_Lock)
{
if (current == tail)
{
CopyNextChunkElement();
}
}

// Move to the next ChunkItem in the list.


current = current.Next;
current = current.Next;
}
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>


GetEnumerator();
}
}

// A simple named type is used for easier viewing in the debugger. Anonymous types
// work just as well with the ChunkBy operator.
public class KeyValPair
{
public string Key { get; set; }
public string Value { get; set; }
}

class Program
{
// The source sequence.
public static IEnumerable<KeyValPair> list;

// Query variable declared as class member to be available


// on different threads.
static IEnumerable<IGrouping<string, KeyValPair>> query;

static void Main(string[] args)


{
// Initialize the source sequence with an array initializer.
list = new[]
{
new KeyValPair{ Key = "A", Value = "We" },
new KeyValPair{ Key = "A", Value = "think" },
new KeyValPair{ Key = "A", Value = "that" },
new KeyValPair{ Key = "B", Value = "Linq" },
new KeyValPair{ Key = "C", Value = "is" },
new KeyValPair{ Key = "A", Value = "really" },
new KeyValPair{ Key = "B", Value = "cool" },
new KeyValPair{ Key = "B", Value = "!" }
};

// Create the query by using our user-defined query operator.


query = list.ChunkBy(p => p.Key);

// ChunkBy returns IGrouping objects, therefore a nested


// foreach loop is required to access the elements in each "chunk".
foreach (var item in query)
{
Console.WriteLine($"Group key = {item.Key}");
foreach (var inner in item)
{
Console.WriteLine($"\t{inner.Value}");
}
}

Console.WriteLine("Press any key to exit");


Console.ReadKey();
}
}
}

Per usare il metodo di estensione nel progetto, copiare la classe statica MyExtensions in un file di codice
sorgente nuovo o esistente e, se richiesto, aggiungere una direttiva using per lo spazio dei nomi in cui si trova.

Vedere anche
Language Integrated Query (LINQ)
Specificare dinamicamente i filtri dei predicati in
fase di esecuzione
18/03/2020 • 4 minutes to read • Edit Online

In alcuni casi, fino alla fase di esecuzione non si sa quanti predicati è necessario applicare agli elementi di origine
nella clausola where . Un modo per specificare dinamicamente più filtri di predicato consiste nell'usare il
metodo Contains, come illustrato nell'esempio seguente. L'esempio si costruisce in due modi. Innanzitutto viene
eseguito il progetto applicando filtri basati sui valori forniti nel programma. Il progetto viene quindi eseguito di
nuovo usando l'input fornito in fase di esecuzione.

Per applicare un filtro usando il metodo Contains


1. Aprire una nuova applicazione console e denominarla PredicateFilters .
2. Copiare la classe StudentClass da Eseguire query in una raccolta di oggetti e incollarla nello spazio dei
nomi PredicateFilters sotto la classe Program . StudentClass fornisce un elenco di oggetti Student .
3. Impostare il metodo Main come commento in StudentClass .
4. Sostituire la classe Program con il codice seguente:

class DynamicPredicates : StudentClass


{
static void Main(string[] args)
{
string[] ids = { "111", "114", "112" };

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}

static void QueryByID(string[] ids)


{
var queryNames =
from student in students
let i = student.ID.ToString()
where ids.Contains(i)
select new { student.LastName, student.ID };

foreach (var name in queryNames)


{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}
}

5. Aggiungere la riga seguente al metodo Main nella classe DynamicPredicates , sotto la dichiarazione di
ids .

QueryById(ids);

6. Eseguire il progetto.
7. Nella finestra della console verrà visualizzato l'output seguente:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
8. Il passaggio successivo consiste nell'eseguire nuovamente il progetto, questa volta usando l'input
immesso in fase di esecuzione anziché la matrice ids . Cambiare QueryByID(ids) in QueryByID(args) nel
metodo Main .
9. Eseguire il progetto con gli argomenti della riga di comando 122 117 120 115 . Quando il progetto viene
eseguito, questi valori diventano elementi di args , il parametro del metodo Main .
10. Nella finestra della console verrà visualizzato l'output seguente:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122

Per filtrare tramite un'istruzione switch


1. È possibile usare un'istruzione switch per scegliere tra query alternative predefinite. Nell'esempio
seguente studentQuery usa una clausola where diversa a seconda di quale livello o anno viene
specificato in fase di esecuzione.
2. Copiare il metodo seguente e incollarlo nella classe DynamicPredicates .
// To run this sample, first specify an integer value of 1 to 4 for the command
// line. This number will be converted to a GradeLevel value that specifies which
// set of students to query.
// Call the method: QueryByYear(args[0]);

static void QueryByYear(string level)


{
GradeLevel year = (GradeLevel)Convert.ToInt32(level);
IEnumerable<Student> studentQuery = null;
switch (year)
{
case GradeLevel.FirstYear:
studentQuery = from student in students
where student.Year == GradeLevel.FirstYear
select student;
break;
case GradeLevel.SecondYear:
studentQuery = from student in students
where student.Year == GradeLevel.SecondYear
select student;
break;
case GradeLevel.ThirdYear:
studentQuery = from student in students
where student.Year == GradeLevel.ThirdYear
select student;
break;
case GradeLevel.FourthYear:
studentQuery = from student in students
where student.Year == GradeLevel.FourthYear
select student;
break;

default:
break;
}
Console.WriteLine($"The following students are at level {year}");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}

3. Nel metodo Main sostituire la chiamata di QueryByID con la chiamata seguente che invia il primo
elemento dalla matrice args come suo argomento: QueryByYear(args[0]) .
4. Eseguire il progetto usando come argomento della riga di comando un valore intero compreso tra 1 e 4.

Vedere anche
Language Integrated Query (LINQ)
clausola where
Eseguire inner join
18/03/2020 • 14 minutes to read • Edit Online

In termini di database relazionale, un inner join produce un set di risultati in cui ogni elemento della prima
raccolta viene visualizzato una volta per ogni elemento corrispondente nella seconda raccolta. Se un elemento
nella prima raccolta non ha corrispondenti, non viene visualizzato nel set di risultati. Il metodo Join, chiamato
dalla clausola join in C#, implementa un inner join.
Questo articolo illustra come eseguire quattro varianti di un inner join:
Un inner join semplice che correla gli elementi di due origini dati in base a una chiave semplice.
Un inner join che correla gli elementi di due origini dati in base a una chiave composta. Una chiave
composta, che è una chiave costituita da più di un valore, consente di correlare gli elementi in base a più
di una proprietà.
Un join multiplo in cui le operazioni di join successive vengono aggiunte l'una all'altra.
Un inner join che viene implementato usando un group join.

Esempio - Join con chiave semplice


Nell'esempio seguente vengono create due raccolte che contengono oggetti di due tipi definiti dall'utente,
Person e Pet . La query usa la clausola join in C# per la corrispondenza degli oggetti Person con gli oggetti
Pet per il cui il valore di Owner è Person . La clausola select in C# definisce l'aspetto degli oggetti risultanti.
In questo esempio gli oggetti risultanti sono tipi anonimi costituiti dal nome del proprietario (owner) e dal nome
dell'animale domestico (pet).
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

/// <summary>
/// Simple inner join.
/// </summary>
public static void InnerJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

// Create a collection of person-pet pairs. Each element in the collection


// is an anonymous type containing both the person's name and their pet's name.
var query = from person in people
join pet in pets on person equals pet.Owner
select new { OwnerName = person.FirstName, PetName = pet.Name };

foreach (var ownerAndPet in query)


{
Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by {ownerAndPet.OwnerName}");
}
}

// This code produces the following output:


//
// "Daisy" is owned by Magnus
// "Barley" is owned by Terry
// "Boots" is owned by Terry
// "Whiskers" is owned by Charlotte
// "Blue Moon" is owned by Rui

Notare che l'oggetto Person il cui valore LastName è "Huff" non viene visualizzato nel set di risultati perché non
esiste un oggetto Pet con Pet.Owner uguale a Person .

Esempio - Join con chiave composta


Anziché correlare gli elementi in base a una sola proprietà, è possibile usare una chiave composta per
confrontare gli elementi in base a più proprietà. Per eseguire questa operazione, specificare la funzione del
selettore di chiave per ogni raccolta in modo da restituire un tipo anonimo che include le proprietà da
confrontare. Se si applicano etichette alle proprietà, l'etichetta deve essere la stessa in ogni tipo anonimo della
chiave. Le proprietà devono inoltre apparire nello stesso ordine.
L'esempio seguente usa un elenco di oggetti Employee e un elenco di oggetti Student per determinare quali
dipendenti sono anche studenti. Entrambi questi tipi presentano una proprietà FirstName e una proprietà
LastName di tipo String. Le funzioni che creano le chiavi di join dagli elementi di ogni elenco restituiscono un
tipo anonimo costituito dalle proprietà FirstName e LastName di ogni elemento. L'operazione di join confronta
le chiavi composte per verificarne l'uguaglianza e restituisce coppie di oggetti da ogni elenco in cui sia il nome
che il cognome corrispondono.

class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}

class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int StudentID { get; set; }
}

/// <summary>
/// Performs a join operation using a composite key.
/// </summary>
public static void CompositeKeyJoinExample()
{
// Create a list of employees.
List<Employee> employees = new List<Employee> {
new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 },
new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 },
new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 },
new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } };

// Create a list of students.


List<Student> students = new List<Student> {
new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 },
new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 },
new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } };

// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
IEnumerable<string> query = from employee in employees
join student in students
on new { employee.FirstName, employee.LastName }
equals new { student.FirstName, student.LastName }
select employee.FirstName + " " + employee.LastName;

Console.WriteLine("The following people are both employees and students:");


foreach (string name in query)
Console.WriteLine(name);
}

// This code produces the following output:


//
// The following people are both employees and students:
// Terry Adams
// Vernette Price

Esempio - Join multiplo


È possibile collegare tra loro qualsiasi numero di operazioni di join per eseguire un join multiplo. Ogni clausola
join in C# consente di correlare un'origine dati specificata con i risultati del join precedente.
Il seguente esempio crea tre raccolte: un elenco di oggetti Person , un elenco di oggetti Cat e un elenco di
oggetti Dog .
La prima clausola join in C# associa le persone e i gatti in base a un oggetto Person corrispondente a
Cat.Owner . Restituisce una sequenza di tipi anonimi che contengono l'oggetto Person e Cat.Name .

La seconda clausola join in C# consente di correlare i tipi anonimi restituiti dal primo join con gli oggetti Dog
dell'elenco di cani, in base a una chiave costituita dalla proprietà Owner del tipo Person e dalla prima lettera del
nome dell'animale. Restituisce una sequenza di tipi anonimi che contengono le proprietà Cat.Name e Dog.Name
di ogni coppia corrispondente. Poiché si tratta di un inner join, vengono restituiti solo gli oggetti della prima
origine dati per cui esiste una corrispondenza nella seconda origine dati.

class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

class Cat : Pet


{ }

class Dog : Pet


{ }

public static void MultipleJoinExample()


{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };
Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" };

Cat barley = new Cat { Name = "Barley", Owner = terry };


Cat boots = new Cat { Name = "Boots", Owner = terry };
Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte };
Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui };
Cat daisy = new Cat { Name = "Daisy", Owner = magnus };

Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis };
Dog duke = new Dog { Name = "Duke", Owner = magnus };
Dog denim = new Dog { Name = "Denim", Owner = terry };
Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
Dog snickers = new Dog { Name = "Snickers", Owner = arlene };

// Create three lists.


List<Person> people =
new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis };
List<Cat> cats =
new List<Cat> { barley, boots, whiskers, bluemoon, daisy };
List<Dog> dogs =
new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers };

// The first join matches Person and Cat.Owner from the list of people and
// cats, based on a common Person. The second join matches dogs whose names start
// with the same letter as the cats that have the same owner.
var query = from person in people
join cat in cats on person equals cat.Owner
join dog in dogs on
join dog in dogs on
new { Owner = person, Letter = cat.Name.Substring(0, 1) }
equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }
select new { CatName = cat.Name, DogName = dog.Name };

foreach (var obj in query)


{
Console.WriteLine(
$"The cat \"{obj.CatName}\" shares a house, and the first letter of their name, with \"
{obj.DogName}\".");
}
}

// This code produces the following output:


//
// The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
// The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".

Esempio - Inner join usando l'esempio con join raggruppati


Nell'esempio seguente viene illustrato come implementare un inner join usando un group join.
In query1 l'elenco di oggetti Person viene collegato con un group join all'elenco di oggetti Pet in base
all'oggetto Person corrispondente alla proprietà Pet.Owner . Il group join crea una raccolta di gruppi intermedi,
dove ogni gruppo è costituito da un oggetto Person e una sequenza di oggetti Pet corrispondenti.
Aggiungendo una seconda clausola from alla query, questa sequenza di sequenze viene combinata (o
appiattita) in un'unica sequenza più lunga. Il tipo degli elementi della sequenza finale viene specificato dalla
clausola select . In questo esempio il tipo è un tipo anonimo costituito dalle proprietà Person.FirstName e
Pet.Name per ogni coppia corrispondente.

Il risultato di query1 è equivalente al set di risultati che si ottiene usando la clausola join senza la clausola
into per eseguire un inner join. La variabile query2 dimostra questa query equivalente.

class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

/// <summary>
/// Performs an inner join by using GroupJoin().
/// </summary>
public static void InnerGroupJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

var query1 = from person in people


join pet in pets on person equals pet.Owner into gj
from subpet in gj
select new { OwnerName = person.FirstName, PetName = subpet.Name };

Console.WriteLine("Inner join using GroupJoin():");


foreach (var v in query1)
{
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}

var query2 = from person in people


join pet in pets on person equals pet.Owner
select new { OwnerName = person.FirstName, PetName = pet.Name };

Console.WriteLine("\nThe equivalent operation using Join():");


foreach (var v in query2)
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}

// This code produces the following output:


//
// Inner join using GroupJoin():
// Magnus - Daisy
// Terry - Barley
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers
//
// The equivalent operation using Join():
// Magnus - Daisy
// Terry - Barley
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers

Vedere anche
Join
GroupJoin
Eseguire join raggruppati
Eseguire outer join a sinistra
Tipi anonimi
Eseguire join raggruppati
02/11/2020 • 7 minutes to read • Edit Online

Il join di gruppo è utile per produrre strutture di dati gerarchiche. Abbina ogni elemento della prima raccolta con
un set di elementi correlati della seconda raccolta.
Ad esempio, una classe o una tabella di database relazionale denominata Student potrebbe contenere due
campi: Id e Name . Un'altra classe o tabella di database relazionale denominata Course potrebbe contenere
due campi: StudentId e CourseTitle . Un join di gruppo di queste due origini dati, basato sulla corrispondenza
di Student.Id e Course.StudentId raggrupperebbe ogni Student con una raccolta di oggetti Course (che può
essere vuota).

NOTE
Ogni elemento della prima raccolta viene visualizzato nel set di risultati di un join di gruppo indipendentemente dal fatto
che gli elementi correlati vengano trovati nella seconda raccolta. Nel caso in cui non venga trovato alcun elemento
correlato, la sequenza di elementi correlati per l'elemento è vuota. Il selettore del risultato ha pertanto accesso a ogni
elemento della prima raccolta. È diverso dal selettore del risultato in un join non di gruppo, che non può accedere a
elementi della prima raccolta che non hanno corrispondenza nella seconda raccolta.

WARNING
Enumerable.GroupJoin non ha equivalenti diretti in termini di database relazionali tradizionali. Tuttavia, questo metodo
implementa un superset di Inner join e left outer join. Entrambe queste operazioni possono essere scritte in base a un join
raggruppato. Per ulteriori informazioni, vedere operazioni di join e Entity Framework Core, GroupJoin.

Il primo esempio in questo articolo illustra come eseguire un join di gruppo. Il secondo esempio descrive come
usare un join di gruppo per creare elementi XML.

Esempio - Join di gruppo


L'esempio seguente esegue un join di gruppo di oggetti di tipo Person e Pet basato su Person
corrispondente alla proprietà Pet.Owner . Diversamente da un join non di gruppo che produrrebbe una coppia
di elementi per ogni corrispondenza, il join di gruppo produce un solo oggetto risultante per ogni elemento
della prima raccolta, che in questo esempio è un oggetto Person . Gli elementi corrispondenti della seconda
raccolta, che in questo esempio sono oggetti Pet vengono raggruppati in una raccolta. La funzione del
selettore del risultato crea infine un tipo anonimo per ogni corrispondenza costituita da Person.FirstName e una
raccolta di oggetti Pet .
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

/// <summary>
/// This example performs a grouped join.
/// </summary>
public static void GroupJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

// Create a list where each element is an anonymous type


// that contains the person's first name and a collection of
// pets that are owned by them.
var query = from person in people
join pet in pets on person equals pet.Owner into gj
select new { OwnerName = person.FirstName, Pets = gj };

foreach (var v in query)


{
// Output the owner's name.
Console.WriteLine($"{v.OwnerName}:");
// Output each of the owner's pet's names.
foreach (Pet pet in v.Pets)
Console.WriteLine($" {pet.Name}");
}
}

// This code produces the following output:


//
// Magnus:
// Daisy
// Terry:
// Barley
// Boots
// Blue Moon
// Charlotte:
// Whiskers
// Arlene:

Esempio - Join di gruppo per la creazione di XML


I join di gruppo sono ideali per la creazione di XML tramite LINQ to XML. L'esempio seguente è simile a quello
precedente tranne per il fatto che, invece di creare tipi anonimi, la funzione del selettore del risultato crea
elementi XML che rappresentano gli oggetti uniti in join.

class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

/// <summary>
/// This example creates XML output from a grouped join.
/// </summary>
public static void GroupJoinXMLExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

// Create XML to display the hierarchical organization of people and their pets.
XElement ownersAndPets = new XElement("PetOwners",
from person in people
join pet in pets on person equals pet.Owner into gj
select new XElement("Person",
new XAttribute("FirstName", person.FirstName),
new XAttribute("LastName", person.LastName),
from subpet in gj
select new XElement("Pet", subpet.Name)));

Console.WriteLine(ownersAndPets);
}

// This code produces the following output:


//
// <PetOwners>
// <Person FirstName="Magnus" LastName="Hedlund">
// <Pet>Daisy</Pet>
// </Person>
// <Person FirstName="Terry" LastName="Adams">
// <Pet>Barley</Pet>
// <Pet>Boots</Pet>
// <Pet>Blue Moon</Pet>
// </Person>
// <Person FirstName="Charlotte" LastName="Weiss">
// <Pet>Whiskers</Pet>
// </Person>
// <Person FirstName="Arlene" LastName="Huff" />
// </PetOwners>

Vedere anche
Join
GroupJoin
Eseguire inner join
Eseguire left outer join
Tipi anonimi
Eseguire left outer join
18/03/2020 • 3 minutes to read • Edit Online

Un left outer join è un join in cui viene restituito ogni elemento della prima raccolta, anche se non ha elementi
correlati nella seconda raccolta. È possibile usare LINQ per eseguire un left outer join chiamando il metodo
DefaultIfEmpty nei risultati di un join di gruppo.

Esempio
L'esempio seguente illustra come usare il metodo DefaultIfEmpty nei risultati di un join di gruppo per eseguire
un left outer join.
Il primo passaggio nella produzione di un left outer join di due raccolte consiste nell'esecuzione di un inner join
usando un join di gruppo. Per una spiegazione di questo processo, vedere Eseguire inner join. In questo
esempio, Person l'elenco di oggetti è Pet unito in Person join Pet.Owner interno all'elenco di oggetti in base a
un oggetto che corrisponde a .
Il secondo passaggio consiste nell'includere ogni elemento della prima raccolta di sinistra nel set di risultati
anche se l'elemento non ha corrispondenze nella raccolta di destra. Questa operazione viene eseguita
chiamando DefaultIfEmpty in ogni sequenza di elementi corrispondenti dal join di gruppo. In questo esempio
DefaultIfEmpty viene chiamato in ogni sequenza di oggetti Pet corrispondenti. Il metodo restituisce una
raccolta che contiene un solo valore predefinito se la sequenza di oggetti Pet corrispondenti è vuota per
qualsiasi oggetto Person , assicurando in questo modo che ogni oggetto Person sia rappresentato nella
raccolta di risultati.

NOTE
Il valore predefinito per un tipo di riferimento è null . Di conseguenza, l'esempio cerca un riferimento Null prima di
accedere a ogni elemento di ogni raccolta Pet .
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

public static void LeftOuterJoinExample()


{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

var query = from person in people


join pet in pets on person equals pet.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };

foreach (var v in query)


{
Console.WriteLine($"{v.FirstName+":",-15}{v.PetName}");
}
}

// This code produces the following output:


//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Terry: Blue Moon
// Charlotte: Whiskers
// Arlene:

Vedere anche
Join
GroupJoin
Eseguire inner join
Eseguire join raggruppati
Tipi anonimi
Ordinare i risultati di una clausola join
18/03/2020 • 2 minutes to read • Edit Online

Questo esempio descrive come ordinare i risultati di un'operazione di join. Si noti che l'ordinamento viene
eseguito dopo l'operazione di join. In genere, sebbene sia possibile, non è consigliabile usare una clausola
orderby con una o più sequenze di origine prima dell'operazione di join. Alcuni provider LINQ potrebbero non
mantenere tale ordinamento dopo l'operazione di join.

Esempio
Questa query crea un join di gruppo e quindi ordina i gruppi in base all'elemento categoria che è ancora
nell'ambito. Nell'inizializzatore di tipi anonimi una sottoquery ordina tutti gli elementi corrispondenti della
sequenza di prodotti.

class HowToOrderJoins
{
#region Data
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}

class Category
{
public string Name { get; set; }
public int ID { get; set; }
}

// Specify the first data source.


List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
new Category() { Name="Grains", ID=004},
new Category() { Name="Fruit", ID=005}
};

// Specify the second data source.


List<Product> products = new List<Product>()
{
new Product{Name="Cola", CategoryID=001},
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
};
#endregion
static void Main()
{
HowToOrderJoins app = new HowToOrderJoins();
app.OrderJoin1();

// Keep console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
Console.ReadKey();
}

void OrderJoin1()
{
var groupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
orderby category.Name
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};

foreach (var productGroup in groupJoinQuery2)


{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
Console.WriteLine($" {prodItem.Name,-10} {prodItem.CategoryID}");
}
}
}
/* Output:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Fruit
Melons 5
Peaches 5
Grains
Vegetables
Bok Choy 3
Carrots 3
*/
}

Vedere anche
Language Integrated Query (LINQ)
Clausola orderby
clausola join
Eseguire un join usando chiavi composte
18/03/2020 • 2 minutes to read • Edit Online

In questo esempio viene illustrato come eseguire operazioni di join in cui si vuole usare più di una chiave per
definire una corrispondenza. Ciò è possibile usando una chiave composta, che viene creata come tipo anonimo
o tipo denominato con i valori che si vogliono confrontare. Se la variabile di query viene passata attraverso i
limiti del metodo, usare un tipo denominato che esegue l'override di Equals e GetHashCode per la chiave. I nomi
delle proprietà e l'ordine in cui si verificano devono essere identici in ogni chiave.

Esempio
Nell'esempio seguente viene illustrato come usare una chiave composta per eseguire un join dei dati da tre
tabelle:

var query = from o in db.Orders


from p in db.Products
join d in db.OrderDetails
on new {o.OrderID, p.ProductID} equals new {d.OrderID, d.ProductID} into details
from d in details
select new {o.OrderID, p.ProductID, d.UnitPrice};

L'inferenza del tipo nelle chiavi composte dipende dai nomi delle proprietà nelle chiavi e dall'ordine in cui si
verificano. Se le proprietà nelle sequenze di origine non contengono gli stessi nomi, è necessario assegnare
nuovi nomi nelle chiavi. Ad esempio, se nella tabella Orders e nella tabella OrderDetails vengono usati nomi
diversi per le colonne, è possibile creare chiavi composte assegnando nomi identici nei tipi anonimi:

join...on new {Name = o.CustomerName, ID = o.CustID} equals


new {Name = d.CustName, ID = d.CustID }

Le chiavi composte possono essere usate anche nella clausola group .

Vedere anche
Language Integrated Query (LINQ)
clausola join
Clausola group
Eseguire operazioni di join personalizzate
18/03/2020 • 7 minutes to read • Edit Online

Questo esempio descrive come eseguire operazioni di join personalizzate non possibili con la clausola join . In
un'espressione di query la clausola join è limitata e ottimizzata per gli equijoin che sono in assoluto il tipo più
comune di operazione di join. Quando si esegue un equijoin, è quasi sempre possibile ottenere le prestazioni
migliori usando la clausola join .
Non è tuttavia possibile usare la clausola join nei casi seguenti:
Quando il join viene affermato su un'espressione di ineguaglianza (un non equijoin).
Quando il join viene affermato su più di un'espressione di eguaglianza o di ineguaglianza.
Quando è necessario introdurre una variabile di intervallo temporanea per la sequenza del lato destro
(interno) prima dell'operazione di join.
Per eseguire join che non sono equijoin, è possibile usare più clausole from per introdurre ogni origine dati in
modo indipendente. Si applica quindi un'espressione di predicato in una clausola where alla variabile di
intervallo per ogni origine. L'espressione può inoltre avere il formato di una chiamata al metodo.

NOTE
Non confondere questo tipo di operazione di join personalizzata con l'uso di più clausole from per accedere a raccolte
interne. Per ulteriori informazioni, vedere Clausola join.

Esempio
Il primo metodo nell'esempio seguente illustra un cross join semplice. I cross join devono essere usati con
attenzione perché possono produrre set di risultati molto grandi. Tuttavia, possono essere utili in alcuni scenari
per la creazione di sequenze di origine in cui eseguire query aggiuntive.
Il secondo metodo produce una sequenza di tutti i prodotti il cui ID categoria è incluso nell'elenco di categorie
sul lato sinistro. Si noti l'uso della clausola let e del metodo Contains per creare una matrice temporanea. È
anche possibile creare la matrice prima della query ed eliminare la prima clausola from .

class CustomJoins
{

#region Data

class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}

class Category
{
public string Name { get; set; }
public int ID { get; set; }
}

// Specify the first data source.


List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
};

// Specify the second data source.


List<Product> products = new List<Product>()
{
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
new Product{Name="Ice Cream", CategoryID=007},
new Product{Name="Mackerel", CategoryID=012},
};
#endregion

static void Main()


{
CustomJoins app = new CustomJoins();
app.CrossJoin();
app.NonEquijoin();

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}

void CrossJoin()
{
var crossJoinQuery =
from c in categories
from p in products
select new { c.ID, p.Name };

Console.WriteLine("Cross Join Query:");


foreach (var v in crossJoinQuery)
{
Console.WriteLine($"{v.ID,-5}{v.Name}");
}
}

void NonEquijoin()
{
var nonEquijoinQuery =
from p in products
let catIds = from c in categories
select c.ID
where catIds.Contains(p.CategoryID) == true
select new { Product = p.Name, CategoryID = p.CategoryID };

Console.WriteLine("Non-equijoin query:");
foreach (var v in nonEquijoinQuery)
{
Console.WriteLine($"{v.CategoryID,-5}{v.Product}");
}
}
}
/* Output:
Cross Join Query:
1 Tea
1 Mustard
1 Pickles
1 Carrots
1 Bok Choy
1 Peaches
1 Peaches
1 Melons
1 Ice Cream
1 Mackerel
2 Tea
2 Mustard
2 Pickles
2 Carrots
2 Bok Choy
2 Peaches
2 Melons
2 Ice Cream
2 Mackerel
3 Tea
3 Mustard
3 Pickles
3 Carrots
3 Bok Choy
3 Peaches
3 Melons
3 Ice Cream
3 Mackerel
Non-equijoin query:
1 Tea
2 Mustard
2 Pickles
3 Carrots
3 Bok Choy
Press any key to exit.
*/

Esempio
Nell'esempio seguente la query deve creare un join di due sequenze basate su chiavi corrispondenti che, nel
caso della sequenza interna (lato destro), non possono essere ottenute prima della clausola join stessa. Se il join
viene eseguito con una clausola join , il metodo Split dovrà essere chiamato per ogni elemento. L'uso di più
clausole from consente alla query di evitare l'overhead della chiamata al metodo ripetuta. Tuttavia, poiché
join è ottimizzata, in questo caso particolare potrebbe risultare ancora più veloce rispetto all'uso di più
clausole from . I risultati varieranno principalmente in base al costo in termini di utilizzo della chiamata al
metodo.

class MergeTwoCSVFiles
{
static void Main()
{
// See section Compiling the Code for information about the data files.
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

// Merge the data sources using a named type.


// You could use var instead of an explicit type for the query.
IEnumerable<Student> queryNamesScores =
// Split each line in the data files into an array of strings.
from name in names
let x = name.Split(',')
from score in scores
let s = score.Split(',')
// Look for matching IDs from the two data files.
where x[2] == s[0]
// If the IDs match, build a Student object.
select new Student()
{
FirstName = x[0],
LastName = x[1],
ID = Convert.ToInt32(x[2]),
ID = Convert.ToInt32(x[2]),
ExamScores = (from scoreAsText in s.Skip(1)
select Convert.ToInt32(scoreAsText)).
ToList()
};

// Optional. Store the newly created student objects in memory


// for faster access in future queries
List<Student> students = queryNamesScores.ToList();

foreach (var student in students)


{
Console.WriteLine($"The average score of {student.FirstName} {student.LastName} is
{student.ExamScores.Average()}.");
}

//Keep console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}

/* Output:
The average score of Omelchenko Svetlana is 82.5.
The average score of O'Donnell Claire is 72.25.
The average score of Mortensen Sven is 84.5.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Hugo is 85.75.
The average score of Tucker Lance is 81.75.
The average score of Adams Terry is 85.25.
The average score of Zabokritski Eugene is 83.
The average score of Tucker Michael is 92.
*/

Vedere anche
Language Integrated Query (LINQ)
clausola join
Ordinare i risultati di una clausola join
Gestire i valori Null nelle espressioni di query
26/03/2020 • 2 minutes to read • Edit Online

In questo esempio viene illustrato come gestire i possibili valori Null nelle raccolte di origine. Una raccolta di
oggetti, ad esempio IEnumerable<T>, può contenere elementi il cui valore è Null. Se una raccolta di origine è
Null o contiene un elemento il cui valore è Null e la query non gestisce valori Null, verrà generata un'eccezione
NullReferenceException quando si esegue la query.

Esempio
È possibile codificare in modo sicuro per evitare un'eccezione di riferimento Null come illustrato nell'esempio
seguente:

var query1 =
from c in categories
where c != null
join p in products on c.ID equals
p?.CategoryID
select new { Category = c.Name, Name = p.Name };

Nell'esempio precedente la clausola where esclude tutti gli elementi Null nella sequenza di categorie. Questa
tecnica è indipendente dal controllo Null nella clausola join. In questo esempio è possibile usare l'espressione
condizionale con Null poiché Products.CategoryID è di tipo int? , vale a dire una sintassi abbreviata di
Nullable<int> .

Esempio
In una clausola join, se solo una delle chiavi di confronto è un tipo di valore nullable, è possibile eseguire il cast
dell'altro a un tipo di valore nullable nell'espressione di query. Nell'esempio seguente si supponga che
EmployeeID sia una colonna contenente valori di tipo int? :

void TestMethod(Northwind db)


{
var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };
}

Vedere anche
Nullable<T>
Language Integrated Query (LINQ)
Tipi valore nullable
Gestire le eccezioni nelle espressioni di query
18/03/2020 • 3 minutes to read • Edit Online

Nel contesto di un'espressione di query è possibile chiamare qualsiasi metodo. È tuttavia consigliabile non
chiamare in un'espressione di query i metodi che possono creare un effetto collaterale, ad esempio la modifica
del contenuto dell'origine dati o la generazione di un'eccezione. Questo esempio illustra come evitare di
generare eccezioni quando si chiamano i metodi in un'espressione di query senza violare le linee guida generali
di .NET sulla gestione delle eccezioni. Tali linee guida stabiliscono che è accettabile intercettare un'eccezione
specifica quando è evidente il motivo per cui viene generata in un contesto specificato. Per altre informazioni,
vedere Suggerimenti per le eccezioni.
Nell'esempio finale viene illustrato come gestire quei casi in cui è necessario generare un'eccezione durante
l'esecuzione di una query.

Esempio
Nell'esempio seguente viene illustrato come spostare codice di gestione dell'eccezione al di fuori di
un'espressione di query. Questa operazione è possibile solo quando il metodo non dipende da variabili locali
per la query.
class ExceptionsOutsideQuery
{
static void Main()
{
// DO THIS with a datasource that might
// throw an exception. It is easier to deal with
// outside of the query expression.
IEnumerable<int> dataSource;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
// Handle (or don't handle) the exception
// in the way that is appropriate for your application.
Console.WriteLine("Invalid operation");
goto Exit;
}

// If we get here, it is safe to proceed.


var query = from i in dataSource
select i * i;

foreach (var i in query)


Console.WriteLine(i.ToString());

//Keep the console window open in debug mode


Exit:
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

// A data source that is very likely to throw an exception!


static IEnumerable<int> GetData()
{
throw new InvalidOperationException();
}
}

Esempio
In alcuni casi la migliore risposta a un'eccezione generata all'interno di una query potrebbe essere l'arresto
immediato dell'esecuzione della query. Nell'esempio seguente viene illustrato come gestire le eccezioni che
potrebbero essere generate all'interno del corpo di una query. Si supponga che SomeMethodThatMightThrow possa
potenzialmente generare un'eccezione che richiede l'arresto dell'esecuzione della query.
Si noti che il blocco try racchiude il ciclo foreach e non la query stessa, in quanto il ciclo foreach è il punto in
corrispondenza del quale la query viene effettivamente eseguita. Per altre informazioni, vedere Introduzione alle
query LINQ.
class QueryThatThrows
{
static void Main()
{
// Data source.
string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" };

// Demonstration query that throws.


var exceptionDemoQuery =
from file in files
let n = SomeMethodThatMightThrow(file)
select n;

// Runtime exceptions are thrown when query is executed.


// Therefore they must be handled in the foreach loop.
try
{
foreach (var item in exceptionDemoQuery)
{
Console.WriteLine($"Processing {item}");
}
}

// Catch whatever exception you expect to raise


// and/or do any necessary cleanup in a finally block
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}

//Keep the console window open in debug mode


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

// Not very useful as a general purpose method.


static string SomeMethodThatMightThrow(string s)
{
if (s[4] == 'C')
throw new InvalidOperationException();
return @"C:\newFolder\" + s;
}
}
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
*/

Vedere anche
Language Integrated Query (LINQ)
Programmazione asincrona
28/01/2021 • 20 minutes to read • Edit Online

Se si dispone di esigenze di I/O (ad esempio la richiesta di dati da una rete, l'accesso a un database o la lettura e
la scrittura in un file system), è consigliabile utilizzare la programmazione asincrona. Si potrebbe anche usare
codice associato alla CPU, ad esempio per eseguire un calcolo di spese, che rappresenta uno scenario
importante per scrivere codice asincrono.
In C# è disponibile un modello di programmazione asincrona a livello di linguaggio che consente di scrivere
facilmente codice asincrono senza dover manipolare i callback o conformarsi a una libreria che supporta
modalità asincrona. Questo modalità segue ciò che è noto come Task-based Asynchronous Pattern (TAP)
(Modello asincrono basato sull'attività).

Cenni preliminari sul modello asincrono


Il nucleo della programmazione asincrona è costituito da oggetti Task e Task<T> che modellano le operazioni
asincrone. Sono supportati dalle parole chiavi async e await . Il modello è piuttosto semplice nella maggior
parte dei casi:
Per il codice associato a I/O, si attende un'operazione che restituisce un oggetto Task o Task<T> all'interno
di un async metodo.
Per il codice associato alla CPU, si attende un'operazione avviata in un thread in background con il Task.Run
metodo.
La parola chiave await è l'elemento cruciale. Restituisce il controllo al chiamante del metodo che esegue await
ed è questo che in ultima analisi consente a un'interfaccia utente di essere reattiva o a un servizio di essere
flessibile. Sebbene esistano modi per approcciare codice asincrono diverso da async e await , questo articolo
è incentrato sui costrutti a livello di linguaggio.
Esempio di i/O associato: scaricare dati da un servizio Web
Potrebbe essere necessario scaricare alcuni dati da un servizio Web quando viene premuto un pulsante ma non
si vuole bloccare il thread dell'interfaccia utente. Questa operazione può essere eseguita in questo modo:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>


{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};

Il codice esprime l'intento (scaricando i dati in modo asincrono) senza rimanere impantanati nell'interazione con
Task gli oggetti.

Esempio associato alla CPU: eseguire un calcolo per un gioco


Si supponga di scrivere un gioco per dispositivi mobili in cui l'uso di un pulsante può causare danni a molti
nemici visualizzati sullo schermo. L'esecuzione del calcolo del danno può essere molto onerosa e in questo
modo il thread dell'interfaccia utente dà l'impressione che il gioco si arresti durante l'esecuzione del calcolo.
Il modo migliore per gestire questa situazione è avviare un thread in background, che esegue il lavoro
utilizzando Task.Run , e attenda il risultato utilizzando await . In questo modo l'interfaccia utente può risultare
uniforme quando il lavoro viene eseguito.

private DamageResult CalculateDamageDone()


{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>


{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};

Questo codice esprime chiaramente lo scopo dell'evento click del pulsante, ma non richiede la gestione manuale
di un thread in background e viene eseguito in modo non bloccante.
Operazioni eseguite in background
Sono molti gli elementi in cui sono coinvolte le operazioni asincrone. Per informazioni sulle operazioni eseguite
sotto le quinte di Task e Task<T> , vedere l'articolo di approfondimento asincrono per altre informazioni.
Sul lato C#, il compilatore trasforma il codice in una macchina a Stati che tiene traccia di elementi come la
produzione di un'esecuzione quando await viene raggiunto un oggetto e la ripresa dell'esecuzione al termine
di un processo in background.
Per gli utenti che amano la teoria, questa è un'implementazione del Modello futuro di asincronia.

Elementi chiave da comprendere


Il codice asincrono può essere usato per il codice associato a I/O e alla CPU, ma in modo diverso per ogni
scenario.
Il codice asincrono usa Task<T> e Task , che sono costrutti usati per modellare le operazioni eseguite in
background.
La parola chiave async trasforma un metodo in un metodo asincrono, che consente di usare la parola chiave
await nel relativo corpo.
Quando la parola chiave await viene applicata, interrompe il metodo di chiamata e restituisce il controllo al
chiamante fino al completamento dell'attività attesa.
await può essere usato solo all'interno di un metodo asincrono.

Riconoscere il lavoro associato alla CPU e I/O


I primi due esempi di questa guida hanno illustrato come usare async e per il lavoro associato a await I/O e
CPU. Si tratta di una chiave che è possibile identificare quando un processo da eseguire è associato a I/O o alla
CPU, in quanto può influire significativamente sulle prestazioni del codice e potrebbe causare un uso improprio
di determinati costrutti.
Rispondere a queste due domande prima di scrivere il codice:
1. Il codice deve "attendere" l'esecuzione di operazioni, ad esempio la ricezione di dati da un database?
Se la risposta è "Sì", l'operazione è associata a I/O .
2. Il codice eseguirà un calcolo costoso?
Se la risposta è "Sì", l'operazione è associata alla CPU .
Se l'operazione è associata a I/O , usare async e await senza Task.Run . Non si deve usare la libreria Task
Parallel Library. Il motivo è illustrato in modo asincrono in modo approfondito.
Se il lavoro di cui si dispone è associato alla CPU e si è interessati alla velocità di risposta, utilizzare async e
await , ma generare il lavoro in un altro thread con Task.Run . Se il lavoro è appropriato per la concorrenza e il
parallelismo, considerare anche l'uso del Task Parallel Library.
È anche necessario valutare sempre l'esecuzione del codice. Ad esempio, ci si potrebbe trovare in una situazione
in cui l'operazione associata alla CPU non è abbastanza onerosa confrontata al sovraccarico di commutazioni di
contesto durante il multithreading. Ogni scelta presenta un compromesso ed è necessario selezionare il
compromesso più adatto alla situazione.

Altri esempi
Gli esempi seguenti illustrano i diversi modi in cui è possibile scrivere codice asincrono in C#. Trattano scenari
diversi molto comuni.
Estrarre dati da una rete
Questo frammento Scarica il codice HTML dalla Home Page https://dotnetfoundation.org e conta il numero di
volte in cui la stringa ".NET" viene eseguita nel codice HTML. USA ASP.NET per definire un metodo del controller
API Web, che esegue questa attività e restituisce il numero.

NOTE
Se si prevede di eseguire l'analisi del codice HTML nel codice di produzione, non usare le espressioni regolari. Usare invece
una libreria di analisi.

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");

return Regex.Matches(html, @"\.NET").Count;


}

Di seguito viene illustrato lo stesso scenario scritto per un'app di Windows universale, che esegue la stessa
attività quando viene premuto un pulsante:
private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)


{
// Capture the task handle here so we can await the background task later.
var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");

// Any other work on the UI thread can be done here, such as enabling a Progress Bar.
// This is important to do here, before the "await" call, so that the user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;

// The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.


// This is what allows the app to be responsive and not block the UI thread.
var html = await getDotNetFoundationHtmlTask;
int count = Regex.Matches(html, @"\.NET").Count;

DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}

Attendi il completamento di più attività


Ci si potrebbe trovare in una situazione in cui è necessario recuperare più elementi di dati allo stesso tempo. L'
Task API contiene due metodi, Task.WhenAll e Task.WhenAny , che consentono di scrivere codice asincrono che
esegue un'attesa non di blocco su più processi in background.
Questo esempio illustra come è possibile acquisire dati User per un set di userId .

public async Task<User> GetUserAsync(int userId)


{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)


{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUserAsync(userId));
}

return await Task.WhenAll(getUserTasks);


}

Ecco un altro modo per scrivere questa operazione in modo più conciso, usando LINQ:
public async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)


{
var getUserTasks = userIds.Select(id => GetUserAsync(id));
return await Task.WhenAll(getUserTasks);
}

Sebbene sia meno codice, prestare attenzione quando si combina LINQ con codice asincrono. Poiché LINQ usa
un'esecuzione posticipata (lazy), le chiamate asincrone non verranno eseguite immediatamente come avviene in
un ciclo foreach a meno che non si forzi la sequenza generata per l'iterazione con una chiamata a .ToList() o
.ToArray() .

Informazioni importanti e consigli


Con la programmazione asincrona, è necessario tenere presenti alcuni dettagli che possono impedire un
comportamento imprevisto.
I metodi async devono avere una parola chiave await nel corpo, altrimenti non verranno
eseguiti.
Questo è importante da tenere presente. Se await non viene usato nel corpo di un async metodo, il
compilatore C# genera un avviso, ma il codice viene compilato ed eseguito come se fosse un metodo
normale. Si tratta di un'operazione estremamente inefficiente, perché la macchina a stati generata dal
compilatore C# per il metodo asincrono non esegue alcun risultato.
È consigliabile aggiungere "Async" come suffisso di ogni nome di metodo asincrono da
scrivere.
Si tratta della convenzione utilizzata in .NET per distinguere più facilmente i metodi sincroni e asincroni. Alcuni
metodi che non vengono chiamati in modo esplicito dal codice, ad esempio i gestori eventi o i metodi del
controller web, non sono necessariamente applicabili. Poiché non vengono chiamati in modo esplicito dal
codice, il fatto di essere espliciti sulla denominazione non è altrettanto importante.
async void deve essere usato solo per i gestori eventi.

async void è l'unico modo per consentire ai gestori eventi asincroni di funzionare correttamente, poiché gli
eventi non hanno tipi restituiti (quindi non possono usare Task e Task<T> ). Qualsiasi altro uso di async void
non segue il modello TAP e può essere difficile da usare, ad esempio:
async void Non è possibile intercettare le eccezioni generate in un metodo all'esterno di tale metodo.
async void i metodi sono difficili da testare.
async void i metodi possono causare effetti collaterali non validi se il chiamante non è in attesa di essere
asincrono.
Prestare attenzione quando si usano le espressioni lambda asincrone in espressioni LINQ
Le espressioni lambda in LINQ usano l'esecuzione posticipata, ovvero il codice potrebbe terminare l'esecuzione
in un momento in cui non è previsto. L'introduzione delle attività di blocco in questa operazione produce
facilmente un deadlock se il codice non è scritto in maniera corretta. L'annidamento di codice asincrono come
questo può anche rendere più difficile la valutazione dell'esecuzione del codice. Async e LINQ sono potenti, ma
devono essere usati insieme nel modo più accurato e chiaro possibile.
Scrivere codice che attende attività in modo non bloccante
Il blocco del thread corrente come mezzo per attendere il completamento di un oggetto Task può provocare
deadlock e thread di contesto bloccati e può richiedere una gestione degli errori più complessa. La tabella
seguente fornisce indicazioni su come gestire l'attesa di attività in modo non bloccante:

Q UA N DO SI DESIDERA ESEGUIRE
O P Z IO N E IN VEC E DI Q UESTO Q UESTA O P ERA Z IO N E. . .

await Task.Wait o Task.Result Recuperare il risultato di un'attività in


background

await Task.WhenAny Task.WaitAny Attendere che un'attività sia completa

await Task.WhenAll Task.WaitAll Attendere che tutte le attività siano


complete

await Task.Delay Thread.Sleep Attendere un periodo di tempo

Prendere in considerazione l'uso ValueTask di dove possibile

La restituzione di un oggetto Task dai metodi asincroni può introdurre colli di bottiglia delle prestazioni in
determinati percorsi. Task è un tipo di riferimento, quindi usarlo significa allocare un oggetto. Nei casi in cui un
metodo dichiarato con il async modificatore restituisce un risultato memorizzato nella cache o viene
completato in modo sincrono, le allocazioni aggiuntive possono diventare un costo significativo in termini di
tempo nelle sezioni del codice critiche per le prestazioni. Possono diventare onerose se si verificano in cicli
ridotti. Per altre informazioni, vedere tipi restituiti asincroni generalizzati.
Considerare l'uso dei moduli ConfigureAwait(false)

Una domanda comune è: "quando è consigliabile usare il Task.ConfigureAwait(Boolean) metodo?". Il metodo


consente a un' Task istanza di di configurare il relativo awaiter. Si tratta di una considerazione importante e
l'impostazione non corretta potrebbe potenzialmente avere implicazioni in termini di prestazioni e persino
deadlock. Per altre informazioni su ConfigureAwait , vedere le domande frequenti su ConfigureAwait.
Scrivere codice con meno dettagli sullo stato
Non dipendono dallo stato degli oggetti globali o dall'esecuzione di determinati metodi. È preferibile dipendere
dai valori restituiti dei metodi. Perché?
Sarà più facile valutare il codice.
Sarà più facile testare il codice.
La combinazione di codice sincrono e asincrono è molto più semplice.
È possibile evitare completamente le race condition.
La dipendenza dai valori restituiti semplifica il coordinamento di codice asincrono.
(Extra) funziona particolarmente bene con l'inserimento di dipendenze.
È consigliabile raggiungere una completa o quasi completa trasparenza referenziale nel codice. Ciò risulterà in
una base di codice completamente prevedibile, testabile e gestibile.

Altre risorse
L'articolo La programmazione asincrona in dettaglio offre altre informazioni sul funzionamento di Task.
Programmazione asincrona con Async e await (C#)
L'articolo Six Essential Tips for Async (Sei suggerimenti essenziali per la modalità asincrona) di Lucian
Wischik è una risorsa eccellente per la programmazione asincrona.
Criteri di ricerca
02/11/2020 • 24 minutes to read • Edit Online

I criteri verificano che un valore abbia una determinata forma e possono estrarre informazioni dal valore
quando ha la forma corrispondente. I criteri di ricerca offrono una sintassi più concisa per gli algoritmi già in
uso. Usando la sintassi esistente vengono già creati algoritmi di criteri di ricerca. Vengono scritte istruzioni if o
switch che testano i valori. Quindi, quando le istruzioni corrispondono, si estraggono e si usano le informazioni
del valore. I nuovi elementi di sintassi sono estensioni di istruzioni già note: is e switch . Queste nuove
estensioni associano il test di un valore all'estrazione delle informazioni.
In questo articolo viene descritta la nuova sintassi per illustrare come creare codice leggibile e conciso. I criteri di
ricerca consentono di usare termini in cui dati e codice sono separati, a differenza delle progettazioni orientate a
oggetti in cui i dati e i metodi che li modificano sono strettamente associati.
Per descrivere questi nuovi termini, verranno usate strutture che rappresentano forme geometriche usando
istruzioni di criteri di ricerca. È probabile che si abbia già acquisito dimestichezza con la compilazione delle
gerarchie di classi e la creazione di metodi virtuali e metodi sottoposti a override per personalizzare il
comportamento degli oggetti in base al tipo di runtime dell'oggetto.
Queste tecniche non sono possibili con dati che non sono strutturati in una gerarchia di classi. Quando dati e
metodi sono separati, è necessario usare altri strumenti. I costrutti dei nuovi criteri di ricerca offrono una sintassi
più chiara per esaminare i dati e modificare il flusso di controllo in base a qualsiasi condizione dei dati. Si
scrivono già istruzioni if e switch che testano il valore di una variabile. Si scrivono istruzioni is che testano
il tipo di una variabile. I criteri di ricerca aggiungono nuove funzionalità alle istruzioni.
In questo articolo verrà creato un metodo che calcola l'area di diverse forme geometriche. L'operazione verrà
tuttavia eseguita senza ricorrere a tecniche orientate a oggetti e compilare una gerarchia di classi per le diverse
forme. Verranno usati invece i criteri di ricerca. Procedendo nell'esempio, confrontare il codice a un eventuale
codice strutturato come gerarchia di oggetti. Quando i dati in cui è necessario eseguire le query e che devono
essere modificati non sono costituiti da una gerarchia di classi, i criteri di ricerca offrono progettazioni chiare.
Anziché iniziare con una definizione della forma astratta e l'aggiunta di diverse classi di forme specifiche, iniziare
con definizioni di soli dati semplici per ogni forma geometrica:
public class Square
{
public double Side { get; }

public Square(double side)


{
Side = side;
}
}
public class Circle
{
public double Radius { get; }

public Circle(double radius)


{
Radius = radius;
}
}
public struct Rectangle
{
public double Length { get; }
public double Height { get; }

public Rectangle(double length, double height)


{
Length = length;
Height = height;
}
}
public class Triangle
{
public double Base { get; }
public double Height { get; }

public Triangle(double @base, double height)


{
Base = @base;
Height = height;
}
}

Da queste strutture scrivere un metodo che calcola l'area di una forma.

Espressione del criterio di tipo is


Prima di C# 7.0, era necessario testare ogni tipo di una serie di istruzioni if e is :
public static double ComputeArea(object shape)
{
if (shape is Square)
{
var s = (Square)shape;
return s.Side * s.Side;
}
else if (shape is Circle)
{
var c = (Circle)shape;
return c.Radius * c.Radius * Math.PI;
}
// elided
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}

Il codice precedente è un'espressione classica del criterio di tipo: si esegue il test di una variabile per
determinarne il tipo ed eseguire un'azione diversa in base al tipo.
Questo codice diventa più semplice con l'uso di estensioni dell'espressione is per l'assegnazione di una
variabile se il test ha esito positivo:

public static double ComputeAreaModernIs(object shape)


{
if (shape is Square s)
return s.Side * s.Side;
else if (shape is Circle c)
return c.Radius * c.Radius * Math.PI;
else if (shape is Rectangle r)
return r.Height * r.Length;
// elided
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}

In questa versione aggiornata l'espressione is esegue il test della variabile e la assegna a una nuova variabile
del tipo appropriato. Si noti anche che questa versione include il tipo Rectangle che è uno struct . La nuova
espressione is può essere usata con tipi di valore e tipi di riferimento.
Le regole del linguaggio per i criteri di ricerca consentono di evitare un uso errato dei risultati di un'espressione
di confronto. Nell'esempio precedente le variabili s , c e r sono nell'ambito e assegnate definitivamente solo
quando le relative espressioni di criteri di ricerca hanno risultati true . Se si tenta di usare una variabile in un
altro percorso, il codice genera errori del compilatore.
Vengono ora esaminate entrambe le regole nel dettaglio iniziando con l'ambito. La variabile c è nell'ambito
solo nel ramo else della prima istruzione if . La variabile s è nell'ambito nel metodo ComputeAreaModernIs .
Ciò accade perché ogni ramo di un'istruzione if definisce un ambito separato per le variabili. Tuttavia,
l'istruzione if stessa non esegue questa operazione. Ciò significa che le variabili dichiarate nell' if istruzione
si trovano nello stesso ambito dell' if istruzione (in questo caso il metodo). Questo comportamento non è
specifico dei criteri di ricerca, ma è il comportamento definito per gli ambiti delle variabili e le istruzioni if e
else .

Le variabili c e s vengono assegnate quando le rispettive istruzioni if sono vere a causa del meccanismo di
assegnazione definitiva quando sono vere.
TIP
Gli esempi di questo argomento usano il costrutto consigliato dove un'espressione is di criteri di ricerca assegna in
modo definitivo la variabile di corrispondenza nel ramo true dell'istruzione if . È possibile invertire la logica
affermando che if (!(shape is Square s)) e la variabile s vengono assegnate definitivamente solo nel ramo
false . Sebbene sia valido in C#, questo approccio non è consigliabile poiché risulta più complesso seguire la logica.

Queste regole comportano l'impossibilità di accedere accidentalmente al risultato di un'espressione di criteri di


ricerca quando i criteri non vengono soddisfatti.

Uso di istruzioni switch di criteri di ricerca


È possibile sia necessario in seguito supportare altri tipi di forme. Man mano che aumenta il numero di
condizioni da testare, l'uso di espressioni di criteri di ricerca is può risultare troppo complesso. Oltre a
richiedere istruzioni if in ogni tipo da controllare, le espressioni is sono limitate all'esecuzione di test se
l'input corrisponde a un singolo tipo. In questo caso, le espressioni di criteri di ricerca switch risultano una
scelta migliore.
L'istruzione switch tradizionale era un'espressione di criteri che supportava i criteri costanti. Era possibile
confrontare una variabile con qualsiasi costante in un'istruzione case :

public static string GenerateMessage(params string[] parts)


{
switch (parts.Length)
{
case 0:
return "No elements to the input";
case 1:
return $"One element: {parts[0]}";
case 2:
return $"Two elements: {parts[0]}, {parts[1]}";
default:
return $"Many elements. Too many to write";
}
}

Gli unici criteri supportati dall'istruzione switch erano i criteri costanti. Era presente un'ulteriore limitazione ai
tipi numerici e al tipo string . Queste limitazioni sono state rimosse ed è ora possibile scrivere un'istruzione
switch usando i criteri di tipo:

public static double ComputeAreaModernSwitch(object shape)


{
switch (shape)
{
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Rectangle r:
return r.Height * r.Length;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
L'istruzione switch di criteri di ricerca usa una sintassi già nota agli sviluppatori che hanno usato l'istruzione
switch di tipo C. Viene valutata ogni istruzione case e viene eseguito il codice sottostante la condizione che
corrisponde alla variabile di input. L'esecuzione del codice non può "passare" da un'espressione case alla
successiva. La sintassi dell'istruzione case richiede che ogni case termini con break , return o goto .

NOTE
Le istruzioni goto per il passaggio a un'altra etichetta sono valide solo per il criterio costante, l'istruzione switch classica.

L'istruzione switch è governata da nuove regole importanti. Le limitazioni al tipo della variabile
nell'espressione switch sono state rimosse. È possibile usare tutti i tipi, ad esempio object . Le espressioni case
non sono più limitate ai valori costanti. La rimozione di questa limitazione significa che la ridisposizione delle
sezioni switch può modificare il comportamento di un programma.
Quando sono limitate ai valori costanti, solo un'etichetta case può corrispondere al valore dell'espressione
switch . Se a questo si aggiungeva la regola per la quale ogni sezione switch non doveva passare alla sezione
successiva ne conseguiva che le sezioni switch potevano essere ridisposte in qualsiasi ordine senza effetti sul
comportamento. Ora, con espressioni switch più generalizzate, l'ordine di ogni sezione è rilevante. Le
espressioni switch vengono valutate in ordine testuale. L'esecuzione si trasferisce alla prima etichetta switch
che corrisponde all'espressione switch .
Il case default verrà eseguito solo se non corrispondono altre etichette case. Il caso default viene valutato per
ultimo, indipendentemente dall'ordine testuale. Se non è presente alcun case default e nessuna delle altre
istruzioni case corrisponde, l'esecuzione continua con l'istruzione che segue l'istruzione switch . Non viene
eseguito il codice di alcuna etichetta case .

Clausole when nelle espressioni case


È possibile creare casi speciali per le forme con area 0 usando una clausola when nell'etichetta case . Un
quadrato con una lunghezza del lato pari a 0 o un cerchio con raggio pari a 0 ha un'area 0. Questa condizione
viene specificata usando una clausola when nell'etichetta case :

public static double ComputeArea_Version3(object shape)


{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
return 0;

case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}

Questa modifica illustra alcune considerazioni importanti sulla nuova sintassi. Innanzitutto, è possibile applicare
più etichette case a una sezione switch . Il blocco di istruzioni viene eseguito quando una delle etichette è
true . In questa istanza se l'espressione switch è un cerchio o un quadrato con area 0, il metodo restituisce la
costante 0.
Questo esempio illustra due variabili diverse in due etichette case per il primo blocco switch . Si noti che le
istruzioni in questo blocco switch non usano la variabile c per il cerchio o s per il quadrato. Nessuna delle
variabili è assegnata in modo definitivo in questo blocco switch . Se uno di questi casi corrisponde, significa che
una delle variabili è stata assegnata. Tuttavia, non è possibile sapere quale variabile è stata assegnata in fase di
compilazione poiché entrambi i case possono corrispondere in fase di esecuzione. Per questo motivo, nella
maggior parte dei casi in cui vengono usate più etichette case per lo stesso blocco, non verrà introdotta una
nuova variabile nell'istruzione case o verrà usata solo la variabile nella clausola when .
Dopo aver aggiunto le forme con area 0, si aggiungono due tipi di forme aggiuntivi, un rettangolo e un
triangolo:

public static double ComputeArea_Version4(object shape)


{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
case Triangle t when t.Base == 0 || t.Height == 0:
case Rectangle r when r.Length == 0 || r.Height == 0:
return 0;

case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height / 2;
case Rectangle r:
return r.Length * r.Height;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}

Questo set di modifiche aggiunge etichette case per il caso degenere ed etichette e blocchi per ogni nuova
forma.
È possibile infine aggiungere un case null per assicurarsi che l'argomento non sia null :
public static double ComputeArea_Version5(object shape)
{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
case Triangle t when t.Base == 0 || t.Height == 0:
case Rectangle r when r.Length == 0 || r.Height == 0:
return 0;

case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height / 2;
case Rectangle r:
return r.Length * r.Height;
case null:
throw new ArgumentNullException(paramName: nameof(shape), message: "Shape must not be null");
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}

Il comportamento speciale per il null modello è interessante perché la costante null nel modello non ha un
tipo, ma può essere convertita in qualsiasi tipo di riferimento o tipo di valore Nullable. Anziché convertire una
costante null in un tipo, il linguaggio definisce che un valore null non corrisponderà ad alcun criterio del
tipo, indipendentemente dal tipo in fase di compilazione della variabile. Questo comportamento rende il nuovo
criterio del tipo basato su switch coerente con l'istruzione is : le istruzioni is restituiscono sempre false
quando il valore controllato è null . È anche più semplice: dopo aver controllato il tipo, non è necessario un
controllo null aggiuntivo. Questo è dimostrato dal fatto che non è presente alcun controllo null in nessuno dei
blocchi di case degli esempi precedenti: i controlli non sono necessari poiché la corrispondenza del criterio del
tipo garantisce già un valore non null.

Dichiarazioni var nelle espressioni case


L'introduzione di var come una delle espressioni di corrispondenza introduce nuove regole per i criteri di
ricerca.
La prima regola è che la dichiarazione var segue le normali regole di inferenza del tipo: il tipo viene dedotto
come tipo statico dell'espressione switch. In base a questa regola, il tipo corrisponde sempre.
La seconda regola è che una dichiarazione var non include il controllo Null incluso in altre espressioni di
criterio del tipo. Questo significa che la variabile può essere Null e in questo caso è necessario il controllo Null.
Queste due regole indicano che, in molti casi, una dichiarazione var in un'espressione case corrisponde alle
stesse condizioni di un'espressione default . Dato che qualsiasi case non predefinito è preferibile al case
default , il case default non verrà mai eseguito.

NOTE
Il compilatore non genera un avviso nei casi in cui è stato scritto un case default che non verrà mai eseguito. Ciò è
coerente con il comportamento corrente dell'istruzione switch in cui sono stati elencati tutti i case possibili.

La terza regola introduce gli usi per cui può essere utile un case var . Si supponga di eseguire una
corrispondenza di criterio in cui l'input è una stringa e si stanno cercando valori di comando noti. È possibile
scrivere codice simile al seguente:

static object CreateShape(string shapeDescription)


{
switch (shapeDescription)
{
case "circle":
return new Circle(2);

case "square":
return new Square(4);

case "large-circle":
return new Circle(12);

case var o when (o?.Trim().Length ?? 0) == 0:


// white space
return null;
default:
return "invalid shape description";
}
}

Il case var corrisponde a null , una stringa vuota o qualsiasi stringa che contiene solo spazi vuoti. Si noti che il
codice precedente usa l'operatore ?. per assicurarsi che non venga generata accidentalmente un'eccezione
NullReferenceException. Il case default gestisce qualsiasi altro valore stringa non riconosciuto dal parser dei
comandi.
Questo è un esempio in cui si può prendere in considerazione l'uso di un'espressione case var distinta da
un'espressione default .

Conclusioni
I costrutti dei criteri di ricerca consentono di gestire in modo semplice il flusso di controllo tra variabili e tipi
diversi non correlati a una gerarchia di ereditarietà. È anche possibile controllare la logica per usare qualsiasi
condizione testata sulla variabile. Sono anche disponibili criteri e termini che è possibile usare per compilare
applicazioni più distribuite, dove i dati e i metodi che modificano i dati sono separati. Sarà possibile notare che
gli struct delle forme usati nell'esempio non contengono metodi, ma solo proprietà di sola lettura. I criteri di
ricerca possono essere usati con qualsiasi tipo di dati. Vengono scritte espressioni che esaminano l'oggetto ed
eseguono decisioni per il flusso di controllo in base alle condizioni.
Confrontare il codice dell'esempio con la progettazione che deriverebbe dalla creazione di una gerarchia di
classi per una Shape astratta e le forme derivate specifiche ognuna con la propria implementazione di un
metodo virtuale per il calcolo dell'area. Spesso sarà possibile osservare che le espressioni di criteri di ricerca
possono essere uno strumento utile quando si usano dati e si vogliono separare le esigenze di archiviazione di
dati da quelle di comportamento.

Vedere anche
Esercitazione: usare i criteri di ricerca per creare algoritmi basati sui tipi e basati sui dati
Scrivere codice C# sicuro ed efficiente
22/04/2020 • 32 minutes to read • Edit Online

Le nuove funzionalità di C# consentono di scrivere codice sicuro verificabile con prestazioni migliori. Se si
applicano queste tecniche attentamente, un numero inferiore di scenari richiede codice non gestito. Queste
funzionalità semplificano l'uso dei riferimenti ai tipi valore come argomenti di metodo e valori restituiti di
metodi. Se eseguite in modo sicuro, queste tecniche riducono al minimo la copia dei tipi valore. Usando i tipi
valore, è possibile ridurre al minimo il numero di allocazioni e di passaggi di Garbage Collection.
Gran parte del codice di esempio in questo articolo usa le funzionalità aggiunte in C# 7.2. Per usare tali
funzionalità, è necessario configurare il progetto per l'uso di C# 7.2 o versione successiva. Per ulteriori
informazioni sull'impostazione della versione della lingua, vedere configurare la versione della lingua.
Questo articolo è incentrato sulle tecniche per la gestione efficiente delle risorse. Un vantaggio dell'uso dei tipi
valore è che spesso evitano le allocazioni di heap. Lo svantaggio è invece che vengono copiati in base al valore.
Questo compromesso rende più difficile ottimizzare gli algoritmi che operano su grandi quantità di dati. Le
nuove funzionalità del linguaggio C# 7.2 forniscono meccanismi che abilitano il codice sicuro efficiente tramite i
riferimenti ai tipi valore. Usare queste funzionalità con criterio, per ridurre al minimo sia le allocazioni che le
operazioni di copia. Questo articolo esamina le nuove funzionalità.
Questo articolo è incentrato sulle tecniche per la gestione delle risorse seguenti:
Dichiarare readonly struct un per esprimere che un tipo non è modificabile. Ciò consente al compilatore
di in salvare copie difensive quando si utilizzano parametri.
Se un tipo non può essere immutabile, dichiarare struct i membri readonly per indicare che il membro
non modifica lo stato.
Utilizzare ref readonly un valore restituito quando struct il IntPtr.Size valore restituito è maggiore di e la
durata di archiviazione è maggiore del metodo che restituisce il valore.
Quando le dimensioni di readonly struct sono maggiori di IntPtr.Size, è consigliabile passarle come
parametro in per motivi di prestazioni.
Non passare struct mai in un come parametro readonly a meno che readonly non venga dichiarato con
il modificatore o il metodo non chiami solo i membri dello struct. La violazione di queste indicazioni può
influire negativamente sulle prestazioni e potrebbe portare a un comportamento oscuro.
Utilizzare ref struct un oggetto readonly ref struct , Span<T> ReadOnlySpan<T> o ad esempio o per
lavorare con la memoria come sequenza di byte.
Queste tecniche obbligano a trovare un compromesso tra due obiettivi opposti in termini di riferimenti e
valori . Le variabili che sono tipi riferimento contengono un riferimento alla posizione in memoria. Le variabili
che sono tipi valore contengono direttamente il rispettivo valore. Queste caratteristiche evidenziano le principali
differenze da tenere in considerazione per la gestione delle risorse della memoria. I tipi valore vengono in
genere copiati se passati a un metodo o restituiti da un metodo. Questo comportamento include la copia del
valore di this quando si chiamano i membri di un tipo valore. Il costo della copia è correlato alla dimensione
del tipo. I tipi riferimento vengono allocati nell'heap gestito. Ogni nuovo oggetto richiede una nuova
allocazione e successivamente deve essere recuperato. Entrambe queste operazioni richiedono tempo. Il
riferimento viene copiato quando un tipo riferimento viene passato come argomento a un metodo o restituito
da un metodo.
Questo articolo usa il concetto di esempio seguente relativo alla struttura di punti 3D per illustrare queste
raccomandazioni:
public struct Point3D
{
public double X;
public double Y;
public double Z;
}

Le implementazioni di questo concetto variano a seconda degli esempi.

Dichiarare struct readonly per tipi valore non modificabili


Dichiarando uno struct tramite il modificatore readonly , si informa il compilatore che la finalità è la creazione
di un tipo non modificabile. Il compilatore applica tale decisione di progettazione con le regole seguenti:
Tutti i membri dei campi devono essere readonly
Tutte le proprietà devono essere di sola lettura, incluse le proprietà implementate automaticamente.
Queste due regole sono sufficienti a garantire che nessun membro di un readonly struct modifichi lo stato di
tale struct. Lo struct non è modificabile. La struttura Point3D può essere definita come struct non
modificabile, come illustrato nell'esempio seguente:

readonly public struct ReadonlyPoint3D


{
public ReadonlyPoint3D(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}

public double X { get; }


public double Y { get; }
public double Z { get; }
}

Seguire questa raccomandazione quando la finalità della progettazione è la creazione di un tipo valore non
modificabile. Qualsiasi miglioramento delle prestazioni comporta un ulteriore vantaggio. Il readonly struct
esprime chiaramente la finalità della progettazione.

Dichiarare membri readonly quando uno struct non può essere


immutabileDeclare readonly members when a struct can't be
immutable
Quando un tipo struct è modificabile, in Cè 8.0 e versioni successive, readonly è necessario dichiarare i membri
che non causano la mutazione. Si consideri un'applicazione diversa che richiede una struttura di punti 3D, ma
deve supportare la mutabilità. La versione seguente della struttura dei readonly punti 3D aggiunge il
modificatore solo ai membri che non modificano la struttura. Seguire questo esempio quando la progettazione
deve supportare le modifiche apportate allo struct da parte di alcuni membri, ma si desidera comunque i
vantaggi derivanti dall'applicazione di readonly su alcuni membri:
public struct Point3D
{
public Point3D(double x, double y, double z)
{
_x = x;
_y = y;
_z = z;
}

private double _x;


public double X
{
readonly get => _x;
set => _x = value;
}

private double _y;


public double Y
{
readonly get => _y;
set => _y = value;
}

private double _z;


public double Z
{
readonly get => _z;
set => _z = value;
}

public readonly double Distance => Math.Sqrt(X * X + Y * Y + Z * Z);

public readonly override string ToString() => $"{X}, {Y}, {Z}";


}

Nell'esempio precedente vengono illustrate molte delle readonly posizioni in cui è possibile applicare il
modificatore: metodi, proprietà e funzioni di accesso alle proprietà. Se si utilizzano proprietà implementate
readonly automaticamente, get il compilatore aggiunge il modificatore alla funzione di accesso per le
proprietà di lettura/scrittura. Il compilatore readonly aggiunge il modificatore alle dichiarazioni di get
proprietà implementate automaticamente per le proprietà con solo una funzione di accesso.
L'aggiunta del readonly modificatore ai membri che non modificano lo stato offre due vantaggi correlati. In
primo luogo, il compilatore applica l'intento. Tale membro non può mutare lo stato dello struct. In secondo
luogo, il compilatore in non creerà readonly copie difensive dei parametri quando si accede a un membro. Il
compilatore può rendere questa ottimizzazione struct in modo readonly sicuro perché garantisce che
l'oggetto non viene modificato da un membro.

Usare le istruzioni ref readonly return per le strutture di grandi


dimensioni, se possibile
È possibile restituire i valori per riferimento quando il valore restituito non è locale per il metodo di restituzione.
Con la restituzione per riferimento viene copiato solo il riferimento, non la struttura. Nell'esempio seguente la
proprietà Origin non può usare un valore restituito ref perché il valore da restituire è una variabile locale:

public Point3D Origin => new Point3D(0,0,0);

La definizione della proprietà seguente può tuttavia essere restituita per riferimento perché il valore restituito è
un membro statico:
public struct Point3D
{
private static Point3D origin = new Point3D(0,0,0);

// Dangerous! returning a mutable reference to internal storage


public ref Point3D Origin => ref origin;

// other members removed for space


}

Per fare in modo che i chiamanti non modifichino l'origine, restituire il valore per ref readonly :

public struct Point3D


{
private static Point3D origin = new Point3D(0,0,0);

public static ref readonly Point3D Origin => ref origin;

// other members removed for space


}

La restituzione di ref readonly consente un risparmio a livello di copie di strutture di dimensioni maggiori e di
mantenere l'immutabilità dei membri dati interni.
Nel sito della chiamata i chiamanti scelgono di usare la proprietà Origin come ref readonly o come valore:

var originValue = Point3D.Origin;


ref readonly var originReference = ref Point3D.Origin;

La prima assegnazione del codice precedente crea una copia della costante Origin e assegna tale copia. La
seconda assegna un riferimento. Si noti che il modificatore readonly deve fare parte della dichiarazione della
variabile. Il riferimento a cui viene fatto riferimento non può essere modificato. I tentativi di eseguire questa
operazione generano un errore in fase di compilazione.
Il modificatore readonly è necessario nella dichiarazione di originReference .
Il compilatore fa in modo che il chiamante non possa modificare il riferimento. I tentativi di assegnazione diretta
del valore generano un errore in fase di compilazione. Il compilatore non può tuttavia sapere se un metodo del
membro modifica lo stato dello struct. Per assicurarsi che l'oggetto non venga modificato, il compilatore crea
una copia e chiama i riferimenti al membro usando tale copia. Tutte le modifiche vengono apportate a tale copia
difensiva.

Applicare il modificatore in ai parametri readonly struct maggiori di


System.IntPtr.Size
La parola chiave in è completare alle parole chiave ref e out esistenti per passare gli argomenti per
riferimento. La parola chiave in specifica il passaggio dell'argomento per riferimento, ma il metodo chiamato
non modifica il valore.
Questa aggiunta fornisce un vocabolario completo per esprimere le finalità di progettazione. I tipi valore
vengono copiati quando vengono passati a un metodo chiamato se nella firma del metodo non si specifica
alcuno dei modificatori seguenti. Ognuno di questi modificatori specifica che una variabile viene passata per
riferimento, evitando la copia. Ogni modificatore esprime una finalità diversa:
out : questo metodo imposta il valore dell'argomento usato come parametro.
ref : questo metodo può impostare il valore dell'argomento usato come parametro.
in : questo metodo non modifica il valore dell'argomento utilizzato come parametro.

Aggiungere il modificatore in per passare un argomento per riferimento e dichiarare che la finalità di passare
argomenti per riferimento, per evitare operazioni di copia non necessarie, non quella di modificare l'oggetto
usato come argomento.
Questa procedura spesso migliora le prestazioni per i tipi valore readonly di dimensioni superiori a IntPtr.Size.
Per i tipi semplici (tipi sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double ,
decimal e bool ed enum ), i potenziali miglioramenti alle prestazioni sono minimi. Le prestazioni potrebbero
infatti peggiorare usando il passaggio per riferimento per i tipi con dimensioni inferiori a IntPtr.Size.
Il codice seguente illustra un esempio di metodo che calcola la distanza tra due punti nello spazio 3D.

private static double CalculateDistance(in Point3D point1, in Point3D point2)


{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;

return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);


}

Gli argomenti sono due strutture, contenenti ognuna tre valori double. Un valore double è pari a 8 byte, quindi
ogni argomento è pari a 24 byte. Specificando il modificatore in , si passa un riferimento a 4 byte o 8 byte a tali
argomenti, a seconda dell'architettura del computer. La differenza a livello di dimensioni è piccola, ma aumenta
quando l'applicazione chiama questo metodo in un ciclo ridotto usando più valori diversi.
Il modificatore in integra out e ref anche in altri modi. Non è possibile creare overload di un metodo che si
distinguono solo per la presenza di in , out o ref . Queste nuove regole estendono lo stesso comportamento
da sempre definito per i parametri out e ref . Come i modificatori out e ref , i tipi valore non sono boxed
perché viene applicato il modificatore in .
Il modificatore in può essere applicato a tutti i membri che accettano parametri: metodi, delegati, espressioni
lambda, funzioni locali, indicizzatori, operatori.
Un'altra caratteristica dei parametri in è che è possibile usare valori letterali o costanti per l'argomento in un
parametro in . Inoltre, diversamente da un parametro ref o out , non è necessario applicare il modificatore
in nel sito di chiamata. Il codice seguente illustra due esempi di chiamata al metodo CalculateDistance . Il
primo usa due variabili locali passate per riferimento. Il secondo include una variabile temporanea creata
durante la chiamata al metodo.

var distance = CalculateDistance(pt1, pt2);


var fromOrigin = CalculateDistance(pt1, new Point3D());

Il compilatore applica la natura di sola lettura di un argomento in in diversi modi. Prima di tutto il metodo
chiamato non può essere direttamente assegnato a un parametro in . Non può essere direttamente assegnato
ai campi di un parametro in se quel valore è un tipo struct . Non è neppure possibile passare un parametro
in a metodi che usano il modificatore ref o out . Queste regole sono valide per qualsiasi campo di un
parametro in , a condizione che il campo sia di tipo struct e che anche il parametro sia di tipo struct .
Queste regole, in effetti, vengono applicate per più livelli di accesso ai membri, a condizione che a tutti i livelli di
accesso ai membri i tipi siano structs . Il compilatore impone che i tipi struct passati come argomenti in e i
relativi membri struct siano variabili di sola lettura, se usati come argomenti per altri metodi.
L'uso di parametri in consente di evitare i costi potenziali sulle prestazioni per l'esecuzione di copie, ma non
modifica la semantica delle chiamate a metodi. Non è quindi necessario specificare il modificatore in presso il
sito di chiamata. Omettendo il modificatore in presso il sito di chiamata si informa il compilatore che può
effettuare una copia dell'argomento per i motivi seguenti:
Esiste una conversione implicita, ma non una conversione di identità dal tipo di argomento al tipo di
parametro.
L'argomento è un'espressione, ma non ha una variabile di archiviazione nota.
È presente un overload diverso per la presenza o l'assenza di in . In tal caso, l'overload in base al valore è
una corrispondenza migliore.
Queste regole sono utili quando si aggiorna codice esistente per usare argomenti di riferimento di sola lettura.
All'interno del metodo chiamato è possibile chiamare qualsiasi metodo di istanza che usa parametri in base al
valore. In tali istanze viene creata una copia del parametro in . Poiché il compilatore può creare una variabile
temporanea per qualsiasi parametro in , è anche possibile specificare i valori predefiniti per qualsiasi
parametro in . Il codice seguente specifica l'origine (punto 0,0) come valore predefinito per il secondo punto:

private static double CalculateDistance2(in Point3D point1, in Point3D point2 = default)


{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;

return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);


}

Per imporre al compilatore di passare argomenti di sola lettura per riferimento, specificare il modificatore in
per gli argomenti presso il sito di chiamata, come illustrato nel codice seguente:

distance = CalculateDistance(in pt1, in pt2);


distance = CalculateDistance(in pt1, new Point3D());
distance = CalculateDistance(pt1, in Point3D.Origin);

Questo comportamento semplifica nel tempo l'adozione di parametri in in codebase di grandi dimensioni in
cui è possibile ottenere miglioramenti delle prestazioni. È prima necessario aggiungere il modificatore in alle
firme dei metodi. È quindi possibile aggiungere il modificatore in presso i siti di chiamata e creare tipi
readonly struct per consentire al compilatore di evitare di creare copie difensive di parametri in in altre
posizioni.
La designazione del parametro in può anche essere usata con tipi riferimento o valori numerici. Gli eventuali
vantaggi in entrambi i casi sono tuttavia minimi.

Evitare struct modificabili in come argomentoAvoid mutable structs


as an argument
Le tecniche descritte in precedenza illustrano come evitare le copie restituendo i riferimenti e passando i valori
per riferimento. Queste tecniche garantiscono prestazioni ottimali quando i tipi di argomenti vengono dichiarati
come tipi readonly struct . In caso contrario, il compilatore deve creare copie difensive in molte situazioni per
applicare la natura di sola lettura degli argomenti. Considerare l'esempio seguente che calcola la distanza di un
punto 3D dall'origine:
private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;

return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);


}

La struttura Point3D non è uno struct readonly. Sono presenti sei diverse chiamate di accesso a proprietà nel
corpo di questo metodo. A un primo esame, si potrebbe pensare che questi accessi siano sicuri. Una funzione di
accesso get infatti non dovrebbe modificare lo stato dell'oggetto, ma nessuna regola del linguaggio lo impone.
È solo una convenzione comune. Qualsiasi tipo può implementare una funzione di accesso get che modifica lo
stato interno. Senza una certa garanzia del linguaggio, il compilatore deve creare readonly una copia
temporanea dell'argomento prima di chiamare qualsiasi membro non contrassegnato con il modificatore. La
risorsa di archiviazione temporanea viene creata nello stack, i valori dell'argomento vengono copiati nella
risorsa di archiviazione temporanea e il valore viene copiato nello stack per ogni accesso ai membri come
argomento this . In molte situazioni, queste copie danneggiano le prestazioni in modo sufficiente che il pass-
by-value è readonly struct più veloce del riferimento pass-by-readonly quando il tipo di argomento non è un e
il metodo chiama membri non contrassegnati readonly con . Se si contrassegnano tutti i metodi readonly che
non modificano lo stato struct come , il compilatore può determinare in modo sicuro che lo stato struct non
viene modificato e non è necessaria una copia difensiva.
Al contrario, se il calcolo della ReadonlyPoint3D distanza utilizza lo struct non modificabile, non sono necessari
oggetti temporanei:

private static double CalculateDistance3(in ReadonlyPoint3D point1, in ReadonlyPoint3D point2 = default)


{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;

return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);


}

Il compilatore genera codice più efficiente readonly struct quando this si chiamano i membri di un in
oggetto : il riferimento, anziché una copia del ricevitore, è sempre un parametro passato per riferimento al
metodo membro. Questa ottimizzazione consente un risparmio a livello di copie quando si usa readonly struct
come argomento in .
Non è necessario passare un tipo di in valore nullable come argomento. Il Nullable<T> tipo non è dichiarato
come struct di sola lettura. Ciò significa che il compilatore deve generare le copie difensive di tutti gli argomenti
del valore di tipo nullable passato a un metodo tramite il modificatore in nella dichiarazione del parametro.
È possibile visualizzare un programma di esempio che illustra le differenze di prestazioni utilizzando
BenchmarkDotNet nel repository degli esempi su GitHub.You can see an example program that demonstrates
the performance differences using BenchmarkDotNet in our samples repository on GitHub. Viene confrontato il
passaggio di uno struct modificabile per valore e per riferimento con il passaggio di uno struct non modificabile
per valore e per riferimento. L'uso dello struct non modificabile e del passaggio per riferimento è più veloce.

Usare i tipi ref struct per lavorare con i blocchi o la memoria in un


singolo stack frame
Una funzionalità del linguaggio correlata è la possibilità di dichiarare un tipo valore che deve essere vincolato a
un singolo stack frame. Questa restrizione consente al compilatore di eseguire diverse ottimizzazioni. La
principale motivazione di questa funzionalità sono stati Span<T> e le strutture correlate. Da questi
miglioramenti si otterranno prestazioni più avanzate grazie alle API .NET nuove e aggiornate che usano il tipo
Span<T>.
È possibile avere requisiti simili stackalloc quando si usa la memoria creata con o quando si usa memoria
dalle API di interoperabilità. È possibile definire i tipi ref struct in base a tali esigenze.

Tipo di readonly ref struct


Dichiarando uno struct come readonly ref si combinano i vantaggi e le restrizioni delle dichiarazioni
ref struct e readonly struct . La memoria usata dallo span di sola lettura è limitata a un singolo stack frame e
non può essere modificata.

Conclusioni
L'uso di tipi valore ridice al minimo il numero di operazioni di allocazione:
Lo spazio di archiviazione per i tipi valore viene allocato nello stack per le variabili locali e gli argomenti dei
metodi.
Lo spazio di archiviazione per i tipi valore membri di altri oggetti viene allocato come parte dell'oggetto, non
come allocazione separata.
Lo spazio di archiviazione per i valori restituiti dei tipi valore viene allocato nello stack.
Confrontare questi tipi con i tipi riferimento nelle stesse situazioni:
Lo spazio di archiviazione per i tipi riferimento viene allocato nell'heap per le variabili locali e gli argomenti
dei metodi. Il riferimento è archiviato nello stack.
Lo spazio di archiviazione per i tipi riferimento membri di altri oggetti viene allocato separatamente
nell'heap. L'oggetto contenitore archivia il riferimento.
Lo spazio di archiviazione per i valori restituiti dei tipi riferimento viene allocato nell'heap. Il riferimento a tale
spazio di archiviazione è archiviato nello stack.
La riduzione al minimo delle allocazioni richiede alcuni compromessi. Si copia più memoria quando le
dimensioni dello struct sono superiori a quelle di un riferimento. Un riferimento è in genere a 64 bit o a 32 bit
e dipende dalla CPU del computer di destinazione.
Questi compromessi in genere hanno un impatto minimo sulle prestazioni. Tuttavia, per gli struct di grandi
dimensioni o le raccolte di dimensioni maggiori, l'impatto sulle prestazioni aumenta. L'impatto può essere forte
nei cicli ridotti e nei percorsi critici per i programmi.
Questi miglioramenti del linguaggio C# sono progettati per gli algoritmi con prestazioni critiche in cui la
riduzione al minimo delle allocazioni della memoria è un fattore determinante per raggiungere le prestazioni
necessarie. È possibile che non si usino spesso queste funzionalità nella scrittura del codice. Tuttavia, questi
miglioramenti sono stati adottati in .NET. Il sempre maggior numero di API che useranno queste funzionalità
renderà evidente il miglioramento delle prestazioni delle applicazioni.

Vedere anche
ref (parola chiave)
Valori restituiti e variabili locali ref
Alberi delle espressioni
02/11/2020 • 4 minutes to read • Edit Online

Se si è usato LINQ, si ha esperienza con una ricca libreria in cui i tipi Func fanno parte del set di API. Se non si
ha familiarità con LINQ, è consigliabile leggere l'esercitazione su LINQ e l'articolo sulle espressioni lambda
prima di questo. Gli alberi delle espressioni forniscono interazioni più complete con gli argomenti che sono
funzioni.
Scrivere gli argomenti della funzione, in genere usando le espressioni lambda, quando si creano query LINQ. In
una tipica query LINQ, tali argomenti delle funzioni vengono trasformati in un delegato che viene creato dal
compilatore.
Quando si vuole usare una maggiore interazione, è necessario usare gli alberi delle espressioni. Gli alberi delle
espressioni rappresentano il codice come una struttura che è possibile esaminare, modificare o eseguire. Questi
strumenti offrono la possibilità di modificare il codice in fase di esecuzione. È possibile scrivere codice che
esamina gli algoritmi in esecuzione o inserisce nuove funzionalità. Negli scenari più avanzati, è possibile
modificare gli algoritmi in esecuzione e anche convertire espressioni C# in un altro formato per l'esecuzione in
un altro ambiente.
Si è probabilmente già scritto codice che usa gli alberi delle espressioni. Le API LINQ di Entity Framework
accettano gli alberi delle espressioni come argomenti per il criterio di espressione di query LINQ. Ciò consente a
Entity Framework di convertire la query scritta in C# in SQL che viene eseguito nel motore di database. Un altro
esempio è Moq, che è un framework di simulazione tra i più diffusi per .NET.
Le sezioni rimanenti di questa esercitazione illustreranno che cosa sono gli alberi delle espressioni,
esamineranno le classi di framework che supportano gli alberi delle espressioni e spiegheranno come lavorare
con gli alberi delle espressioni. Si apprenderà come leggere gli alberi delle espressioni, come creare alberi delle
espressioni, come creare alberi delle espressioni modificati e come eseguire il codice rappresentato dagli alberi
delle espressioni. Al termine, sarà possibile usare queste strutture per creare algoritmi adattivi completi.
1. Nozioni di base sugli alberi delle espressioni
Informazioni sulla struttura e sui concetti correlati agli alberi delle espressioni.
2. Tipi di framework che supportano alberi delle espressioni
Informazioni sulle strutture e le classi che definiscono e modificano gli alberi delle espressioni.
3. Esecuzione di espressioni
Informazioni su come convertire un albero delle espressioni rappresentato come un'espressione lambda
in un delegato ed eseguire il delegato risultante.
4. Interpretazione di espressioni
Informazioni su come attraversare ed esaminare gli alberi delle espressioni per comprendere quale
codice rappresenta l'albero delle espressioni.
5. Creazione di espressioni
Informazioni su come costruire i nodi per un albero delle espressioni e creare alberi delle espressioni.
6. Conversione di espressioni
Informazioni su come creare una copia modificata di un albero delle espressioni o convertire un albero
delle espressioni in un formato diverso.
7. Riepilogo
Esaminare le informazioni sugli alberi delle espressioni.
Nozioni di base sugli alberi delle espressioni
18/03/2020 • 9 minutes to read • Edit Online

Precedente -- Panoramica
Un albero delle espressioni è una struttura dei dati che definisce il codice. Si basa sulle stesse strutture usate da
un compilatore per analizzare il codice e generare l'output compilato. Nel corso di questa esercitazione si noterà
una certa similarità tra gli alberi delle espressioni e i tipi usati nelle API di Roslyn per compilare analizzatori e
correzioni di codice. (Analyzers e CodeFixes sono pacchetti NuGet che eseguono l'analisi statica sul codice e
possono suggerire potenziali correzioni per uno sviluppatore.) I concetti sono simili e il risultato finale è una
struttura di dati che consente di esaminare il codice sorgente in modo significativo. Gli alberi delle espressioni si
basano tuttavia su un set di classi e API completamente diverso dalle API di Roslyn.
Si osservi l'esempio seguente. Ecco una riga di codice:

var sum = 1 + 2;

Analizzando la riga come se fosse un albero delle espressioni, si nota che l'albero contiene numerosi nodi. Il
nodo più esterno è un'istruzione di dichiarazione di variabile con assegnazione ( var sum = 1 + 2; ). Tale nodo
contiene più nodi figlio: una dichiarazione di variabile, un operatore di assegnazione e un'espressione che
rappresenta la parte a destra del segno di uguale. Questa espressione è a sua volta suddivisa in espressioni che
rappresentano l'operazione di addizione e gli operandi sinistro e destro dell'addizione.
Di seguito vengono esaminate in dettaglio le espressioni che costituiscono la parte a destra del segno di uguale.
L'espressione è 1 + 2 . Si tratta di un'espressione binaria. Nello specifico, è un'espressione di addizione binaria.
Un'espressione di addizione binaria ha due figli, che rappresentano i nodi sinistro e destro dell'espressione di
addizione. In questo caso, entrambi i nodi sono espressioni costanti: l'operando sinistro è il valore 1 e
l'operando destro è il valore 2 .
A livello visivo, l'intera istruzione è una struttura ad albero: è possibile iniziare dal nodo radice e passare a ogni
nodo nell'albero per visualizzare il codice che costituisce l'istruzione:
Istruzione di dichiarazione di variabili con assegnazione ( var sum = 1 + 2; )
Dichiarazione di variabili implicite ( var sum )
Parola chiave di variabile implicita ( var )
Dichiarazione del nome di variabile ( sum )
Operatore di assegnazione ( = )
Espressione di addizione binaria ( 1 + 2 )
Operando sinistro ( 1 )
Operatore di addizione ( + )
Operando destro ( 2 )
Tutto ciò può apparire complicato, ma è molto funzionale. Seguendo la stessa procedura, è possibile scomporre
espressioni molto più complesse. Si consideri l'espressione seguente:

var finalAnswer = this.SecretSauceFunction(


currentState.createInterimResult(), currentState.createSecondValue(1, 2),
decisionServer.considerFinalOptions("hello")) +
MoreSecretSauce('A', DateTime.Now, true);
Anche l'espressione precedente è una dichiarazione di variabile con un'assegnazione. In questo caso, il lato
destro dell'assegnazione è una struttura ad albero molto più complessa. Senza procedere con la scomposizione
dell'espressione, si considerino i diversi nodi. Questi includono chiamate al metodo che usano l'oggetto corrente
come un ricevitore, una che include un ricevitore this esplicito, una che non lo usa. Sono presenti chiamate al
metodo che usano altri oggetti ricevitore, e argomenti costanti di diversi tipi. Infine, è presente un operatore di
addizione binaria. A seconda del tipo restituito di SecretSauceFunction() o MoreSecretSauce() , l'operatore di
addizione binaria può rappresentare una chiamata al metodo a un operatore di addizione con override, risolto in
una chiamata al metodo statico dell'operatore di addizione binaria definito per una classe.
Nonostante l'apparente complessità, l'espressione precedente crea una struttura ad albero semplice da
esplorare quanto il primo esempio. È possibile continuare ad attraversare i nodi figlio per individuare i nodi
foglia nell'espressione. I nodi padre includono riferimenti ai propri elementi figlio e ogni nodo dispone di una
proprietà che descrive di che tipo di nodo si tratta.
La struttura di albero delle espressioni è molto coerente. Dopo aver appreso le nozioni di base, è possibile
comprendere anche il codice più complesso quando questo viene rappresentato come un albero delle
espressioni. L'eleganza nella struttura dei dati spiega come il compilatore C# possa analizzare anche i
programmi C# più complessi e creare l'output corretto a partire da un codice sorgente così complesso.
Dopo avere acquisito familiarità con la struttura degli alberi delle espressioni, si noterà che conoscenze acquisite
consentono di operare anche con scenari avanzati. Gli alberi delle espressioni hanno capacità straordinarie.
Oltre alla conversione degli algoritmi da eseguire in altri ambienti, essi possono essere usati per semplificare la
scrittura di algoritmi che verificano il codice prima che venga eseguito. È possibile scrivere un metodo i cui
argomenti sono espressioni e quindi esaminare tali espressioni prima di eseguire il codice. L'albero delle
espressioni è una rappresentazione completa del codice e consente quindi di visualizzare i valori di qualsiasi
sottoespressione, i nomi dei metodi e delle proprietà e il valore di qualsiasi espressione costante. È anche
possibile convertire un albero delle espressioni in un delegato eseguibile, ed eseguire il codice.
Le API degli alberi delle espressioni consentono di creare strutture che rappresentano praticamente qualsiasi
costrutto di codice valido. Tuttavia, per evitare complicazioni eccessive, in un albero delle espressioni non è
possibile creare alcuni idiomi di C#. Ne sono esempi le espressioni asincrone che usano le delle parole chiave
async e await . Se sono necessari gli algoritmi asincroni, modificare direttamente gli oggetti Task invece di
affidarsi al supporto del compilatore. Un altro metodo è la creazione dei cicli. In genere, i cicli vengono creati
usando i cicli for , foreach , while o do . Come si vedrà più avanti in questa serie, le API degli alberi delle
espressioni supportano una singola espressione Loop, con le espressioni break e continue che controllano il
ciclo di ripetizione.
Non è possibile modificare un albero delle espressioni. Sono infatti strutture dei dati immutabili. Se si vuole
modificare un albero delle espressioni, è necessario creare un nuovo albero che sia una copia dell'originale e
che contenga le modifiche desiderate.
Successivo -- Tipi di framework che supportano alberi delle espressioni
Tipi di framework che supportano alberi delle
espressioni
02/11/2020 • 5 minutes to read • Edit Online

Precedente -- Nozioni di base sugli alberi delle espressioni


In .NET Core Framework esiste un lungo elenco di classi che funzionano con gli alberi delle espressioni. È
possibile visualizzare l'elenco completo in System.Linq.Expressions. Anziché rivedere l'elenco completo,
cerchiamo di comprendere come sono state progettate le classi del framework.
Nella progettazione del linguaggio, un'espressione è un corpo di codice che valuta e restituisce un valore. Le
espressioni possono essere molto semplici: l'espressione costante 1 restituisce il valore costante 1. Possono
essere più complicate: l'espressione (-B + Math.Sqrt(B*B - 4 * A * C)) / (2 * A) restituisce una radice di
un'equazione quadratica (nel caso in cui l'equazione abbia una soluzione).

Tutto inizia con System.Linq.Expression


Una delle difficoltà legate all'uso degli alberi delle espressioni è che molti tipi diversi di espressioni sono validi in
molte posizioni nei programmi. Si consideri un'espressione di assegnazione. Il lato destro di un'assegnazione
può essere un valore costante, una variabile, un'espressione per chiamata al metodo o altro. Tale flessibilità del
linguaggio significa che possono insorgere molti tipi di espressioni diverse in qualsiasi punto nei nodi di un
albero quando si attraversa un albero delle espressioni. Pertanto, quando è possibile usare il tipo di espressione
di base questo è il modo più semplice per lavorare. È talvolta necessario, tuttavia, ottenere maggiori
informazioni. La classe Expression di base contiene una proprietà NodeType per questo scopo. Restituisce un
ExpressionType che costituisce un'enumerazione dei tipi di espressioni possibili. Quando si conosce il tipo di
nodo, è possibile eseguirne il cast al tipo ed eseguire azioni specifiche conoscendo il tipo del nodo
dell'espressione. È possibile eseguire la ricerca di determinati tipi di nodo e quindi usare le proprietà specifiche
di quel tipo di espressione.
Ad esempio, questo codice stamperà il nome di una variabile per un'espressione di accesso alle variabili. È stata
seguita la procedura di verifica del tipo di nodo, quindi è stato eseguito il cast a un'espressione di accesso alle
variabili e infine sono state verificate le proprietà del tipo di espressione specifica:

Expression<Func<int, int>> addFive = (num) => num + 5;

if (addFive.NodeType == ExpressionType.Lambda)
{
var lambdaExp = (LambdaExpression)addFive;

var parameter = lambdaExp.Parameters.First();

Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
}

Creazione dell'albero delle espressioni


La classe System.Linq.Expression contiene anche molti metodi statici per creare espressioni. Questi metodi
creano un nodo di espressione usando gli argomenti specificati per i relativi elementi figlio. In questo modo, si
compila un'espressione dai relativi nodi foglia. Ad esempio, questo codice compila un'espressione di aggiunta:
// Addition is an add expression for "1 + 2"
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);

In questo semplice esempio si vede che nella creazione e nell'uso degli alberi delle espressioni sono coinvolti
molti tipi. Tale complessità è necessaria per offrire le funzionalità di vocabolario avanzate del linguaggio C#.

Esplorazione delle API


Sono disponibili tipi di nodo Expression che eseguono il mapping a quasi tutti gli elementi della sintassi del
linguaggio C#. Ogni tipo offre metodi specifici per il tipo di elemento di linguaggio. Vi sono molti elementi da
tenere presenti contemporaneamente. Anziché tentare di memorizzare tutti gli elementi, ecco le tecniche
consigliate per lavorare con gli alberi delle espressioni:
1. Esaminare i membri dell'enumerazione ExpressionType per determinare i possibili nodi da esaminare.
Questo è estremamente utile quando si intende analizzare e comprendere un albero delle espressioni.
2. Esaminare i membri statici della classe Expression per compilare un'espressione. Tali metodi possono
compilare qualsiasi tipo di espressione da un set dei relativi nodi figlio.
3. Esaminare la classe ExpressionVisitor per compilare un albero delle espressioni modificato.
Esaminando ognuna di queste aree, si otterranno maggiori informazioni. Si troverà quanto necessario iniziando
con uno di questi tre passaggi.
Successivo -- Esecuzione di alberi delle espressioni
Esecuzione di alberi delle espressioni
18/03/2020 • 11 minutes to read • Edit Online

Precedente -- Tipi di framework che supportano alberi delle espressioni


Un albero delle espressioni è una struttura dei dati che rappresenta il codice. Non è codice compilato ed
eseguibile. Se si vuole eseguire il codice .NET che è rappresentato da un albero delle espressioni, è necessario
convertirlo in istruzioni IL eseguibili.

Espressioni lambda per funzioni


È possibile convertire qualsiasi LambdaExpression o qualsiasi tipo derivato da LambdaExpression in IL
eseguibile. Altri tipi di espressioni non possono essere convertiti direttamente in codice. Questa restrizione ha
un effetto limitato nella pratica. Le espressioni lambda sono gli unici tipi di espressioni che potrebbero essere
eseguite convertendole in linguaggio intermedio eseguibile (IL). (Riflettere su cosa significherebbe eseguire
direttamente una ConstantExpression . Significherebbe qualcosa di utile?) Qualsiasi struttura ad
LambdaExpression albero dell'espressione LambdaExpression che è un , o un tipo derivato da può essere
convertito in IL. Il tipo di espressione Expression<TDelegate> è l'unico esempio concreto nelle librerie di .NET
Core. Viene usato per rappresentare un'espressione che esegue il mapping a qualsiasi tipo delegato. Poiché
questo tipo è mappato a un tipo delegato, .NET può esaminare l'espressione e generare IL per un delegato
appropriato che corrisponda alla firma dell'espressione lambda.
Nella maggior parte dei casi, verrà creato un mapping semplice tra un'espressione e il delegato corrispondente.
Ad esempio, un albero delle espressioni che è rappresentato da Expression<Func<int>> viene convertito in un
delegato del tipo Func<int> . Per un'espressione lambda con qualsiasi tipo restituito e un elenco di argomenti,
esiste un tipo delegato che rappresenta il tipo di destinazione per il codice eseguibile rappresentato
dall'espressione lambda.
Il tipo LambdaExpression contiene i membri Compile e CompileToMethod usati per convertire un albero delle
espressioni in codice eseguibile. Il metodo Compile crea un delegato. Il metodo CompileToMethod aggiorna un
oggetto MethodBuilder con il linguaggio intermedio che rappresenta l'output compilato dell'albero delle
espressioni. Si noti che CompileToMethod è disponibile solo nella versione desktop completa di Framework, non
su .NET Core.
Facoltativamente, è anche possibile specificare un DebugInfoGenerator che riceverà le informazioni di debug del
simbolo per l'oggetto delegato generato. Ciò consente di convertire l'albero delle espressioni in un oggetto
delegato e di avere informazioni di debug complete sul delegato generato.
È necessario convertire un'espressione in un delegato tramite il codice seguente:

Expression<Func<int>> add = () => 1 + 2;


var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);

Si noti che il tipo delegato è basato sul tipo di espressione. Se si vuole usare l'oggetto delegato in modo
fortemente tipizzato, è necessario conoscere il tipo restituito e l'elenco di argomenti. Il metodo
LambdaExpression.Compile() restituisce il tipo Delegate . È necessario eseguirne il cast al tipo di delegato
corretto affinché gli strumenti in fase di compilazione controllino l'elenco di argomenti o il tipo restituito.
Esecuzione e durate
Si esegue il codice richiamando il delegato creato durante la chiamata a LambdaExpression.Compile() . È possibile
vederlo qui sopra dove add.Compile() restituisce un delegato. Richiamando il delegato, la chiamata a func()
esegue il codice.
Il delegato rappresenta il codice nell'albero delle espressioni. È possibile mantenere il punto di controllo al
delegato e richiamarlo in un secondo momento. Non è necessario compilare l'albero delle espressioni ogni volta
che si vuole eseguire il codice che rappresenta. Tenere presente che gli alberi delle espressioni non sono
modificabili e la compilazione dello stesso albero delle espressioni in un secondo momento creerà un delegato
che esegue lo stesso codice.
È consigliabile prestare la massima attenzione quando si tenta di creare un meccanismo di memorizzazione
nella cache più sofisticato per migliorare le prestazioni evitando chiamate di compilazione non necessarie. Il
confronto tra due alberi delle espressioni arbitrari per determinare se rappresentino lo stesso algoritmo
richiederà anche molto tempo. Probabilmente si scoprirà che il tempo di calcolo risparmiato evitando le
chiamate aggiuntive a LambdaExpression.Compile() verrà abbondantemente consumato dal tempo per
l'esecuzione del codice che determina due risultati diversi degli alberi delle espressioni nello stesso codice
eseguibile.

Precisazioni
La compilazione di un'espressione lambda a un delegato e la chiamata al delegato è una delle operazioni più
semplici che si possono eseguire con un albero delle espressioni. Anche con questa semplice operazione vi sono
tuttavia alcune avvertenze da tenere in considerazione.
Le espressioni lambda creano chiusure su tutte le variabili locali a cui si fa riferimento nell'espressione. È
necessario garantire che tutte le variabili che fanno parte del delegato siano utilizzabili in corrispondenza della
posizione in cui viene chiamato Compile e quando si esegue il delegato risultante.
In generale, il compilatore garantirà che questa condizione sia vera. Se tuttavia l'espressione accede a una
variabile che implementa IDisposable , è possibile che il codice elimini l'oggetto mentre è ancora mantenuto
attivo dall'albero delle espressioni.
Ad esempio, questo codice funziona correttamente poiché int non implementa IDisposable :

private static Func<int, int> CreateBoundFunc()


{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}

Il delegato ha acquisito un riferimento alla variabile locale constant . Tale variabile è accessibile in qualsiasi
momento successivo, quando viene eseguita la funzione restituita da CreateBoundFunc .
Tenere presente tuttavia questa classe (piuttosto improbabile) che implementa IDisposable :
public class Resource : IDisposable
{
private bool isDisposed = false;
public int Argument
{
get
{
if (!isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}

public void Dispose()


{
isDisposed = true;
}
}

Se usata in un'espressione come illustrato di seguito, si otterrà un ObjectDisposedException quando si esegue il


codice a cui fa riferimento la proprietà Resource.Argument :

private static Func<int, int> CreateBoundResource()


{
using (var constant = new Resource()) // constant is captured by the expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}

Il delegato restituito da questo metodo è stato chiuso sull'oggetto constant , che è stato eliminato. (È stato
eliminato, perché è stato dichiarato in un'istruzione using .)
A questo punto, quando si esegue il delegato restituito da questo metodo, si avrà una ObjectDisposedException
generata al momento dell'esecuzione.
Può sembrare strano ricevere un errore di runtime che rappresenta un costrutto in fase di compilazione, ma
questo è ciò che avviene negli alberi delle espressioni.
Esistono molte variazioni di questo problema, pertanto è difficile offrire indicazioni generali per evitarlo.
Prestare attenzione all'accesso alle variabili locali quando si definiscono le espressioni e prestare attenzione
all'accesso allo stato nell'oggetto corrente (rappresentato da this ) quando viene creato un albero delle
espressioni che può essere restituito da un'API pubblica.
Il codice nell'espressione può fare riferimento a metodi o proprietà in altri assembly. Tale assembly deve essere
accessibile quando viene definita l'espressione, quando viene compilata e quando viene richiamato il delegato
risultante. Quando non è presente, si incontrerà una ReferencedAssemblyNotFoundException .

Summary
Gli alberi delle espressioni che rappresentano le espressioni lambda possono essere compilati per creare un
delegato che è possibile eseguire. Questo offre un meccanismo per eseguire il codice rappresentato da un
albero delle espressioni.
L'albero delle espressioni non rappresenta il codice da eseguire per qualsiasi costrutto specifico creato. Finché
l'ambiente in cui viene compilato ed eseguito il codice corrisponderà all'ambiente in cui si crea l'espressione,
tutto funzionerà come previsto. In caso contrario, gli errori sono molto prevedibili e verranno intercettati nei
primi test di qualsiasi codice basato sugli alberi delle espressioni.
Successivo --Interpretazione di espressioni
Interpretazione di espressioni
02/11/2020 • 20 minutes to read • Edit Online

Precedente -- Esecuzione di espressioni


Scriviamo ora del codice per esaminare la struttura di un albero delle espressioni. Ogni nodo in un albero delle
espressioni sarà un oggetto di una classe derivata da Expression .
Tale progettazione rende la visita di tutti i nodi in un albero delle espressioni un'operazione ricorsiva
relativamente semplice. Come strategia generale, iniziare in corrispondenza del nodo radice e determinare di
quale tipo di nodo si tratti.
Se il tipo di nodo contiene elementi figlio, visitare in modo ricorsivo gli elementi figlio. In ogni nodo figlio,
ripetere il processo usato in corrispondenza del nodo radice: determinare il tipo e, se il tipo contiene elementi
figlio, visitare ognuno di essi.

Analisi di un'espressione senza elementi figlio


Per iniziare, visitare ogni nodo in un albero delle espressioni semplice. Ecco il codice che crea un'espressione
costante e ne esamina quindi le proprietà:

var constant = Expression.Constant(24, typeof(int));

Console.WriteLine($"This is a/an {constant.NodeType} expression type");


Console.WriteLine($"The type of the constant value is {constant.Type}");
Console.WriteLine($"The value of the constant value is {constant.Value}");

Questo comporterà il risultato seguente:

This is an Constant expression type


The type of the constant value is System.Int32
The value of the constant value is 24

A questo punto, iniziare a scrivere il codice che esamina l'espressione e scrivere alcune proprietà importanti su
di esso. Di seguito è riportato il codice:

Analisi di un'espressione di addizione semplice


Iniziamo con l'esempio di addizione presentato nella parte introduttiva di questa sezione.

Expression<Func<int>> sum = () => 1 + 2;

Non si usa var per dichiarare questo albero delle espressioni e ciò non è possibile perché il lato destro
dell'assegnazione è tipizzato in modo implicito.

Il nodo radice è una LambdaExpression . Per ottenere il codice interessante sul lato destro dell' => operatore, è
necessario trovare uno degli elementi figlio dell'oggetto LambdaExpression . È possibile farlo con tutte le
espressioni in questa sezione. Il nodo padre consente di trovare il tipo restituito della LambdaExpression .
Per esaminare ogni nodo in questa espressione, è necessario visitare in modo ricorsivo un certo numero di nodi.
Ecco una semplice prima implementazione:

Expression<Func<int, int, int>> addition = (a, b) => a + b;

Console.WriteLine($"This expression is a {addition.NodeType} expression type");


Console.WriteLine($"The name of the lambda is {((addition.Name == null) ? "<null>" : addition.Name)}");
Console.WriteLine($"The return type is {addition.ReturnType.ToString()}");
Console.WriteLine($"The expression has {addition.Parameters.Count} arguments. They are:");
foreach(var argumentExpression in addition.Parameters)
{
Console.WriteLine($"\tParameter Type: {argumentExpression.Type.ToString()}, Name:
{argumentExpression.Name}");
}

var additionBody = (BinaryExpression)addition.Body;


Console.WriteLine($"The body is a {additionBody.NodeType} expression");
Console.WriteLine($"The left side is a {additionBody.Left.NodeType} expression");
var left = (ParameterExpression)additionBody.Left;
Console.WriteLine($"\tParameter Type: {left.Type.ToString()}, Name: {left.Name}");
Console.WriteLine($"The right side is a {additionBody.Right.NodeType} expression");
var right= (ParameterExpression)additionBody.Right;
Console.WriteLine($"\tParameter Type: {right.Type.ToString()}, Name: {right.Name}");

Questo esempio dà l'output seguente:

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
Parameter Type: System.Int32, Name: a
Parameter Type: System.Int32, Name: b
The body is a/an Add expression
The left side is a Parameter expression
Parameter Type: System.Int32, Name: a
The right side is a Parameter expression
Parameter Type: System.Int32, Name: b

Nell'esempio di codice riportato sopra è possibile rilevare numerose ripetizioni. Procediamo alla pulizia e alla
creazione di un visitatore del nodo dell'espressione per un utilizzo più generico. Questo richiederà la scrittura di
un algoritmo ricorsivo. Qualsiasi nodo potrebbe essere di un tipo con elementi figlio. Qualsiasi nodo con
elementi figlio richiede la visita di tali elementi figlio e la determinazione del tipo di nodo. Ecco la versione pulita
che usa la ricorsione per visitare le operazioni di addizione:

// Base Visitor class:


public abstract class Visitor
{
private readonly Expression node;

protected Visitor(Expression node)


{
this.node = node;
}

public abstract void Visit(string prefix);

public ExpressionType NodeType => this.node.NodeType;


public static Visitor CreateFromExpression(Expression node)
{
switch(node.NodeType)
{
case ExpressionType.Constant:
return new ConstantVisitor((ConstantExpression)node);
case ExpressionType.Lambda:
return new LambdaVisitor((LambdaExpression)node);
case ExpressionType.Parameter:
return new ParameterVisitor((ParameterExpression)node);
case ExpressionType.Add:
return new BinaryVisitor((BinaryExpression)node);
default:
Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
return default(Visitor);
}
}
}

// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression type");
Console.WriteLine($"{prefix}The name of the lambda is {((node.Name == null) ? "<null>" :
node.Name)}");
Console.WriteLine($"{prefix}The return type is {node.ReturnType.ToString()}");
Console.WriteLine($"{prefix}The expression has {node.Parameters.Count} argument(s). They are:");
// Visit each parameter:
foreach (var argumentExpression in node.Parameters)
{
var argumentVisitor = Visitor.CreateFromExpression(argumentExpression);
argumentVisitor.Visit(prefix + "\t");
}
Console.WriteLine($"{prefix}The expression body is:");
// Visit the body:
var bodyVisitor = Visitor.CreateFromExpression(node.Body);
bodyVisitor.Visit(prefix + "\t");
}
}

// Binary Expression Visitor:


public class BinaryVisitor : Visitor
{
private readonly BinaryExpression node;
public BinaryVisitor(BinaryExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This binary expression is a {NodeType} expression");
var left = Visitor.CreateFromExpression(node.Left);
Console.WriteLine($"{prefix}The Left argument is:");
left.Visit(prefix + "\t");
var right = Visitor.CreateFromExpression(node.Right);
Console.WriteLine($"{prefix}The Right argument is:");
right.Visit(prefix + "\t");
}
}

// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}Type: {node.Type.ToString()}, Name: {node.Name}, ByRef:
{node.IsByRef}");
}
}

// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}The type of the constant value is {node.Type}");
Console.WriteLine($"{prefix}The value of the constant value is {node.Value}");
}
}

Questo algoritmo è la base di un algoritmo che può visitare qualsiasi LambdaExpression arbitraria. Esistono molti
buchi, vale a dire che il codice creato Cerca solo un campione molto piccolo dei possibili set di nodi dell'albero
delle espressioni che possono verificarsi. È tuttavia possibile ricavare una certa quantità di informazioni utili dal
risultato prodotto. Il caso predefinito nel metodo Visitor.CreateFromExpression visualizza un messaggio nella
console di errore quando viene rilevato un nuovo tipo di nodo. In questo modo, si sa di dover aggiungere un
nuovo tipo di espressione.
Quando si esegue questo visitatore nell'espressione di addizione precedente, si ottiene l'output seguente:

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False

Ora che è stata creata un'implementazione del visitatore più generale, è possibile visitare ed elaborare più tipi
diversi di espressioni.

Analisi di un'espressione di addizione con molti livelli


Procediamo con un esempio più complesso, limitando comunque i tipi di nodo alla sola addizione:

Expression<Func<int>> sum = () => 1 + 2 + 3 + 4;


Prima di procedere all'esecuzione sull'algoritmo visitatore, provare a pensare quale potrebbe essere l'output.
Tenere presente che l'operatore + è un operatore binario: deve avere due figli, che rappresentano gli operandi
sinistro e destro. Sono possibili diversi modi per costruire un albero che può essere corretto:

Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));


Expression<Func<int>> sum2 = () => ((1 + 2) + 3) + 4;

Expression<Func<int>> sum3 = () => (1 + 2) + (3 + 4);


Expression<Func<int>> sum4 = () => 1 + ((2 + 3) + 4);
Expression<Func<int>> sum5 = () => (1 + (2 + 3)) + 4;

Si può visualizzare la separazione in due possibili risposte per evidenziare quella più efficace. La prima
possibilità rappresenta le espressioni associative all'operando destro. La seconda rappresenta le espressioni
associative all'operando sinistro. Il vantaggio di entrambi i formati è che il formato si adatta a qualsiasi numero
arbitrario di espressioni di addizione.
Se si esegue questa espressione tramite il visitatore, verrà visualizzato questo output, verificando che
l'espressione di addizione semplice sia associativa a sinistra.
Per eseguire questo esempio e visualizzare l'albero delle espressioni completo, è stato necessario apportare una
modifica all'albero delle espressioni di origine. Quando l'albero delle espressioni contiene tutte le costanti,
l'albero risultante contiene semplicemente il valore costante di 10 . Il compilatore esegue tutta l'addizione e
riduce l'espressione alla sua forma più semplice. La semplice aggiunta di una variabile nell'espressione è
sufficiente per visualizzare l'albero originale:

Expression<Func<int, int>> sum = (a) => 1 + a + 3 + 4;

Creare un visitatore per questa somma ed eseguire il visitatore per visualizzare l'output seguente:

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 4

È anche possibile eseguire uno qualsiasi degli altri esempi tramite il codice del visitatore e vedere quali alberi
rappresenta. Di seguito è riportato un esempio dell'espressione sum3 precedente (con un parametro aggiuntivo
per impedire che il compilatore elabori la costante):

Expression<Func<int, int, int>> sum3 = (a, b) => (1 + a) + (3 + b);

Di seguito è riportato l'output dal visitatore:

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False

Si noti che le parentesi non fanno parte dell'output. Non sono presenti nodi nell'albero delle espressioni che
rappresentano le parentesi nell'espressione di input. La struttura dell'albero delle espressioni contiene tutte le
informazioni necessarie per comunicare la precedenza.

Estensione da questo esempio


L'esempio riguarda solo gli alberi delle espressioni più elementari. Il codice in questa sezione gestisce solo interi
costanti e l'operatore + binario. Come esempio finale, aggiorniamo il visitatore per gestire un'espressione più
complessa. Facciamolo funzionare in questo modo:

Expression<Func<int, int>> factorial = (n) =>


n == 0 ?
1 :
Enumerable.Range(1, n).Aggregate((product, factor) => product * factor);

Questo codice rappresenta una possibile implementazione per funzione matematica fattoriale. Il modo in cui è
stato scritto questo codice consente di evidenziare due limitazioni della creazione degli alberi delle espressioni
tramite l'assegnazione di espressioni lambda alle espressioni. In primo luogo, non sono consentite espressioni
lambda dell'istruzione. Non è quindi possibile usare cicli, blocchi, istruzioni if / else e altre strutture di controllo
comuni in C#. Si è limitati all'uso delle espressioni. In secondo luogo, non è possibile chiamare in modo ricorsivo
la stessa espressione. Ciò sarebbe possibile se fosse già un delegato, ma non può essere chiamata nella sua
forma di albero delle espressioni. Nella sezione relativa alla compilazione degli alberi delle espressioniverranno
illustrate le tecniche per superare queste limitazioni.
In questa espressione si incontrano i nodi di tutti questi tipi:
1. Uguale (espressione binaria)
2. Per (espressione binaria)
3. Condizionale (l'espressione ? : )
4. Espressione per chiamata al metodo (chiamata Range() e Aggregate() )

Un modo per modificare l'algoritmo visitatore consiste nel continuare a eseguirlo e scrivere il tipo di nodo ogni
volta che si raggiunge la clausola default . Dopo alcune iterazioni, si osserverà ognuno dei nodi potenziali. Si
dispone quindi di tutto il necessario. Il risultato è simile al seguente:

public static Visitor CreateFromExpression(Expression node)


{
switch(node.NodeType)
{
case ExpressionType.Constant:
return new ConstantVisitor((ConstantExpression)node);
case ExpressionType.Lambda:
return new LambdaVisitor((LambdaExpression)node);
case ExpressionType.Parameter:
return new ParameterVisitor((ParameterExpression)node);
case ExpressionType.Add:
case ExpressionType.Equal:
case ExpressionType.Multiply:
return new BinaryVisitor((BinaryExpression)node);
case ExpressionType.Conditional:
return new ConditionalVisitor((ConditionalExpression)node);
case ExpressionType.Call:
return new MethodCallVisitor((MethodCallExpression)node);
default:
Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
return default(Visitor);
}
}

Il ConditionalVisitor e il MethodCallVisitor elaborano questi due nodi:


public class ConditionalVisitor : Visitor
{
private readonly ConditionalExpression node;
public ConditionalVisitor(ConditionalExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
var testVisitor = Visitor.CreateFromExpression(node.Test);
Console.WriteLine($"{prefix}The Test for this expression is:");
testVisitor.Visit(prefix + "\t");
var trueVisitor = Visitor.CreateFromExpression(node.IfTrue);
Console.WriteLine($"{prefix}The True clause for this expression is:");
trueVisitor.Visit(prefix + "\t");
var falseVisitor = Visitor.CreateFromExpression(node.IfFalse);
Console.WriteLine($"{prefix}The False clause for this expression is:");
falseVisitor.Visit(prefix + "\t");
}
}

public class MethodCallVisitor : Visitor


{
private readonly MethodCallExpression node;
public MethodCallVisitor(MethodCallExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
if (node.Object == null)
Console.WriteLine($"{prefix}This is a static method call");
else
{
Console.WriteLine($"{prefix}The receiver (this) is:");
var receiverVisitor = Visitor.CreateFromExpression(node.Object);
receiverVisitor.Visit(prefix + "\t");
}

var methodInfo = node.Method;


Console.WriteLine($"{prefix}The method name is {methodInfo.DeclaringType}.{methodInfo.Name}");
// There is more here, like generic arguments, and so on.
Console.WriteLine($"{prefix}The Arguments are:");
foreach(var arg in node.Arguments)
{
var argVisitor = Visitor.CreateFromExpression(arg);
argVisitor.Visit(prefix + "\t");
}
}
}

L'output per l'albero delle espressioni sarà:


This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The expression body is:
This expression is a Conditional expression
The Test for this expression is:
This binary expression is a Equal expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 0
The True clause for this expression is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The False clause for this expression is:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Aggregate
The Arguments are:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Range
The Arguments are:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False
The expression body is:
This binary expression is a Multiply expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False

Estensione della libreria degli esempi


Gli esempi in questa sezione illustrano le tecniche principali per visitare ed esaminare i nodi in un albero delle
espressioni. Sono state tralasciate molte azioni che potrebbero essere necessarie in modo da concentrarsi sulle
attività di base relative alla visita e all'accesso ai nodi in un albero delle espressioni.
In primo luogo, i visitatori gestiscono solo le costanti che sono valori interi. I valori costanti potrebbero essere di
qualsiasi altro tipo numerico e il linguaggio C# supporta le conversioni e le promozioni tra tali tipi. Una versione
più affidabile di questo codice rispecchierebbe tutte queste funzionalità.
Anche nell'ultimo esempio viene riconosciuto un sottoinsieme dei possibili tipi di nodo. È comunque possibile
inserire molte espressioni che ne determinerebbero l'esito negativo. Un'implementazione completa è inclusa in
.NET Standard sotto il nome ExpressionVisitor e può gestire tutti i possibili tipi di nodo.
Infine, la libreria usata in questo articolo è stata creata per la formazione e dimostrazione. Non è ottimizzata. Lo
ho scritto per rendere le strutture più chiare e per evidenziare le tecniche usate per visitare i nodi e analizzare i
risultati. Un'implementazione di produzione presterebbe maggiore attenzione alle prestazioni di quanto è stato
fatto qui.
Anche con queste limitazioni, sarà possibile iniziare a scrivere algoritmi e leggere e comprendere gli alberi delle
espressioni.
Successivo -- Creazione di espressioni
Compilazione di alberi delle espressioni
02/11/2020 • 9 minutes to read • Edit Online

Precedente -- Interpretazione di espressioni


Tutti gli alberi delle espressioni visti finora sono stati creati dal compilatore C#. È stato sufficiente creare
un'espressione lambda assegnata a una variabile tipizzata come un'espressione Expression<Func<T>> o di tipo
simile. Questo tuttavia non è l'unico modo per creare un albero delle espressioni. In molti scenari potrebbe
essere necessario compilare un'espressione in memoria in fase di esecuzione.
La compilazione di alberi delle espressioni è complicata dal fatto che gli alberi delle espressioni non sono
modificabili. Ciò significa che l'albero deve essere compilato dalle foglie alla radice. Ciò si riflette nelle API usate
per compilare gli alberi delle espressioni: i metodi usati per compilare un nodo accettano tutti gli elementi figlio
come argomenti. I seguenti esempi illustrano le tecniche.

Creazione di nodi
Iniziamo con un esempio relativamente semplicemente, usando l'espressione di addizione già impiegata nelle
sezioni seguenti:

Expression<Func<int>> sum = () => 1 + 2;

Per costruire l'albero delle espressioni, è necessario creare i nodi foglia. I nodi foglia sono costanti, e per
costruirli è pertanto possibile usare il metodo Expression.Constant :

var one = Expression.Constant(1, typeof(int));


var two = Expression.Constant(2, typeof(int));

Successivamente, viene creata creato l'espressione di addizione:

var addition = Expression.Add(one, two);

Dopo aver creato l'espressione di addizione, è possibile creare l'espressione lambda:

var lambda = Expression.Lambda(addition);

Si tratta di un'espressione lambda molto semplice, perché non contiene argomenti. Più avanti in questa sezione
verrà illustrato come eseguire il mapping degli argomenti ai parametri e compilare espressioni più complesse.
Per le espressioni semplici come questa, è possibile combinare tutte le chiamate in un'unica istruzione:

var lambda = Expression.Lambda(


Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
Compilazione di una struttura ad albero
Vengono illustrate le nozioni di base per la compilazione di un albero delle espressioni in memoria. Strutture ad
albero più complesse implicano in genere più tipi di nodo e più nodi dell'albero. L'esempio seguente mostra due
tipi di nodo che vengono in genere compilati quando si creano alberi delle espressioni: i nodi argomento e i
nodi di chiamata al metodo.
Di seguito viene compilato un albero delle espressioni per creare questa espressione:

Expression<Func<double, double, double>> distanceCalc =


(x, y) => Math.Sqrt(x * x + y * y);

Si inizia creando espressioni per i parametri per x e y :

var xParameter = Expression.Parameter(typeof(double), "x");


var yParameter = Expression.Parameter(typeof(double), "y");

Per creare le espressioni di addizione e moltiplicazione seguire il modello illustrato in precedenza:

var xSquared = Expression.Multiply(xParameter, xParameter);


var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

Quindi è necessario creare un'espressione di chiamata al metodo per la chiamata a Math.Sqrt .

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });


var distance = Expression.Call(sqrtMethod, sum);

Infine, inserire la chiamata al metodo in un'espressione lambda e verificare che gli argomenti nell'espressione
lambda siano stati definiti:

var distanceLambda = Expression.Lambda(


distance,
xParameter,
yParameter);

In questo esempio più complesso, vengono illustrate altre due tecniche spesso necessarie per creare alberi delle
espressioni.
Innanzitutto è necessario creare gli oggetti che rappresentano i parametri o le variabili locali prima di usarli.
Dopo aver creato questi oggetti, è possibile inserirli nell'albero delle espressioni secondo necessità.
È quindi necessario usare un sottoinsieme dell'API Reflection per creare un oggetto MethodInfo , così da poter
creare un albero delle espressioni per accedere a tale metodo. È necessario limitarsi al sottoinsieme delle API
Reflection disponibili nella piattaforma .NET Core. Anche queste tecniche possono essere estese ad altri alberi
delle espressioni.

Compilazione di codice complesso


Non ci sono limiti alle possibilità di creare usando queste API. Tuttavia, più complicato è l'albero delle
espressioni che si vuole compilare, più difficile sarà da gestire e leggere il codice.
Viene ora creato un albero delle espressioni che è l'equivalente di questo codice:
Func<int, int> factorialFunc = (n) =>
{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};

Si noti che non è stato compilato l'albero delle espressioni, ma il delegato. Usando la classe Expression non è
possibile compilare espressioni lambda dell'istruzione. Ecco il codice necessario per compilare la stessa
funzionalità. È complicato dal fatto che non esiste un'API per compilare un ciclo while , ed è invece necessario
creare un ciclo contenente un test condizionale e una destinazione dell'etichetta per uscire dal ciclo.

var nArgument = Expression.Parameter(typeof(int), "n");


var result = Expression.Variable(typeof(int), "result");

// Creating a label that represents the return value


LabelTarget label = Expression.Label(typeof(int));

var initializeResult = Expression.Assign(result, Expression.Constant(1));

// This is the inner block that performs the multiplication,


// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);

// Creating a method body.


BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);

Il codice per creare l'albero delle espressioni per la funzione fattoriale è più lungo, più complesso, e include
numerose etichette e istruzioni di interruzione che è preferibile evitare nelle quotidiane attività di codifica.
Per questa sezione è stato anche aggiornato il codice visitatore, per visitare ogni nodo in questo albero delle
espressioni e scrivere le informazioni relative ai nodi creati in questo esempio. È possibile visualizzare o
scaricare il codice di esempio dal repository GitHub dotnet/docs. Provare a compilare ed eseguire da soli gli
esempi. Per istruzioni sul download, vedere Esempi ed esercitazioni.

Analisi delle API


Le API dell'albero delle espressioni sono tra le più difficili da esplorare .NET Core. La loro finalità è piuttosto
complessa: consentono di scrivere codice che genera codice in fase di esecuzione. Sono intrinsecamente
complicate perché devono offrire un equilibrio tra la capacità di supportare tutte le strutture di controllo
disponibili nel linguaggio C# e mantenere l'area di superficie delle API di dimensioni ridotte. Questo equilibrio fa
sì che molte strutture di controllo siano rappresentate non dai propri costrutti in C#, ma da costrutti che
rappresentano la logica sottostante generata dal compilatore a partire da quei costrutti di livello superiore.
Inoltre, attualmente sono disponibili espressioni in C# che non possono essere compilate direttamente usando
metodi della classe Expression . In generale, questi saranno gli operati e le espressioni più recenti aggiunte in
C# 5 e C# 6. Ad esempio, non è possibile compilare espressioni async e non è possibile creare direttamente il
nuovo operatore ?. .
Successivo -- Conversione di espressioni
Traduci alberi delle espressioni
02/11/2020 • 10 minutes to read • Edit Online

Precedente -- Creazione di espressioni


In questa sezione finale si apprenderà come visitare ogni nodo in un albero delle espressioni, creando una copia
modificata di tale albero delle espressioni. Queste sono le tecniche che verranno usate in due scenari importanti.
La prima consiste nel comprendere gli algoritmi espressi da un albero delle espressioni in modo che possa
essere traslato in un altro ambiente. La seconda si applica quando si desidera modificare l'algoritmo creato. È
possibile aggiungere la registrazione, intercettare chiamate al metodo e tenerne traccia, oltre ad altri scopi.

Traslare è sinonimo di visitare


Il codice compilato per traslare un albero delle espressioni è un'estensione di tutto questo, utile per visitare tutti i
nodi in un albero. Quando si trasla un albero delle espressioni, tutti i nodi vengono visitati e durante la visita
viene compilato il nuovo albero. Il nuovo albero può contenere riferimenti ai nodi originali o ai nuovi nodi
posizionati nell'albero.
Vediamo questo aspetto praticamente visitando un albero delle espressioni e creando un nuovo albero con
alcuni nodi di sostituzione. In questo esempio, sostituiamo qualsiasi costante con una costante dieci volte
maggiore. In alternativa, possiamo lasciare l'albero delle espressioni intatto. Invece di leggere il valore della
costante e sostituirla con una nuova costante, eseguiremo una sostituzione del nodo della costante con un
nuovo nodo che esegue la moltiplicazione.
In questo caso, dopo aver trovato il nodo di una costante, creare un nuovo nodo di moltiplicazione i cui figli
rappresentino la costante originale e la costante 10 :

private static Expression ReplaceNodes(Expression original)


{
if (original.NodeType == ExpressionType.Constant)
{
return Expression.Multiply(original, Expression.Constant(10));
}
else if (original.NodeType == ExpressionType.Add)
{
var binaryExpression = (BinaryExpression)original;
return Expression.Add(
ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
}
return original;
}

Sostituendo il nodo originale con il sostituto, viene formato un nuovo albero che contiene le modifiche.
Possiamo verificarlo tramite la compilazione e l'esecuzione dell'albero sostituito.
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);

var func = (Func<int>)executableFunc.Compile();


var answer = func();
Console.WriteLine(answer);

La creazione di un nuovo albero è una combinazione tra la visita ai nodi nell'albero esistente e la creazione e
l'inserimento di nuovi nodi nell'albero.
Questo esempio illustra l'importanza dell'immutabilità degli alberi delle espressioni. Si noti che il nuovo albero
creato in precedenza contiene una combinazione di nodi appena creati e nodi dall'albero esistente. Questa
combinazione è sicura, poiché i nodi dell'albero esistente non possono essere modificati. Ciò può comportare
una notevole efficienza della memoria. Gli stessi nodi possono essere usati in uno o più alberi delle espressioni.
Poiché i nodi non possono essere modificati, è possibile riutilizzare lo stesso nodo ogni volta che è necessario.

Attraversamento ed esecuzione di un'addizione


Esamineremo ora questi aspetti creando un secondo visitatore che percorre l'albero dei nodi di addizione e
calcola il risultato. A questo scopo, è possibile apportare un paio di modifiche al visitatore osservato fino a
questo punto. In questa nuova versione, il visitatore restituisce la somma parziale dell'operazione di addizione
fino a questo punto. Per un'espressione costante, la somma corrisponde semplicemente al valore
dell'espressione costante. Per un'espressione di addizione, il risultato è la somma degli operandi sinistro e
destro, dopo l'attraversamento degli alberi.

var one = Expression.Constant(1, typeof(int));


var two = Expression.Constant(2, typeof(int));
var three= Expression.Constant(3, typeof(int));
var four = Expression.Constant(4, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2);

// Declare the delegate, so we can call it


// from itself recursively:
Func<Expression, int> aggregate = null;
// Aggregate, return constants, or the sum of the left and right operand.
// Major simplification: Assume every binary expression is an addition.
aggregate = (exp) =>
exp.NodeType == ExpressionType.Constant ?
(int)((ConstantExpression)exp).Value :
aggregate(((BinaryExpression)exp).Left) + aggregate(((BinaryExpression)exp).Right);

var theSum = aggregate(sum);


Console.WriteLine(theSum);

Useremo qui il codice, ma i concetti sono molto intuitivi. Questo codice visita gli elementi figlio in una prima
ricerca profondità. Quando incontra un nodo della costante, il visitatore restituisce il valore della costante. Dopo
che il visitatore ha visitato entrambi gli elementi figlio, gli elementi figlio avranno calcolato la somma calcolata
per tale sottoalbero. A questo punto, il nodo di addizione può calcolare la somma. Dopo aver visitato tutti i noti
dell'albero delle espressioni, verrà calcolata la somma. È possibile tracciare l'esecuzione eseguendo l'esempio
nel debugger.
È possibile semplificare il tracciamento dell'analisi dei nodi e delle modalità di calcolo della somma tramite
l'attraversamento dell'albero. Di seguito è riportata una versione aggiornata del metodo Aggregate che include
una certa quantità di informazioni di tracciamento:

private static int Aggregate(Expression exp)


{
if (exp.NodeType == ExpressionType.Constant)
{
var constantExp = (ConstantExpression)exp;
Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
return (int)constantExp.Value;
}
else if (exp.NodeType == ExpressionType.Add)
{
var addExp = (BinaryExpression)exp;
Console.Error.WriteLine("Found Addition Expression");
Console.Error.WriteLine("Computing Left node");
var leftOperand = Aggregate(addExp.Left);
Console.Error.WriteLine($"Left is: {leftOperand}");
Console.Error.WriteLine("Computing Right node");
var rightOperand = Aggregate(addExp.Right);
Console.Error.WriteLine($"Right is: {rightOperand}");
var sum = leftOperand + rightOperand;
Console.Error.WriteLine($"Computed sum: {sum}");
return sum;
}
else throw new NotSupportedException("Haven't written this yet");
}

L'esecuzione del metodo sulla stessa espressione produce l'output seguente:

10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10

Tracciare l'output e procedere con il codice in alto. È possibile comprendere in che modo il codice visiti ogni
nodo e calcoli la somma attraversando l'albero e trovando la somma.
Esaminiamo ora un'esecuzione diversa, con l'espressione rappresentata da sum1 :

Expression<Func<int> sum1 = () => 1 + (2 + (3 + 4));

Di seguito è riportato l'output dell'analisi di questa espressione:


Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 2
Left is: 2
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10

Mentre la risposta finale è la stesso, l'attraversamento dell'albero è completamente diverso. I nodi vengono
attraversati in un ordine diverso, perché l'albero è stato costruita con diverse operazioni che si verificano prima.

Altre informazioni
Questo esempio illustra un piccolo sottoinsieme del codice creato per attraversare e interpretare gli algoritmi
rappresentati da un albero delle espressioni. Per una descrizione completa di tutte le operazioni necessarie per
compilare una libreria generica che trasla gli alberi delle espressioni in un'altra lingua, vedere questa serie di
Matt Warren. La serie spiega in dettaglio come traslare il codice di un albero delle espressioni.
Mi auguro che abbiamo percepito la vera potenza degli alberi delle espressioni. È possibile esaminare un set di
codice, apportare modifiche a tale codice ed eseguire la versione modificata. Dal momento che gli alberi delle
espressioni sono immutabili, è possibile creare nuovi alberi usando i componenti degli alberi esistenti. Questa
operazione riduce la quantità di memoria necessaria per creare alberi delle espressioni modificati.
Successivo -- Conclusioni
Riepilogo degli alberi delle espressioni
18/03/2020 • 2 minutes to read • Edit Online

Precedente -- Traduzione delle espressioni


In questa serie, abbiamo visto come usare gli alberi delle espressioni per creare programmi dinamici che
interpretano il codice sotto forma di dati e come compilare nuove funzionalità basate su tale codice.
È possibile esaminare gli alberi delle espressioni per comprendere lo scopo di un algoritmo e non solo il codice.
È possibile compilare nuovi alberi delle espressioni che rappresentano le versioni modificate del codice
originale.
È anche possibile usare gli alberi delle espressioni per esaminare un algoritmo e convertirlo in un altro
linguaggio o ambiente.

Limitazioni
Esistono alcuni elementi del linguaggio C# più recenti per cui la traslazione in alberi delle espressioni non
funziona bene. Gli alberi delle espressioni non possono contenere espressioni await o espressioni lambda
async . Molte delle funzionalità aggiunte nella versione C# 6 non vengono visualizzate esattamente come scritte
negli alberi delle espressioni. Al contrario, le funzionalità più recenti verranno esposte negli alberi delle
espressioni nell'equivalente sintassi precedente. Ciò potrebbe non costituire una limitazione come si potrebbe
pensare. Questo significa che il codice che interpreta gli alberi delle espressioni continuerà probabilmente a
funzionare quando verranno introdotte nuove funzionalità del linguaggio.
Nonostante queste limitazioni, gli alberi delle espressioni consentono di creare algoritmi dinamici che si basano
sull'interpretazione e sulla modifica del codice rappresentato come struttura di dati. È uno strumento potente e
rappresenta una delle funzionalità dell'ecosistema .NET che consente alle librerie avanzate come Entity
Framework di portare a termine le operazioni previste.
Interoperabilità (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

L'interoperabilità consente di preservare e sfruttare appieno gli investimenti effettuati in codice non gestito. Il
codice eseguito sotto il controllo del Common Language Runtime (CLR) è detto codice gestito, mentre quello
eseguito all'esterno è detto codice non gestito. Esempi di codice non gestito sono i componenti COM, COM+,
C++, i componenti ActiveX e le API Microsoft Windows.
.NET consente l'interoperabilità con codice non gestito tramite platform invoke servizi, lo
System.Runtime.InteropServices spazio dei nomi, l'interoperabilità C++ e l'interoperabilità COM
(interoperabilità COM).

Contenuto della sezione


Cenni preliminari sull'interoperabilità
Vengono descritti i metodi per l'interoperabilità tra codice C# gestito e non gestito.
Come accedere agli oggetti di interoperabilità di Office usando le funzionalità di C#
Vengono descritte le funzionalità introdotte in Visual C# per facilitare la programmazione di Office.
Come usare proprietà indicizzate nella programmazione dell'interoperabilità COM
Viene illustrato come usare le proprietà indicizzate per accedere alle proprietà COM con parametri.
Come usare platform invoke per riprodurre un file WAV
Viene descritto come usare i servizi platform invoke per riprodurre un file audio con estensione wav nel sistema
operativo Windows.
Procedura dettagliata: Programmazione di Office
Viene illustrato come creare una cartella di lavoro di Excel o un documento di Word contenente un
collegamento alla cartella di lavoro.
Esempio di classe COM
Viene illustrato come esporre una classe C# come oggetto COM.

Specifiche del linguaggio C#


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

Vedere anche
Marshal.ReleaseComObject
Guida per programmatori C#
Interoperabilità con codice non gestito
Procedura dettagliata: Programmazione di Office
Documentare il codice con commenti XML
02/11/2020 • 36 minutes to read • Edit Online

I commenti in formato documentazione XML sono commenti speciali, aggiunti alla definizione di ogni tipo o
membro definito dall'utente. Sono speciali perché possono essere elaborati dal compilatore per generare un file
di documentazione XML in fase di compilazione. Il file XML generato dal compilatore può essere distribuito
insieme all'assembly .NET in modo che Visual Studio e altri IDE possano usare IntelliSense per visualizzare
informazioni rapide sui tipi o i membri. È anche possibile eseguire il file XML tramite strumenti, ad esempio
DocFX e Sandcastle per generare i siti Web di riferimento all'API.
I commenti in formato documentazione XML, come tutti gli altri commenti, vengono ignorati dal compilatore.
È possibile generare il file XML in fase di compilazione eseguendo una delle operazioni seguenti:
Se si sviluppa un'applicazione con .NET Core dalla riga di comando, è possibile aggiungere un
GenerateDocumentationFile elemento alla <PropertyGroup> sezione del file di progetto con estensione
csproj. È anche possibile specificare il percorso del file di documentazione direttamente usando l'
DocumentationFile elemento. L'esempio seguente genera un file XML nella directory del progetto con lo
stesso nome file radice dell'assembly:

<GenerateDocumentationFile>true</GenerateDocumentationFile>

L'espressione equivale alla seguente:

<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>

Se si sviluppa un'applicazione tramite Visual Studio, fare clic con il pulsante destro del mouse sul
progetto e scegliere Proprietà . Nella finestra di dialogo Proprietà selezionare la scheda Genera e
controllare File di documentazione XML . È anche possibile modificare il percorso in cui il compilatore
scrive il file.
Se si compila un'applicazione .NET dalla riga di comando, aggiungere l'opzione del compilatore-doc
durante la compilazione.
I commenti in formato documentazione XML usano tre barre ( /// ) e un corpo di commento in formato XML.
Ad esempio:

/// <summary>
/// This class does something.
/// </summary>
public class SomeClass
{
}

Procedura dettagliata
Esaminiamo la documentazione di una libreria matematica molto semplice per semplificare la comprensione e
la collaborazione da parte di nuovi sviluppatori e per gli sviluppatori di terze parti a usare.
Questo è il codice per la semplice libreria matematica:
/*
The main Math class
Contains all methods for performing basic math functions
*/
public class Math
{
// Adds two integers and returns the result
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

// Adds two doubles and returns the result


public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

// Subtracts an integer from another and returns the result


public static int Subtract(int a, int b)
{
return a - b;
}

// Subtracts a double from another and returns the result


public static double Subtract(double a, double b)
{
return a - b;
}

// Multiplies two integers and returns the result


public static int Multiply(int a, int b)
{
return a * b;
}

// Multiplies two doubles and returns the result


public static double Multiply(double a, double b)
{
return a * b;
}

// Divides an integer by another and returns the result


public static int Divide(int a, int b)
{
return a / b;
}

// Divides a double by another and returns the result


public static double Divide(double a, double b)
{
return a / b;
}
}

La libreria di esempio supporta quattro operazioni aritmetiche principali ( add ,, subtract multiply e divide
) int sui double tipi di dati e.
È ora possibile creare un documento di riferimento per le API dal codice per gli sviluppatori di terze parti che
usano la libreria ma non hanno accesso al codice sorgente. Come accennato in precedenza, è possibile usare tag
della documentazione XML a questo scopo. Verranno ora presentati i tag XML standard supportati dal
compilatore C#.

<summary>
Il tag <summary> aggiunge brevi informazioni su un tipo o membro. Ne viene illustrato l'uso aggiungendolo alla
definizione di classe Math e al primo metodo Add . È possibile applicarlo al resto del codice.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
// Adds two integers and returns the result
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
}

Il <summary> tag è importante ed è consigliabile includerlo perché il relativo contenuto è la fonte primaria di
informazioni sul tipo o sul membro in IntelliSense o un documento di riferimento API.

<remarks>
Il tag <remarks> integra le informazioni sui tipi o membri definiti dal tag <summary> . In questo esempio, sarà
sufficiente aggiungerlo alla classe.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// This class can add, subtract, multiply and divide.
/// </remarks>
public class Math
{
}

<returns>
Il tag <returns> descrive il valore restituito di una dichiarazione di metodo. Come in precedenza, nell'esempio
seguente viene illustrato il tag <returns> nel primo metodo Add . È possibile eseguire questa operazione su
altri metodi.

// Adds two integers and returns the result


/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

<value>
Il tag <value> è simile al tag <returns> , ad eccezione del fatto che viene usato per le proprietà. Se la libreria
Math avesse una proprietà statica denominata PI , questo tag dovrebbe essere usato nel modo seguente:

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// This class can add, subtract, multiply and divide.
/// These operations can be performed on both integers and doubles
/// </remarks>
public class Math
{
/// <value>Gets the value of PI.</value>
public static double PI { get; }
}

<example>
Si usa il tag <example> per includere un esempio nella documentazione XML. Ciò comporta l'uso del tag
<code> del figlio.
// Adds two integers and returns the result
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

Il tag code mantiene le interruzioni di riga e il rientro per esempi più lunghi.

<para>
Si usa il tag <para> per formattare il contenuto all'interno del tag padre. <para> viene in genere usato
all'interno di un tag, ad esempio <remarks> o <returns> , per dividere il testo in paragrafi. È possibile formattare
il contenuto del tag <remarks> per la definizione delle classi.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// <para>This class can add, subtract, multiply and divide.</para>
/// <para>These operations can be performed on both integers and doubles.</para>
/// </remarks>
public class Math
{
}

<c>
Nella formattazione si usa il tag <c> per contrassegnare parte del testo come codice. È simile al tag <code> , ma
è inline. È utile quando si vuole visualizzare un esempio breve di codice come parte del contenuto del tag. Ora
viene aggiornata la documentazione per la classe Math .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
}

<exception>
Tramite il tag <exception> è possibile comunicare agli sviluppatori che un metodo può generare eccezioni
specifiche. Esaminando la libreria Math , è possibile vedere che entrambi i metodi Add generano un'eccezione
se viene soddisfatta una determinata condizione. Nonostante non sia così ovvio, anche il metodo Divide
integer genera eccezioni se il parametro b è uguale a zero. Aggiungere ora la documentazione sull'eccezione a
questo metodo.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
public static double Add(double a, double b)
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

/// <summary>
/// Divides an integer by another and returns the result.
/// </summary>
/// <returns>
/// The division of two integers.
/// </returns>
/// <exception cref="System.DivideByZeroException">Thrown when a division by zero occurs.</exception>
public static int Divide(int a, int b)
{
return a / b;
}

/// <summary>
/// Divides a double by another and returns the result.
/// </summary>
/// <returns>
/// The division of two doubles.
/// </returns>
/// <exception cref="System.DivideByZeroException">Thrown when a division by zero occurs.</exception>
public static double Divide(double a, double b)
{
return a / b;
}
}

L'attributo cref rappresenta un riferimento ad un'eccezione disponibile dall'ambiente di compilazione


corrente. Ciò può essere qualsiasi tipo definito nel progetto o in un assembly di riferimento. Il compilatore
genererà un avviso se il relativo valore non può essere risolto.

<see>
Il tag <see> consente di creare un collegamento selezionabile per una pagina di documentazione per un altro
elemento di codice. Nel prossimo esempio, verrà creato un collegamento selezionabile tra i due metodi Add .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
}

L'attributo cref è obbligatorio e rappresenta un riferimento a un tipo o al suo membro disponibile


dall'ambiente di compilazione corrente. Ciò può essere qualsiasi tipo definito nel progetto o in un assembly di
riferimento.

<seealso>
Il tag <seealso> si usa allo stesso modo del tag <see> . L'unica differenza è che il relativo contenuto viene in
genere inserito in una sezione "Vedere anche". Di seguito si aggiungerà un tag seealso al metodo Add integer
per fare riferimento ad altri metodi della classe che accettano parametri integer:
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
}

L'attributo cref rappresenta un riferimento a un tipo o al suo membro disponibile dall'ambiente di


compilazione corrente. Ciò può essere qualsiasi tipo definito nel progetto o in un assembly di riferimento.

<param>
Il tag <param> viene usato per descrivere i parametri del metodo. Di seguito è riportato un esempio sul Add
Metodo Double: il parametro descritto dal tag è specificato nell'attributo required name .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
}

<typeparam>
Il tag <typeparam> viene usato allo stesso modo del tag <param> , ma in dichiarazioni di tipo o di metodo
generico per descrivere un parametro generico. Aggiungere un metodo generico semplice alla classe Math per
verificare se una quantità è maggiore di un'altra.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Checks if an IComparable is greater than another.
/// </summary>
/// <typeparam name="T">A type that inherits from the IComparable interface.</typeparam>
public static bool GreaterThan<T>(T a, T b) where T : IComparable
{
return a.CompareTo(b) > 0;
}
}

<paramref>
A volte è possibile che durante la descrizione dell'operazione di un metodo in un tag <summary> si vuole fare un
riferimento a un parametro. Il tag <paramref> è molto utile per questa operazione. Ora si aggiorna il riepilogo
del metodo Add basato su double. Analogamente al <param> tag, il nome del parametro viene specificato
nell'attributo obbligatorio name .

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
}

<typeparamref>
Il tag <typeparamref> viene usato allo stesso modo del tag <paramref> , ma in dichiarazioni di tipo o di metodo
generico per descrivere un parametro generico. È possibile usare lo stesso metodo generico creato in
precedenza.

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Checks if an IComparable <typeparamref name="T"/> is greater than another.
/// </summary>
/// <typeparam name="T">A type that inherits from the IComparable interface.</typeparam>
public static bool GreaterThan<T>(T a, T b) where T : IComparable
{
return a.CompareTo(b) > 0;
}
}

<list>
Si usa il <list> tag per formattare le informazioni di documentazione come un elenco ordinato, un elenco non
ordinato o una tabella. Creare un elenco non ordinato di ogni operazione matematica supportata dalla libreria
Math .

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// <list type="bullet">
/// <item>
/// <term>Add</term>
/// <description>Addition Operation</description>
/// </item>
/// <item>
/// <term>Subtract</term>
/// <description>Subtraction Operation</description>
/// </item>
/// <item>
/// <term>Multiply</term>
/// <description>Multiplication Operation</description>
/// </item>
/// <item>
/// <term>Divide</term>
/// <description>Division Operation</description>
/// </item>
/// </list>
/// </summary>
public class Math
{
}

È possibile creare un elenco ordinato o una tabella sostituendo l'attributo type con number o table
rispettivamente.

<inheritdoc>
È possibile usare il <inheritdoc> tag per ereditare commenti XML da classi base, interfacce e metodi simili. In
questo modo si elimina la copia indesiderata e si incollano i commenti XML duplicati e i commenti XML
vengono sincronizzati automaticamente.

/*
The IMath interface
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// This is the IMath interface.
/// </summary>
public interface IMath
{
}

/// <inheritdoc/>
public class Math : IMath
{
}

Combinare tutti gli elementi


Dopo aver eseguito questa esercitazione e applicato i tag al codice, dove necessario, il codice dovrebbe ora
essere simile al seguente:

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// <list type="bullet">
/// <item>
/// <term>Add</term>
/// <description>Addition Operation</description>
/// </item>
/// <item>
/// <term>Subtract</term>
/// <description>Subtraction Operation</description>
/// </item>
/// <item>
/// <term>Multiply</term>
/// <description>Multiplication Operation</description>
/// </item>
/// <item>
/// <term>Divide</term>
/// <description>Division Operation</description>
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// <para>This class can add, subtract, multiply and divide.</para>
/// <para>These operations can be performed on both integers and doubles.</para>
/// </remarks>
public class Math
{
// Adds two integers and returns the result
/// <summary>
/// Adds two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
/// <param name="a">An integer.</param>
/// <param name="b">An integer.</param>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}
// Adds two doubles and returns the result
/// <summary>
/// Adds two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <example>
/// <code>
/// double c = Math.Add(4.5, 5.4);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
/// <seealso cref="Math.Subtract(double, double)"/>
/// <seealso cref="Math.Multiply(double, double)"/>
/// <seealso cref="Math.Divide(double, double)"/>
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Add(double a, double b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

// Subtracts an integer from another and returns the result


/// <summary>
/// Subtracts <paramref name="b"/> from <paramref name="a"/> and returns the result.
/// </summary>
/// <returns>
/// The difference between two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Subtract(4, 5);
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// See <see cref="Math.Subtract(double, double)"/> to subtract doubles.
/// <seealso cref="Math.Add(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
/// <param name="a">An integer.</param>
/// <param name="b">An integer.</param>
public static int Subtract(int a, int b)
{
return a - b;
}

// Subtracts a double from another and returns the result


/// <summary>
/// Subtracts a double <paramref name="b"/> from another double <paramref name="a"/> and returns the
result.
/// </summary>
/// <returns>
/// The difference between two doubles.
/// </returns>
/// </returns>
/// <example>
/// <code>
/// double c = Math.Subtract(4.5, 5.4);
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// See <see cref="Math.Subtract(int, int)"/> to subtract integers.
/// <seealso cref="Math.Add(double, double)"/>
/// <seealso cref="Math.Multiply(double, double)"/>
/// <seealso cref="Math.Divide(double, double)"/>
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Subtract(double a, double b)
{
return a - b;
}

// Multiplies two integers and returns the result


/// <summary>
/// Multiplies two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The product of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Multiply(4, 5);
/// if (c > 100)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// See <see cref="Math.Multiply(double, double)"/> to multiply doubles.
/// <seealso cref="Math.Add(int, int)"/>
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
/// <param name="a">An integer.</param>
/// <param name="b">An integer.</param>
public static int Multiply(int a, int b)
{
return a * b;
}

// Multiplies two doubles and returns the result


/// <summary>
/// Multiplies two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The product of two doubles.
/// </returns>
/// <example>
/// <code>
/// double c = Math.Multiply(4.5, 5.4);
/// if (c > 100.0)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// See <see cref="Math.Multiply(int, int)"/> to multiply integers.
/// <seealso cref="Math.Add(double, double)"/>
/// <seealso cref="Math.Subtract(double, double)"/>
/// <seealso cref="Math.Divide(double, double)"/>
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Multiply(double a, double b)
{
return a * b;
}

// Divides an integer by another and returns the result


/// <summary>
/// Divides an integer <paramref name="a"/> by another integer <paramref name="b"/> and returns the
result.
/// </summary>
/// <returns>
/// The quotient of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Divide(4, 5);
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
/// See <see cref="Math.Divide(double, double)"/> to divide doubles.
/// <seealso cref="Math.Add(int, int)"/>
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <param name="a">An integer dividend.</param>
/// <param name="b">An integer divisor.</param>
public static int Divide(int a, int b)
{
return a / b;
}

// Divides a double by another and returns the result


/// <summary>
/// Divides a double <paramref name="a"/> by another double <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The quotient of two doubles.
/// </returns>
/// <example>
/// <code>
/// double c = Math.Divide(4.5, 5.4);
/// if (c > 1.0)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
/// See <see cref="Math.Divide(int, int)"/> to divide integers.
/// <seealso cref="Math.Add(double, double)"/>
/// <seealso cref="Math.Subtract(double, double)"/>
/// <seealso cref="Math.Multiply(double, double)"/>
/// <param name="a">A double precision dividend.</param>
/// <param name="b">A double precision divisor.</param>
public static double Divide(double a, double b)
{
return a / b;
}
}

Dal codice, è possibile generare un sito Web di documentazione dettagliata completa di riferimenti incrociati.
Ciò però potrebbe rendere il codice più difficile da leggere. In quanto la presenza di un numero elevato di
informazioni costituisce un problema molto serio per gli sviluppatori che vogliono contribuire al codice.
Fortunatamente c'è un tag XML che consente di affrontare questo problema:

<include>
Il tag <include> consente di fare riferimento ai commenti in un file XML separato che descrivono i tipi e i
membri nel codice sorgente anziché inserire commenti in formato documentazione direttamente nel file del
codice sorgente.
Ora si spostano tutti i tag XML in un file XML separato denominato docs.xml . È possibile assegnare al file un
nome qualsiasi.

<docs>
<members name="math">
<Math>
<summary>
The main <c>Math</c> class.
Contains all methods for performing basic math functions.
</summary>
<remarks>
<para>This class can add, subtract, multiply and divide.</para>
<para>These operations can be performed on both integers and doubles.</para>
</remarks>
</Math>
<AddInt>
<summary>
Adds two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The sum of two integers.
</returns>
<example>
<code>
int c = Math.Add(4, 5);
if (c > 10)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.OverflowException">Thrown when one parameter is max
and the other is greater than 0.</exception>
See <see cref="Math.Add(double, double)"/> to add doubles.
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</AddInt>
<AddDouble>
<summary>
Adds two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The sum of two doubles.
</returns>
<example>
<code>
double c = Math.Add(4.5, 5.4);
if (c > 10)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.OverflowException">Thrown when one parameter is max
<exception cref="System.OverflowException">Thrown when one parameter is max
and the other is greater than 0.</exception>
See <see cref="Math.Add(int, int)"/> to add integers.
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</AddDouble>
<SubtractInt>
<summary>
Subtracts <paramref name="b"/> from <paramref name="a"/> and returns the result.
</summary>
<returns>
The difference between two integers.
</returns>
<example>
<code>
int c = Math.Subtract(4, 5);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Subtract(double, double)"/> to subtract doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</SubtractInt>
<SubtractDouble>
<summary>
Subtracts a double <paramref name="b"/> from another double <paramref name="a"/> and returns the
result.
</summary>
<returns>
The difference between two doubles.
</returns>
<example>
<code>
double c = Math.Subtract(4.5, 5.4);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Subtract(int, int)"/> to subtract integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</SubtractDouble>
<MultiplyInt>
<summary>
Multiplies two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The product of two integers.
</returns>
<example>
<code>
int c = Math.Multiply(4, 5);
if (c > 100)
{
Console.WriteLine(c);
}
}
</code>
</example>
See <see cref="Math.Multiply(double, double)"/> to multiply doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</MultiplyInt>
<MultiplyDouble>
<summary>
Multiplies two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The product of two doubles.
</returns>
<example>
<code>
double c = Math.Multiply(4.5, 5.4);
if (c > 100.0)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Multiply(int, int)"/> to multiply integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</MultiplyDouble>
<DivideInt>
<summary>
Divides an integer <paramref name="a"/> by another integer <paramref name="b"/> and returns the
result.
</summary>
<returns>
The quotient of two integers.
</returns>
<example>
<code>
int c = Math.Divide(4, 5);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
See <see cref="Math.Divide(double, double)"/> to divide doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<param name="a">An integer dividend.</param>
<param name="b">An integer divisor.</param>
</DivideInt>
<DivideDouble>
<summary>
Divides a double <paramref name="a"/> by another double <paramref name="b"/> and returns the
result.
</summary>
<returns>
The quotient of two doubles.
</returns>
<example>
<code>
double c = Math.Divide(4.5, 5.4);
if (c > 1.0)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
See <see cref="Math.Divide(int, int)"/> to divide integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<param name="a">A double precision dividend.</param>
<param name="b">A double precision divisor.</param>
</DivideDouble>
</members>
</docs>

Nel codice XML precedente, i commenti in formato documentazione di ogni membro vengono visualizzati
direttamente all'interno di un tag denominato dopo le operazioni eseguite. È possibile scegliere una strategia
personalizzata. Ora che i commenti XML si trovano in un file separato, di seguito viene illustrato come il codice
può essere reso più leggibile usando il tag <include> :

/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <include file='docs.xml' path='docs/members[@name="math"]/Math/*'/>
public class Math
{
// Adds two integers and returns the result
/// <include file='docs.xml' path='docs/members[@name="math"]/AddInt/*'/>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

// Adds two doubles and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/AddDouble/*'/>
public static double Add(double a, double b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();

return a + b;
}

// Subtracts an integer from another and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/SubtractInt/*'/>
public static int Subtract(int a, int b)
{
return a - b;
}

// Subtracts a double from another and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/SubtractDouble/*'/>
public static double Subtract(double a, double b)
{
return a - b;
}

// Multiplies two integers and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/MultiplyInt/*'/>
public static int Multiply(int a, int b)
{
return a * b;
}

// Multiplies two doubles and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/MultiplyDouble/*'/>
public static double Multiply(double a, double b)
{
return a * b;
}

// Divides an integer by another and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/DivideInt/*'/>
public static int Divide(int a, int b)
{
return a / b;
}

// Divides a double by another and returns the result


/// <include file='docs.xml' path='docs/members[@name="math"]/DivideDouble/*'/>
public static double Divide(double a, double b)
{
return a / b;
}
}

Infine si avrà un codice di nuovo leggibile e nessuna informazione sulla documentazione è andata persa.
L'attributo file rappresenta il nome del file XML che contiene la documentazione.
L'attributo path rappresenta una query XPath nel presente tag name del file specificato.
L'attributo name rappresenta l'identificatore di nome nel tag che precede i commenti.
L' id attributo, che può essere usato al posto di name , rappresenta l'ID del tag che precede i commenti.
Tag definiti dall'utente
Tutti i tag specificati in precedenza rappresentano i tag riconosciuti dal compilatore C#. Gli utenti comunque
possono definire tag personalizzati. Strumenti come Sandcastle portano il supporto per tag aggiuntivi <event> ,
ad esempio e <note> , e supportano anche la documentazione degli spazi dei nomi. Gli strumenti di creazione
della documentazione interna o personalizzata possono anche essere usati con i tag standard e con più formati
di output da HTML a PDF.

Consigli
La documentazione del codice è consigliabile per vari motivi. Di seguito vengono illustrate alcune procedure
consigliate, scenari di uso generale e altre operazioni che è necessario conoscere quando si usano i tag in
formato documentazione XML nel codice C#.
Per mantenere una certa coerenza, tutti i tipi visibili pubblicamente e i loro membri devono essere
documentati. Se necessario, è consigliabile eseguire un'operazione completa.
I membri private possono anche essere documentati con commenti XML. Ciò espone tuttavia il
funzionamento interno della libreria che potrebbe essere riservato.
Il minimo necessario è che i tipi e i loro membri abbiano i tag <summary> perché il loro contenuto è
necessario per IntelliSense.
Il testo della documentazione deve essere scritto usando frasi complete che terminano con un punto.
Le classi parziali sono completamente supportate e le informazioni sulla documentazione verranno
concatenate in una singola voce per tale tipo.
Il compilatore verifica la sintassi dei <exception> <include> tag,, <param> , <see> , <seealso> e
<typeparam> .
Il compilatore convalida i parametri che contengono i percorsi dei file e i riferimenti ad altre parti del codice.

Vedi anche
Commenti in formato documentazione XML (Guida per programmatori C#)
Tag consigliati per i commenti relativi alla documentazione (Guida per programmatori C#)
Controllo delle versioni in C#
24/04/2020 • 10 minutes to read • Edit Online

In questa esercitazione si apprenderà il significato del controllo delle versioni in .NET. Verranno anche presentati
i fattori da considerare durante il controllo delle versioni della libreria e verrà descritto l'aggiornamento a una
nuova versione di una libreria.

Creazione di librerie
Gli sviluppatori che hanno creato le librerie .NET per uso pubblico probabilmente si sono trovati in situazioni in
cui è necessario distribuire i nuovi aggiornamenti. Le modalità di gestione di questo processo sono molto
importanti poiché è necessario garantire che non si verifichino problemi durante la transizione del codice
esistente alla nuova versione della libreria. Ecco alcuni aspetti da considerare quando si crea una nuova
versione:
Versionamento Semantico
Versionamento Semantico (SemVer per brevità) è una convenzione di denominazione applicata alle versioni
della libreria per indicare cardine specifici. In teoria, le informazioni sulla versione applicate alla libreria
consentono agli sviluppatori di determinare la compatibilità con i progetti che usano versioni precedenti di
quella libreria.
L'approccio di base a SemVer è il formato a 3 componenti MAJOR.MINOR.PATCH , dove:
MAJOR viene incrementato quando si apportano modifiche incompatibili all'API
MINOR viene incrementato quando si aggiungono funzionalità compatibili con le versioni precedenti
PATCH viene incrementato quando si apportano correzioni dei bug compatibili con le versioni precedenti
Esistono anche dei modi per specificare altri scenari, ad esempio le versioni non definitive, quando si applicano
le informazioni sulla versione alla libreria .NET.
Compatibilità con le versioni precedenti
Quando si rilasciano nuove versioni della libreria, la compatibilità con le versioni precedenti probabilmente è
una delle principali preoccupazioni. Una nuova versione della libreria è compatibile a livello di codice sorgente
con una versione precedente se il codice che dipende dalla versione precedente, quando viene ricompilato,
funziona con la nuova versione. Una nuova versione della libreria è compatibile a livello binario se
un'applicazione che dipende dalla versione precedente funziona, senza ricompilazione, con la nuova versione.
Di seguito sono riportati alcuni aspetti da considerare quando si tenta di gestire la compatibilità della libreria
con le versioni precedenti:
Metodi virtuali: quando si trasforma un metodo virtuale in non virtuale nella nuova versione, sarà necessario
aggiornare i progetti che eseguono l'override di tale metodo. Questa è una modifica sostanziale di grande
impatto ed è fortemente sconsigliata.
Firme del metodo: quando si aggiorna il comportamento di un metodo è necessario modificare anche la
firma, è invece necessario creare un overload in modo che il codice che chiama in tale metodo continuerà a
funzionare. È sempre possibile modificare la firma del metodo precedente per chiamare la firma del nuovo
metodo in modo che l'implementazione resti coerente.
Attributo Obsolete: è possibile usare questo attributo nel codice per specificare le classi o i membri di classe
deprecati che potrebbero essere rimossi nelle versioni future. Ciò consente di predisporre meglio gli
sviluppatori che usano la libreria a eventuali modifiche di rilievo.
Argomenti di metodo facoltativi: se si rendono obbligatori argomenti di metodo che in precedenza erano
facoltativi o si modifica il valore predefinito degli argomenti, tutto il codice che non specifica tali argomenti
dovrà essere aggiornato.

NOTE
Rendere facoltativi gli argomenti facoltativi dovrebbe avere pochissimo effetto, soprattutto se non modifica il
comportamento del metodo.

Più è facile per gli utenti eseguire l'aggiornamento alla nuova versione della libreria, più rapido sarà
l'aggiornamento.
File di configurazione dell'applicazione
Gli sviluppatori .NET molto probabilmente hanno trovato il file app.config nella maggior parte dei tipi di
progetto. Questo semplice file di configurazione è in grado di ottimizzare la distribuzione dei nuovi
aggiornamenti. In genere è necessario progettare le librerie in modo che le app.config informazioni che
potrebbero cambiare regolarmente vengono memorizzate nel file, in questo modo quando tali informazioni
vengono aggiornate, il file di configurazione delle versioni precedenti deve solo essere sostituito con quello
nuovo senza la necessità di ricompilazione della libreria.

Elaborazione delle librerie


Uno sviluppatore che elabora librerie .NET compilate da altri sviluppatori probabilmente sa che una nuova
versione di una libreria potrebbe non essere completamente compatibile con il progetto e che spesso è
necessario aggiornare il codice perché funzioni con le modifiche.
Fortunatamente per te, il linguaggio .NET e l'ecosistema .NET sono dotati di funzionalità e tecniche che
consentono di aggiornare facilmente la nostra app per lavorare con nuove versioni di librerie che potrebbero
introdurre modifiche di rilievo.
Reindirizzamento dell'associazione di assembly
Puoi usare il file app.config per aggiornare la versione di una raccolta usata dall'app. Aggiungendo quello che
viene chiamato un reindirizzamento dell'associazione, è possibile utilizzare la nuova versione della libreria senza
dover ricompilare l'app. L'esempio seguente mostra come aggiornare il file app.config dell'app per usare la
1.0.1 versione della patch anziché ReferencedLibrary la 1.0.0 versione con cui è stata compilata in origine.

<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>

NOTE
Questo approccio funziona solo se la nuova versione di ReferencedLibrary è compatibile a livello binario con
l'applicazione. Vedere la sezione Compatibilità con le versioni precedenti per verificare quando deve essere determinata la
compatibilità.

Nuovo
Usare il modificatore new per nascondere i membri ereditati di una classe di base. In questo modo le classi
derivate possono rispondere agli aggiornamenti nelle classi di base.
Vedere l'esempio seguente:
public class BaseClass
{
public void MyMethod()
{
Console.WriteLine("A base method");
}
}

public class DerivedClass : BaseClass


{
public new void MyMethod()
{
Console.WriteLine("A derived method");
}
}

public static void Main()


{
BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();

b.MyMethod();
d.MyMethod();
}

Output

A base method
A derived method

Nell'esempio precedente si può vedere come DerivedClass nasconde il metodo MyMethod presente in
BaseClass . Ciò significa che quando una classe di base nella nuova versione di una libreria aggiunge un
membro già esistente nella classe derivata, è sufficiente usare il modificatore new per il membro della classe
derivata per nascondere il membro della classe di base.
Se non si specifica alcun modificatore new , una classe derivata nasconderà per impostazione predefinita i
membri in conflitto in una classe di base e il codice verrà comunque compilato anche se viene generato un
avviso del compilatore. Ciò significa che la semplice aggiunta di nuovi membri a una classe esistente rende la
nuova versione della libreria compatibile sia a livello di codice sorgente che a livello binario con il codice che
dipende da essa.
override
Il modificatore override indica che un'implementazione derivata estende l'implementazione di un membro
della classe di base anziché nasconderlo. È necessario che al membro della classe di base sia applicato il
modificatore virtual .
public class MyBaseClass
{
public virtual string MethodOne()
{
return "Method One";
}
}

public class MyDerivedClass : MyBaseClass


{
public override string MethodOne()
{
return "Derived Method One";
}
}

public static void Main()


{
MyBaseClass b = new MyBaseClass();
MyDerivedClass d = new MyDerivedClass();

Console.WriteLine("Base Method One: {0}", b.MethodOne());


Console.WriteLine("Derived Method One: {0}", d.MethodOne());
}

Output

Base Method One: Method One


Derived Method One: Derived Method One

Il modificatore override viene valutato in fase di compilazione e il compilatore genera un errore se non trova
un membro virtuale di cui eseguire l'override.
La vostra conoscenza delle tecniche discusse e la vostra comprensione delle situazioni in cui utilizzarle, andrà un
lungo cammino verso l'allentamento della transizione tra le versioni di una libreria.
Procedura (C#)
02/11/2020 • 7 minutes to read • Edit Online

Nella sezione How to della Guida di C# è possibile trovare risposte rapide alle domande più comuni. In alcuni
casi gli articoli possono essere elencati in più sezioni. Volevamo semplificarne la ricerca rendendoli disponibili in
più percorsi.

Concetti generali di C#
Sono disponibili diversi suggerimenti e consigli che sono procedure comuni per gli sviluppatori C#:
Inizializzare oggetti usando un inizializzatore di oggetto.
Informazioni sulle differenze tra il passaggio a un metodo di uno struct e di una classe.
Usare l'overload degli operatori.
Implementare e chiamare un metodo di estensione personalizzato.
Anche i programmatori C# potrebbero voler usare lo My spazio dei nomi da Visual Basic.
Creare un nuovo metodo per un tipo enum usando i metodi di estensione.
Membri di classi e struct
Per implementare il programma è necessario creare classi e struct. Queste tecniche sono comunemente usate
durante la scrittura di classi o struct.
Dichiarare proprietà implementate automaticamente.
Dichiarare e usare proprietà di lettura/scrittura.
Definire costanti.
Eseguire l'override del metodo ToString per specificare l'output della stringa.
Definire le proprietà astratte.
Usare le funzionalità relative alla documentazione XML per documentare il codice.
Implementare in modo esplicito i membri di interfaccia per mantenere concisa l'interfaccia pubblica.
Implementare in modo esplicito i membri di due interfacce.
Uso delle raccolte
Questi articoli contengono informazioni sull'uso delle raccolte di dati.
Inizializzare un dizionario con un inizializzatore di insieme.

Uso delle stringhe


Le stringhe sono il tipo di dati di base usato per visualizzare o modificare il testo. Questi articoli illustrano le
procedure comuni da seguire con le stringhe.
Confrontare le stringhe.
Modificare il contenuto di una stringa.
Determinare se una stringa rappresenta un numero.
Usare String.Split per separare stringhe.
Unire più stringhe in una.
Eseguire la ricerca di testo in una stringa.
Eseguire la conversione tra tipi
Potrebbe essere necessario convertire un oggetto in un tipo diverso.
Determinare se una stringa rappresenta un numero.
Eseguire la conversione tra stringhe che rappresentano numeri esadecimali e numero.
Convertire una stringa in un oggetto DateTime .
Convertire una matrice di byte in un Integer.
Convertire una stringa in un numero.
Usare i criteri di ricerca e gli operatori as e is per eseguire il cast sicuro a un tipo diverso.
Definire le conversioni di tipi personalizzate.
Determinare se un tipo è un tipo di valore nullable.
Eseguire la conversione tra tipi di valore nullable e non nullable.

Confronti di uguaglianza e ordinamento


È possibile creare tipi che definiscono regole specifiche per verificare l'uguaglianza o definire un ordinamento
naturale tra oggetti di quel tipo.
Testare l'uguaglianza di riferimenti.
Definire l'uguaglianza di valori per un tipo.

Gestione delle eccezioni


I programmi .NET segnalano il lavoro incompleto dei metodi generando eccezioni. Questi articoli contengono
informazioni sull'uso delle eccezioni.
Gestire eccezioni usando try e catch .
Eseguire la pulizia delle risorse usando le clausole finally .
Recuperare eccezioni non CLS (Common Language Specification).

Delegati ed eventi
I delegati e gli eventi offrono una funzionalità per strategie che riguardano blocchi di codice con accoppiamento
libero.
Dichiarare, crearne un'istanza e usare delegati.
Combinare delegati multicast.
Gli eventi offrono un meccanismo per pubblicare o sottoscrivere notifiche.
Eseguire e annullare la sottoscrizione di eventi.
Implementare eventi dichiarati in interfacce.
Conformità alle linee guida di .NET quando il codice pubblica gli eventi.
Generare eventi definiti in classi base da classi derivate.
Implementare funzioni di accesso a eventi personalizzati.

Procedure LINQ
LINQ consente di scrivere codice per eseguire query su qualsiasi origine dati che supporta il criterio di
espressione di query LINQ. Questi articoli contengono informazioni sul criterio e sull'uso di origini dati diverse.
Eseguire la query di una raccolta.
Usare espressioni lambda in una query.
Usare var in espressioni di query.
Restituire sottoinsiemi di proprietà degli elementi in una query.
Scrivere query con filtro complesso.
Ordinare elementi di un'origine dati.
Ordinare elementi in base a più chiavi.
Controllare il tipo di una proiezione.
Contare le occorrenze di un valore in una sequenza di origine.
Calcolare valori intermedi.
Unire dati di più origini.
Trovare la differenza dei set tra due sequenze.
Eseguire il debug di risultati di query vuoti.
Aggiungere metodi personalizzati per le query LINQ.

Più thread ed elaborazione asincrona


I programmi moderni usano spesso operazioni asincrone. Questi articoli contengono informazioni sull'uso di
queste tecniche.
Migliorare le prestazioni di operazioni asincrone usando System.Threading.Tasks.Task.WhenAll .
Eseguire più richieste Web in parallelo tramite async e await .
Usare un pool di thread.

Argomenti della riga di comando per il programma


I programmi C# contengono generalmente argomenti della riga di comando. Questi articoli spiegano come
accedere a tali argomenti ed elaborarli.
Recuperare tutti gli argomenti della riga di comando con for .
Come separare le stringhe usando String. Split in
C#
28/01/2021 • 4 minutes to read • Edit Online

Il metodo String.Split crea una matrice di sottostringhe dividendo la stringa di input in base a uno o più
delimitatori. Questo metodo è spesso il modo più semplice per separare una stringa sui limiti di parola. Viene
usato anche per suddividere le stringhe in altri caratteri o stringhe specifiche.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

Il codice seguente divide una frase comune in una matrice di stringhe per ogni parola.

string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

Ogni istanza di un carattere separatore genera un valore nella matrice restituita. Caratteri separatori consecutivi
generano una stringa vuota come valore nella matrice restituita. È possibile vedere come viene creata una
stringa vuota nell'esempio seguente, che usa il carattere spazio come separatore.

string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

Questo comportamento rende più semplice per i formati come i file con valori delimitati da virgole (CSV) che
rappresentano dati tabulari. Due virgole consecutive rappresentano una colonna vuota.
È possibile passare un parametro StringSplitOptions.RemoveEmptyEntries facoltativo per escludere tutte le
stringhe vuote nella matrice restituita. Per un'elaborazione più complessa della raccolta restituita, è possibile
modificare la sequenza di risultati tramite LINQ.
String.Split può usare più caratteri separatori. Nell'esempio seguente vengono usati spazi, virgole, punti, due
punti e tabulazioni come caratteri di separazione, che vengono passati a Split in una matrice. Il ciclo nella parte
inferiore del codice visualizza ogni parola nella matrice restituita.
char[] delimiterChars = { ' ', ',', '.', ':', '\t' };

string text = "one\ttwo three:four,five six seven";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(delimiterChars);


System.Console.WriteLine($"{words.Length} words in text:");

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

Istanze consecutive di un separatore generano una stringa vuota nella matrice di output:

char[] delimiterChars = { ' ', ',', '.', ':', '\t' };

string text = "one\ttwo :,five six seven";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(delimiterChars);


System.Console.WriteLine($"{words.Length} words in text:");

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

String.Split può accettare una matrice di stringhe (sequenze di caratteri, anziché caratteri singoli, con la funzione
di separatori per l'analisi della stringa di destinazione).

string[] separatingStrings = { "<<", "..." };

string text = "one<<two......three<four";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(separatingStrings, System.StringSplitOptions.RemoveEmptyEntries);


System.Console.WriteLine($"{words.Length} substrings in text:");

foreach (var word in words)


{
System.Console.WriteLine(word);
}

Vedere anche
Estrai elementi da una stringa
Guida per programmatori C#
Stringhe
Espressioni regolari .NET
Come concatenare più stringhe (Guida a C#)
02/11/2020 • 6 minutes to read • Edit Online

La concatenazione è il processo di aggiunta di una stringa alla fine di un'altra stringa. Le stringhe vengono
concatenate usando l'operatore + . Per i valori letterali e le costanti di stringa, la concatenazione viene eseguita
in fase di compilazione; non viene eseguita alcuna concatenazione in fase di esecuzione. Per le variabili di
stringa, la concatenazione viene eseguita solo in fase di esecuzione.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

L'esempio seguente usa la concatenazione per suddividere un valore letterale di stringa lungo in stringhe più
piccole per migliorare la leggibilità nel codice sorgente. Queste parti sono concatenate in una singola stringa in
fase di compilazione. Non c'è alcun impatto sulle prestazioni in fase di esecuzione, indipendentemente dal
numero di stringhe coinvolte.

// Concatenation of literals is performed at compile time, not run time.


string text = "Historically, the world of data and the world of objects " +
"have not been well integrated. Programmers work in C# or Visual Basic " +
"and also in SQL or XQuery. On the one side are concepts such as classes, " +
"objects, fields, inheritance, and .NET Framework APIs. On the other side " +
"are tables, columns, rows, nodes, and separate languages for dealing with " +
"them. Data types often require translation between the two worlds; there are " +
"different standard functions. Because the object world has no notion of query, a " +
"query can only be represented as a string without compile-time type checking or " +
"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
"objects in memory is often tedious and error-prone.";

System.Console.WriteLine(text);

Per concatenare le variabili di stringa, è possibile usare gli + += operatori o, l' interpolazione di stringhe o i
String.Format metodi, String.Concat String.Join o StringBuilder.Append . L'operatore + è facile da usare e rende
il codice intuitivo. Anche se si usano diversi operatori + in un'unica istruzione, il contenuto della stringa viene
copiato una sola volta. Il codice seguente mostra esempi dell'uso degli operatori + e += per concatenare le
stringhe:

string userName = "<Type your name here>";


string dateString = DateTime.Today.ToShortDateString();

// Use the + and += operators for one-time concatenations.


string str = "Hello " + userName + ". Today is " + dateString + ".";
System.Console.WriteLine(str);

str += " How are you today?";


System.Console.WriteLine(str);

In alcune espressioni risulta più semplice concatenare le stringhe usando l'interpolazione di stringhe, come
illustrato nel codice seguente:

string userName = "<Type your name here>";


string date = DateTime.Today.ToShortDateString();

// Use string interpolation to concatenate strings.


string str = $"Hello {userName}. Today is {date}.";
System.Console.WriteLine(str);

str = $"{str} How are you today?";


System.Console.WriteLine(str);

NOTE
Nelle operazioni di concatenazione di stringhe il compilatore C# tratta una stringa Null come se fosse una stringa vuota.

Un altro metodo per concatenare le stringhe è String.Format. Questo metodo è utile quando si compila una
stringa da un numero ridotto di stringhe dei componenti.
In altri casi, è possibile combinare le stringhe in un ciclo in cui non si conosce il numero di stringhe di origine
combinate e il numero effettivo di stringhe di origine può essere di grandi dimensioni. La classe StringBuilder è
stata progettata per questi scenari. Il codice seguente usa il metodo Append della classe StringBuilder per
concatenare le stringhe.

// Use StringBuilder for concatenation in tight loops.


var sb = new System.Text.StringBuilder();
for (int i = 0; i < 20; i++)
{
sb.AppendLine(i.ToString());
}
System.Console.WriteLine(sb.ToString());

Per altre informazioni sui motivi per scegliere la concatenazione di stringhe o la StringBuilder classe, vedere.
Un'altra opzione per unire le stringhe di una raccolta consiste nell'usare il metodo String.Concat. Usare
String.Join il metodo se le stringhe di origine devono essere separate da un delimitatore. Il codice seguente
combina una matrice di parole con entrambi i metodi:

string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog." };

var unreadablePhrase = string.Concat(words);


System.Console.WriteLine(unreadablePhrase);

var readablePhrase = string.Join(" ", words);


System.Console.WriteLine(readablePhrase);

Infine, è possibile usare LINQ e il metodo Enumerable.Aggregate per unire le stringhe di una raccolta. Questo
metodo combina le stringhe di origine usando un'espressione lambda. L'espressione lambda esegue le
operazioni necessarie per aggiungere ogni stringa all'accumulo esistente. L'esempio seguente combina una
matrice di parole aggiungendo uno spazio tra ogni parola nella matrice:

string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog." };

var phrase = words.Aggregate((partialPhrase, word) =>$"{partialPhrase} {word}");


System.Console.WriteLine(phrase);
Vedere anche
String
StringBuilder
Guida per programmatori C#
Stringhe
Come cercare stringhe
02/11/2020 • 7 minutes to read • Edit Online

È possibile usare due strategie principali per la ricerca del testo nelle stringhe. I metodi della classe String
consentono di cercare testo specifico. Le espressioni regolari consentono di cercare modelli nel testo.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

Il tipo string, che è un alias per la classe System.String, fornisce diversi metodi utili per la ricerca dei contenuti di
una stringa. Alcuni di questi sono Contains, StartsWith, EndsWith, IndexOf e LastIndexOf. La classe
System.Text.RegularExpressions.Regex offre un vocabolario avanzato per la ricerca di modelli nel testo. In questo
articolo vengono illustrate queste tecniche e viene spiegato come scegliere il metodo migliore per le specifiche
esigenze.

Una stringa contiene testo?


I String.Contains String.StartsWith metodi, e String.EndsWith cercano un testo specifico in una stringa.
Nell'esempio seguente vengono illustrati ognuno di questi metodi e una variante che utilizza una ricerca senza
distinzione tra maiuscole e minuscole:

string factMessage = "Extension methods have all the capabilities of regular static methods.";

// Write the string and include the quotation marks.


Console.WriteLine($"\"{factMessage}\"");

// Simple comparisons are always case sensitive!


bool containsSearchResult = factMessage.Contains("extension");
Console.WriteLine($"Contains \"extension\"? {containsSearchResult}");

// For user input and strings that will be displayed to the end user,
// use the StringComparison parameter on methods that have it to specify how to match strings.
bool ignoreCaseSearchResult = factMessage.StartsWith("extension",
System.StringComparison.CurrentCultureIgnoreCase);
Console.WriteLine($"Starts with \"extension\"? {ignoreCaseSearchResult} (ignoring case)");

bool endsWithSearchResult = factMessage.EndsWith(".", System.StringComparison.CurrentCultureIgnoreCase);


Console.WriteLine($"Ends with '.'? {endsWithSearchResult}");

L'esempio precedente dimostra un aspetto importante per l'uso di questi metodi. Le ricerche fanno
distinzione tra maiuscole e minuscole per impostazione predefinita. Usare il
StringComparison.CurrentCultureIgnoreCase valore di enumerazione per specificare una ricerca senza
distinzione tra maiuscole e minuscole.

Qual è la posizione del testo cercato in una stringa?


Anche i metodi IndexOf e LastIndexOf consentono di cercare testo nelle stringhe. Questi metodi restituiscono la
posizione del testo cercato. Se il testo non viene trovato, restituiscono -1 . L'esempio seguente mostra una
ricerca della prima e dell'ultima occorrenza della parola "methods" e visualizza il testo che intercorre.

string factMessage = "Extension methods have all the capabilities of regular static methods.";

// Write the string and include the quotation marks.


Console.WriteLine($"\"{factMessage}\"");

// This search returns the substring between two strings, so


// the first index is moved to the character just after the first string.
int first = factMessage.IndexOf("methods") + "methods".Length;
int last = factMessage.LastIndexOf("methods");
string str2 = factMessage.Substring(first, last - first);
Console.WriteLine($"Substring between \"methods\" and \"methods\": '{str2}'");

Ricerca di testo specifico con espressioni regolari


La classe System.Text.RegularExpressions.Regex può essere usata per eseguire la ricerca di stringhe. Queste
ricerche possono usare modelli di testo semplici, ma anche piuttosto complicati.
L'esempio di codice seguente cerca la parola "the" o "their" in una frase, ignorando la combinazione di
maiuscole e minuscole. Il metodo statico Regex.IsMatch esegue la ricerca. È necessario passare la stringa da
cercare e i criteri di ricerca. In questo caso, un terzo argomento specifica una ricerca senza distinzione tra
maiuscole e minuscole. Per altre informazioni, vedere System.Text.RegularExpressions.RegexOptions.
I criteri di ricerca descrivono il testo da cercare. La tabella seguente descrive ogni elemento dei criteri di ricerca.
Nella tabella seguente viene usato il singolo oggetto \ , che deve essere preceduto da un carattere di escape
come \\ in una stringa C#.

C RIT ERIO SIGN IF IC ATO

the corrisponde al testo "the"

(eir)? corrisponde a 0 o 1 occorrenza di "eir"

\s corrisponde a un carattere spazio vuoto


string[] sentences =
{
"Put the water over there.",
"They're quite thirsty.",
"Their water bottles broke."
};

string sPattern = "the(ir)?\\s";

foreach (string s in sentences)


{
Console.Write($"{s,24}");

if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
Console.WriteLine($" (match for '{sPattern}' found)");
}
else
{
Console.WriteLine();
}
}

TIP
I metodi string rappresentano in genere una scelta migliore per cercare stringhe esatte. Le espressioni regolari sono
migliori quando si cerca un modello in una stringa di origine.

Una stringa segue un modello?


Il codice seguente usa le espressioni regolari per convalidare il formato di ogni stringa in una matrice. Per la
convalida è necessario che ogni stringa abbia il formato di un numero di telefono costituito da tre gruppi di cifre
separate da trattini, di cui i primi due gruppi contengono tre cifre e il terzo ne contiene quattro. I criteri di ricerca
usano l'espressione regolare ^\\d{3}-\\d{3}-\\d{4}$ . Per altre informazioni, vedere Linguaggio di espressioni
regolari - Riferimento rapido.

C RIT ERIO SIGN IF IC ATO

^ corrisponde all'inizio della stringa

\d{3} corrisponde esattamente a 3 caratteri numerici

- corrisponde al carattere '-'

\d{3} corrisponde esattamente a 3 caratteri numerici

- corrisponde al carattere '-'

\d{4} corrisponde esattamente a 4 caratteri numerici

$ corrisponde alla fine della stringa


string[] numbers =
{
"123-555-0190",
"444-234-22450",
"690-555-0178",
"146-893-232",
"146-555-0122",
"4007-555-0111",
"407-555-0111",
"407-2-5555",
"407-555-8974",
"407-2ab-5555",
"690-555-8148",
"146-893-232-"
};

string sPattern = "^\\d{3}-\\d{3}-\\d{4}$";

foreach (string s in numbers)


{
Console.Write($"{s,14}");

if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
{
Console.WriteLine(" - valid");
}
else
{
Console.WriteLine(" - invalid");
}
}

Questo singolo criterio di ricerca corrisponde a molte stringhe valide. Le espressioni regolari sono preferibili per
eseguire ricerche o convalide in base a un modello, anziché a una singola stringa di testo.

Vedi anche
Guida per programmatori C#
Stringhe
LINQ e stringhe
System.Text.RegularExpressions.Regex
Espressioni regolari .NET
Linguaggio di espressioni regolari-riferimento rapido
Procedure consigliate per l'uso delle stringhe in .NET
Come modificare il contenuto di una stringa in C#
02/11/2020 • 9 minutes to read • Edit Online

Questo articolo illustra diverse tecniche per produrre un oggetto string modificando un oggetto string
esistente. Tutte le tecniche illustrate restituiscono il risultato delle modifiche come nuovo oggetto string . Per
dimostrare che le stringhe originali e modificate sono istanze distinte, gli esempi archiviano il risultato in una
nuova variabile. È possibile esaminare l'originale string e il nuovo oggetto modificato string quando si
eseguono tutti gli esempi.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

Questo articolo illustra diverse tecniche. È possibile sostituire il testo esistente. È possibile cercare criteri e
sostituire il testo corrispondente ai criteri con altro testo. È possibile considerare una stringa come sequenza di
caratteri. È anche possibile usare metodi pratici per la rimozione degli spazi. Scegliere le tecniche più simili a
quelle dello scenario.

Sostituire il testo
Il codice seguente crea una nuova stringa sostituendo il testo esistente con altro testo.

string source = "The mountains are behind the clouds today.";

// Replace one substring with another with String.Replace.


// Only exact matches are supported.
var replacement = source.Replace("mountains", "peaks");
Console.WriteLine($"The source string is <{source}>");
Console.WriteLine($"The updated string is <{replacement}>");

Il codice precedente illustra questa proprietà non modificabile delle stringhe. Come si vede nell'esempio
precedente, la stringa originale, source , non viene modificata. Il metodo String.Replace crea un nuovo oggetto
string contenente le modifiche.

Il metodo Replace può sostituire stringhe o caratteri singoli. In entrambi i casi, viene sostituita ogni ricorrenza
del testo cercato. Nell'esempio seguente tutte le sequenze di caratteri ' ' vengono sostituite da '_':

string source = "The mountains are behind the clouds today.";

// Replace all occurrences of one char with another.


var replacement = source.Replace(' ', '_');
Console.WriteLine(source);
Console.WriteLine(replacement);

La stringa di origine non viene modificata e viene restituita una nuova stringa con la sostituzione.
Eliminare gli spazi
È possibile usare i metodi String.Trim, String.TrimStart e String.TrimEnd per rimuovere tutti gli spazi iniziali o
finali. Il codice seguente visualizza un esempio con ogni metodo. La stringa di origine resta invariata e i metodi
restituiscono una nuova stringa con il contenuto modificato.

// Remove trailing and leading white space.


string source = " I'm wider than I need to be. ";
// Store the results in a new string variable.
var trimmedResult = source.Trim();
var trimLeading = source.TrimStart();
var trimTrailing = source.TrimEnd();
Console.WriteLine($"<{source}>");
Console.WriteLine($"<{trimmedResult}>");
Console.WriteLine($"<{trimLeading}>");
Console.WriteLine($"<{trimTrailing}>");

Rimuovere il testo
È possibile rimuovere testo da una stringa usando il metodo String.Remove. Questo metodo rimuove un
determinato numero di caratteri a partire da un indice specifico. Nell'esempio seguente viene illustrato come
usare String.IndexOf seguito da Remove per rimuovere testo da una stringa:

string source = "Many mountains are behind many clouds today.";


// Remove a substring from the middle of the string.
string toRemove = "many ";
string result = string.Empty;
int i = source.IndexOf(toRemove);
if (i >= 0)
{
result= source.Remove(i, toRemove.Length);
}
Console.WriteLine(source);
Console.WriteLine(result);

Sostituire i criteri corrispondenti


È possibile usare le espressioni regolari per sostituire il testo corrispondente a determinati criteri con nuovo
testo, che a sua volta può essere definito da un criterio. Nell'esempio seguente viene usata la classe
System.Text.RegularExpressions.Regex per trovare un criterio in una stringa di origine e sostituirlo con la
combinazione corretta di maiuscole e minuscole. Il metodo Regex.Replace(String, String, MatchEvaluator,
RegexOptions) accetta una funzione che include tra i suoi argomenti la logica di sostituzione. In questo esempio
la funzione ( LocalReplaceMatchCase ) è una funzione locale dichiarata all'interno del metodo di esempio.
LocalReplaceMatchCase usa la classe System.Text.StringBuilder per compilare la stringa di sostituzione con la
combinazione corretta di maiuscole e minuscole.
Le espressioni regolari sono utili soprattutto per la ricerca e sostituzione di testo corrispondente a un criterio,
anziché di testo noto. Per ulteriori informazioni, vedere come eseguire la ricerca di stringhe. Il criterio di ricerca
"the\s" cerca la parola "the" seguita da uno spazio. Tale parte del criterio garantisce che nella stringa di origine
non venga rilevata la corrispondenza con "there". Per altre informazioni sugli elementi del linguaggio delle
espressioni regolari, vedere Linguaggio di espressioni regolari - Riferimento rapido.
string source = "The mountains are still there behind the clouds today.";

// Use Regex.Replace for more flexibility.


// Replace "the" or "The" with "many" or "Many".
// using System.Text.RegularExpressions
string replaceWith = "many ";
source = System.Text.RegularExpressions.Regex.Replace(source, "the\\s", LocalReplaceMatchCase,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
Console.WriteLine(source);

string LocalReplaceMatchCase(System.Text.RegularExpressions.Match matchExpression)


{
// Test whether the match is capitalized
if (Char.IsUpper(matchExpression.Value[0]))
{
// Capitalize the replacement string
System.Text.StringBuilder replacementBuilder = new System.Text.StringBuilder(replaceWith);
replacementBuilder[0] = Char.ToUpper(replacementBuilder[0]);
return replacementBuilder.ToString();
}
else
{
return replaceWith;
}
}

Il metodo StringBuilder.ToString restituisce una stringa non modificabile con il contenuto dell'oggetto
StringBuilder.

Modifica di caratteri singoli


È possibile produrre una matrice di caratteri da una stringa, modificare il contenuto della matrice e quindi creare
una nuova stringa dal contenuto modificato della matrice.
Nell'esempio seguente viene illustrato come sostituire un gruppo di caratteri in una stringa. In primo luogo si
usa il metodo String.ToCharArray() per creare una matrice di caratteri. Il metodo IndexOf viene usato per trovare
l'indice iniziale della parola "fox". I tre caratteri successivi vengono sostituiti con una parola diversa. Infine viene
creata una nuova stringa dalla matrice di caratteri aggiornata.

string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);

char[] phraseAsChars = phrase.ToCharArray();


int animalIndex = phrase.IndexOf("fox");
if (animalIndex != -1)
{
phraseAsChars[animalIndex++] = 'c';
phraseAsChars[animalIndex++] = 'a';
phraseAsChars[animalIndex] = 't';
}

string updatedPhrase = new string(phraseAsChars);


Console.WriteLine(updatedPhrase);

Compilare contenuto stringa a livello di codice


Poiché le stringhe non sono modificabili, negli esempi precedenti vengono create stringhe temporanee o matrici
di caratteri. Negli scenari a prestazioni elevate può essere utile evitare queste allocazioni di heap. .NET Core
fornisce un String.Create metodo che consente di compilare a livello di codice il contenuto di una stringa tramite
un callback, evitando al tempo stesso le allocazioni di stringhe temporanee intermedie.
// constructing a string from a char array, prefix it with some additional characters
char[] chars = { 'a', 'b', 'c', 'd', '\0' };
int length = chars.Length + 2;
string result = string.Create(length, chars, (Span<char> strContent, char[] charArray) =>
{
strContent[0] = '0';
strContent[1] = '1';
for (int i = 0; i < charArray.Length; i++)
{
strContent[i + 2] = charArray[i];
}
});

Console.WriteLine(result);

È possibile modificare una stringa in un blocco fisso con codice non sicuro, ma è for temente sconsigliata
modificare il contenuto della stringa dopo la creazione di una stringa. In questo modo si interrompono le
operazioni in modi imprevedibili. Se, ad esempio, un utente esegue il interning di una stringa con lo stesso
contenuto, otterrà la copia e non si prevede di modificare la stringa.

Vedi anche
Espressioni regolari .NET
Linguaggio di espressioni regolari-riferimento rapido
Come confrontare stringhe in C#
02/11/2020 • 18 minutes to read • Edit Online

Il confronto delle stringhe viene eseguito per rispondere a una delle due domande seguenti: "Queste due
stringhe sono uguali?" oppure "In che ordine vengono disposte queste stringhe quando si esegue
l'ordinamento?"
Queste due domande vengono complicate da fattori propri dei confronti fra stringhe:
È possibile scegliere un confronto ordinale o linguistico.
È possibile scegliere se la distinzione maiuscole/minuscole è importante.
È possibile scegliere confronti specifici in base alle impostazioni cultura.
I confronti linguistici dipendono dalle impostazioni cultura e dalla piattaforma.

NOTE
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET.
Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile
modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella
finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del
compilatore C#.

Quando si confrontano le stringhe, si definisce un ordine tra di esse. I confronti vengono usati per ordinare una
sequenza di stringhe. Quando la sequenza è disposta in un ordine noto, l'esecuzione di una ricerca risulta più
semplice sia per gli utenti che per le applicazioni. Altri confronti possono verificare se le stringhe sono uguali.
Questi controlli di similitudine sono simili alla verifica di uguaglianza, ma è possibile che alcune differenze, quali
le maiuscole/minuscole, vengano ignorate.

Confronti ordinali predefiniti


Per impostazione predefinita, le operazioni più comuni sono:
String.CompareTo
String.Equals
String.Equalitye String.Inequality , ovvero gli operatori di uguaglianza == e != rispettivamente
eseguire un confronto ordinale con distinzione tra maiuscole e minuscole e, se necessario, usare le impostazioni
cultura correnti. Nell'esempio seguente viene illustrato quanto segue:

string root = @"C:\users";


string root2 = @"C:\Users";

bool result = root.Equals(root2);


Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

result = root.Equals(root2, StringComparison.Ordinal);


Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not
equal")}");

Il confronto ordinale predefinito non prende in considerazione le regole linguistiche quando si confrontano le
stringhe. ma viene confrontato il valore binario di ogni oggetto Char nelle due stringhe. Di conseguenza, anche il
confronto ordinale predefinito prevede la distinzione tra maiuscole e minuscole.
Il test per l'uguaglianza con String.Equals e == gli != operatori e differisce dal confronto tra stringhe usando i
String.CompareTo Compare(String, String) metodi e. Mentre i test per l'uguaglianza eseguono un confronto
ordinale con distinzione tra maiuscole e minuscole, i metodi di confronto eseguono un confronto con
distinzione tra maiuscole e minuscole e con distinzione delle impostazioni cultura. Dato che i metodi di
confronto predefiniti spesso eseguono diversi tipi di confronti, è consigliabile chiarire sempre l'intento del
codice chiamando un overload che specifichi in modo esplicito il tipo di confronto da eseguire.

Confronti ordinali senza distinzione tra maiuscole e minuscole


Il metodo String.Equals(String, StringComparison) consente di specificare un valore StringComparison pari a
StringComparison.OrdinalIgnoreCase per un confronto ordinale senza distinzione tra maiuscole e minuscole. È
anche disponibile un metodo String.Compare(String, String, StringComparison) statico che esegue un confronto
ordinale senza distinzione tra maiuscole e minuscole, se si specifica il valore
StringComparison.OrdinalIgnoreCase per l'argomento StringComparison. Questi metodi sono illustrati nella
figura seguente.

string root = @"C:\users";


string root2 = @"C:\Users";

bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);


bool areEqual = String.Equals(root, root2, StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType: StringComparison.OrdinalIgnoreCase);

Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not
equal.")}");
if (comparison < 0)
Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
Console.WriteLine($"<{root}> is greater than <{root2}>");
else
Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");

Quando si esegue un confronto ordinale senza distinzione tra maiuscole e minuscole, questi metodi usano le
convenzioni di combinazione di maiuscole e minuscole delle impostazioni cultura inglese non dipendenti da
paese/area geografica.

Confronti linguistici
Le stringhe possono anche essere ordinate in base alle regole linguistiche delle impostazioni cultura correnti.
Questo ordinamento è detto anche "ordinamento di Word". Quando si esegue un confronto linguistico, è
possibile che a determinati caratteri Unicode non alfanumerici venga assegnata una valenza specifica. Ad
esempio, il trattino "-" può avere un peso ridotto in modo che "co-op" e "Coop" siano visualizzati uno accanto
all'altro nell'ordinamento. Alcuni caratteri Unicode possono anche essere equivalenti a una sequenza di istanze
di Char. L'esempio seguente usa la frase "Ballano per strada." in tedesco con "ss" (U+0073 U+0073) in una
stringa e 'ß' (U+00DF) in un'altra. Dal punto di vista linguistico (in Windows), "ss" equivale al carattere tedesco
Esszet "ß", sia nelle impostazioni cultura "en-US" che in quelle "de-DE".
string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");


Console.WriteLine($"Second sentence is <{second}>");

bool equal = String.Equals(first, second, StringComparison.InvariantCulture);


Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")} equal.");
showComparison(first, second);

string word = "coop";


string words = "co-op";
string other = "cop";

showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

Questo esempio dimostra che i confronti linguistici sono dipendenti dal sistema operativo. L'host della finestra
interattiva è un host Linux. Il confronto linguistico e il confronto ordinale producono gli stessi risultati. Se si
esegue lo stesso esempio in un host Windows, viene visualizzato l'output seguente:

<coop> is less than <co-op> using invariant culture


<coop> is greater than <co-op> using ordinal comparison
<coop> is less than <cop> using invariant culture
<coop> is less than <cop> using ordinal comparison
<co-op> is less than <cop> using invariant culture
<co-op> is less than <cop> using ordinal comparison

In Windows, l'ordinamento di "cop", "coop" e "co-op" cambia quando si passa da un confronto linguistico a un
confronto ordinale. Anche l'ordinamento delle due frasi in tedesco cambia a seconda del tipo di confronto.

Confronti che usano impostazioni cultura specifiche


Questo esempio archivia oggetti CultureInfo per le impostazioni cultura en-US e de-DE. I confronti vengono
eseguiti usando un oggetto CultureInfo per garantire un confronto dipendente dalle impostazioni cultura.
Le impostazioni cultura usate hanno effetto sui confronti linguistici. L'esempio seguente visualizza i risultati del
confronto tra le due frasi in lingua tedesca usando le impostazioni cultura "en-US" e le impostazioni cultura "de-
DE":
string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");


Console.WriteLine($"Second sentence is <{second}>");

var en = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare


// overload that takes a StringComparison value.
int i = String.Compare(first, second, en, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");

var de = new System.Globalization.CultureInfo("de-DE");


i = String.Compare(first, second, de, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");

bool b = String.Equals(first, second, StringComparison.CurrentCulture);


Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");

string word = "coop";


string words = "co-op";
string other = "cop";

showComparison(word, words, en);


showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo culture)
{
int compareLinguistic = String.Compare(one, two, en, System.Globalization.CompareOptions.None);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using en-US culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using en-US culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using en-US culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

In genere i confronti con rilevamento delle impostazioni cultura vengono usati per confrontare e ordinare
stringhe immesse dagli utenti. I caratteri e le convenzioni di ordinamento di queste stringhe possono variare a
seconda delle impostazioni locali del computer dell'utente. Anche stringhe che contengono caratteri identici
potrebbero essere ordinate in modo diverso a seconda delle impostazioni cultura del thread corrente. Provare
inoltre a eseguire questo codice di esempio localmente in un computer Windows e ottenere i risultati seguenti:

<coop> is less than <co-op> using en-US culture


<coop> is greater than <co-op> using ordinal comparison
<coop> is less than <cop> using en-US culture
<coop> is less than <cop> using ordinal comparison
<co-op> is less than <cop> using en-US culture
<co-op> is less than <cop> using ordinal comparison

I confronti linguistici dipendono dalle impostazioni cultura correnti e dal sistema operativo. Prendere in
considerazione questo quando si lavora con i confronti tra stringhe.

Ordinamento linguistico e ricerca di stringhe nelle matrici


Gli esempi seguenti illustrano come eseguire l'ordinamento e la ricerca di stringhe in una matrice mediante un
confronto linguistico dipendente dalle impostazioni cultura correnti. Si usano i metodi Array statici che
accettano un parametro System.StringComparer.
Questo esempio illustra come ordinare una matrice di stringhe in base alle impostazioni cultura correnti:

string[] lines = new string[]


{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Console.WriteLine("\n\rSorted order:");

// Specify Ordinal to demonstrate the different behavior.


Array.Sort(lines, StringComparer.CurrentCulture);

foreach (string s in lines)


{
Console.WriteLine($" {s}");
}

Quando la matrice è ordinata, è possibile cercare voci con una ricerca binaria. Una ricerca binaria inizia a metà
della raccolta per determinare quale metà contiene la stringa cercata. Ogni confronto successivo divide a metà la
parte rimanente della raccolta. La matrice viene ordinata usando StringComparer.CurrentCulture. La funzione
locale ShowWhere visualizza informazioni sul punto in cui è stata trovata la stringa. Se la stringa non viene
trovata, il valore restituito indica la posizione in cui si trovava.
string[] lines = new string[]
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);

string searchString = @"c:\public\TEXTFILE.TXT";


Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(T[] array, int index)


{
if (index < 0)
{
index = ~index;

Console.Write("Not found. Sorts between: ");

if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{array[index - 1]} and ");

if (index == array.Length)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{array[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}

Ordinamento e ricerca ordinali nelle raccolte


Il codice seguente usa la classe della raccolta System.Collections.Generic.List<T> per memorizzare le stringhe.
Le stringhe vengono ordinate usando il metodo List<T>.Sort. Questo metodo richiede un delegato che
confronta e ordina due stringhe. La funzione di confronto è resa disponibile dal metodo String.CompareTo.
Eseguire l'esempio e osservare l'ordine. Questa operazione di ordinamento utilizza un ordinamento ordinale
con distinzione tra maiuscole e minuscole. Per specificare regole di confronto diverse sarebbe necessario usare i
metodi String.Compare statici.
List<string> lines = new List<string>
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Console.WriteLine("\n\rSorted order:");

lines.Sort((left, right) => left.CompareTo(right));


foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Dopo l'ordinamento è possibile eseguire ricerche nell'elenco di stringhe usando una ricerca binaria. L'esempio
seguente illustra come eseguire una ricerca nell'elenco ordinato usando la stessa funzione di confronto. La
funzione locale ShowWhere visualizza il punto in cui si trova o dovrebbe trovarsi il testo cercato:
List<string> lines = new List<string>
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));

string searchString = @"c:\public\TEXTFILE.TXT";


Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(IList<T> collection, int index)


{
if (index < 0)
{
index = ~index;

Console.Write("Not found. Sorts between: ");

if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{collection[index - 1]} and ");

if (index == collection.Count)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{collection[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}

Assicurarsi di usare sempre lo stesso tipo di confronto per l'ordinamento e la ricerca. L'uso di tipi di confronto
diversi per l'ordinamento e la ricerca produce risultati imprevisti.
Le classi Collection, ad esempio System.Collections.Hashtable,
System.Collections.Generic.Dictionary<TKey,TValue>, e System.Collections.Generic.List<T> contengono
costruttori che accettano un parametro System.StringComparer quando il tipo degli elementi o delle chiavi è
string . In generale è necessario usare sempre questi costruttori, quando possibile, e specificare il parametro
StringComparer.Ordinal o StringComparer.OrdinalIgnoreCase.

Uguaglianza di riferimenti e centralizzazione delle stringhe.


ReferenceEquals non è stato usato in nessun esempio. Questo metodo determina se due stringhe sono lo stesso
oggetto, che può causare risultati incoerenti nei confronti tra stringhe. L'esempio seguente illustra la funzionalità
di centralizzazione delle stringhe di C#. Quando un programma dichiara due o più variabili di stringa identiche, il
compilatore le archivia tutte nello stesso percorso. Chiamando il metodo ReferenceEquals è possibile vedere che
le due stringhe si riferiscono effettivamente allo stesso oggetto in memoria. Per evitare la centralizzazione, usare
il metodo String.Copy. Dopo la copia, le due stringhe hanno posizioni di archiviazione diverse anche se hanno lo
stesso valore. Eseguire l'esempio seguente per verificare che le stringhe a e b sono centralizzate, ovvero
condividono la stessa risorsa di archiviazione. Le stringhe a e c non lo sono.
string a = "The computer ate my source code.";
string b = "The computer ate my source code.";

if (String.ReferenceEquals(a, b))
Console.WriteLine("a and b are interned.");
else
Console.WriteLine("a and b are not interned.");

string c = String.Copy(a);

if (String.ReferenceEquals(a, c))
Console.WriteLine("a and c are interned.");
else
Console.WriteLine("a and c are not interned.");

NOTE
Per verificare l'uguaglianza fra stringhe è opportuno usare metodi che consentono di specificare in modo esplicito il tipo di
confronto da eseguire. In questo modo il codice risulta molto più gestibile e leggibile. Usare gli overload dei metodi delle
classi System.String e System.Array che accettano un parametro di enumerazione StringComparison. È possibile specificare
il tipo di confronto da eseguire. Evitare di usare gli operatori == e != nelle verifiche di uguaglianza. I metodi di istanza
String.CompareTo eseguono sempre un confronto ordinale con distinzione tra maiuscole e minuscole. Sono adatti
soprattutto per ordinare le stringhe in ordine alfabetico.

È possibile inserire una stringa nel pool interno oppure recuperare un riferimento a una stringa inserita nel pool
interno chiamando il metodo String.Intern. Per determinare se una stringa è inserita nel pool interno, chiamare il
metodo String.IsInterned.

Vedi anche
System.Globalization.CultureInfo
System.StringComparer
Stringhe
Confronto di stringhe
Globalizzazione e localizzazione di applicazioni
Come eseguire il cast in modo sicuro usando i criteri
di ricerca e gli operatori is e As
02/11/2020 • 7 minutes to read • Edit Online

Poiché gli oggetti sono polimorfici, è possibile che una variabile di un tipo di classe di base contenga un tipo
derivato. Per accedere ai membri dell'istanza del tipo derivato, è necessario eseguire nuovamente il cast del
valore al tipo derivato. Tuttavia, un cast crea il rischio di generare un InvalidCastException. C# offre istruzioni sui
criteri di ricerca che eseguono un cast in modo condizionale solo quando ha esito positivo. C# offre anche gli
operatori is e as per verificare se il valore è di un determinato tipo.
Nell'esempio seguente viene illustrato come utilizzare l'istruzione dei criteri di ricerca is :
class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

class Program
{
static void Main(string[] args)
{
var g = new Giraffe();
var a = new Animal();
FeedMammals(g);
FeedMammals(a);
// Output:
// Eating.
// Animal is not a Mammal

SuperNova sn = new SuperNova();


TestForMammals(g);
TestForMammals(sn);
// Output:
// I am an animal.
// SuperNova is not a Mammal
}

static void FeedMammals(Animal a)


{
if (a is Mammal m)
{
m.Eat();
}
else
{
// variable 'm' is not in scope here, and can't be used.
Console.WriteLine($"{a.GetType().Name} is not a Mammal");
}
}

static void TestForMammals(object o)


{
// You also can use the as operator and test for null
// before referencing the variable.
var m = o as Mammal;
if (m != null)
{
Console.WriteLine(m.ToString());
}
else
{
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
}
}
}

Il campione precedente dimostra alcune funzionalità della sintassi dei criteri di ricerca. L' if (a is Mammal m)
istruzione combina il test con un'assegnazione di inizializzazione. L'assegnazione si verifica solo quando il test
ha esito positivo. La variabile m è nell'ambito solo nell'istruzione if incorporata a cui è stata assegnata. Non è
possibile accedere a m successivamente nello stesso metodo. Nell'esempio precedente viene inoltre illustrato
come utilizzare l' as operatore per convertire un oggetto in un tipo specificato.
È anche possibile usare la stessa sintassi per verificare se un tipo di valore Nullable ha un valore, come illustrato
nell'esempio seguente:
class Program
{
static void Main(string[] args)
{
int i = 5;
PatternMatchingNullable(i);

int? j = null;
PatternMatchingNullable(j);

double d = 9.78654;
PatternMatchingNullable(d);

PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
}

static void PatternMatchingNullable(System.ValueType val)


{
if (val is int j) // Nullable types are not allowed in patterns
{
Console.WriteLine(j);
}
else if (val is null) // If val is a nullable type with no value, this expression is true
{
Console.WriteLine("val is a nullable type with the null value");
}
else
{
Console.WriteLine("Could not convert " + val.ToString());
}
}

static void PatternMatchingSwitch(System.ValueType val)


{
switch (val)
{
case int number:
Console.WriteLine(number);
break;
case long number:
Console.WriteLine(number);
break;
case decimal number:
Console.WriteLine(number);
break;
case float number:
Console.WriteLine(number);
break;
case double number:
Console.WriteLine(number);
break;
case null:
Console.WriteLine("val is a nullable type with the null value");
break;
default:
Console.WriteLine("Could not convert " + val.ToString());
break;
}
}
}

Il campione precedente dimostra alcune funzionalità dei criteri di ricerca per l'uso con le conversioni. È possibile
sottoporre a test una variabile per il criterio null controllando in modo specifico il valore null . Quando il valore
di runtime della variabile è null , un'istruzione is che controlla il tipo restituisce sempre false . L'istruzione
is dei criteri di ricerca non consente un tipo di valore nullable, quale int? o Nullable<int> , ma è possibile
eseguire il test di qualunque altro tipo di valore. I is modelli dell'esempio precedente non sono limitati ai tipi
di valore Nullable. È anche possibile usare questi modelli per verificare se una variabile di un tipo di riferimento
ha un valore o null .
Nell'esempio precedente viene inoltre illustrato come utilizzare il modello di tipo in un' switch istruzione in cui
la variabile può essere di uno di molti tipi diversi.
Se si desidera verificare se una variabile è un tipo specificato, ma non assegnarla a una nuova variabile, è
possibile utilizzare gli is operatori e as per i tipi di riferimento e i tipi di valore Nullable. Il codice seguente
indica come usare le istruzioni is e as che facevano parte del linguaggio C# prima che fossero introdotti i
criteri di ricerca per verificare se una variabile è di un determinato tipo:

class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

class Program
{
static void Main(string[] args)
{
// Use the is operator to verify the type.
// before performing a cast.
Giraffe g = new Giraffe();
UseIsOperator(g);

// Use the as operator and test for null


// before referencing the variable.
UseAsOperator(g);

// Use the as operator to test


// an incompatible type.
SuperNova sn = new SuperNova();
UseAsOperator(sn);

// Use the as operator with a value type.


// Note the implicit conversion to int? in
// the method body.
int i = 5;
UseAsWithNullable(i);

double d = 9.78654;
UseAsWithNullable(d);
}

static void UseIsOperator(Animal a)


{
if (a is Mammal)
{
Mammal m = (Mammal)a;
m.Eat();
}
}

static void UsePatternMatchingIs(Animal a)


{
if (a is Mammal m)
if (a is Mammal m)
{
m.Eat();
}
}

static void UseAsOperator(object o)


{
Mammal m = o as Mammal;
if (m != null)
{
Console.WriteLine(m.ToString());
}
else
{
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
}
}

static void UseAsWithNullable(System.ValueType val)


{
int? j = val as int?;
if (j != null)
{
Console.WriteLine(j);
}
else
{
Console.WriteLine("Could not convert " + val.ToString());
}
}
}

Come si può vedere dal confronto tra questo codice e il codice dei criteri di ricerca, la sintassi dei criteri di ricerca
offre funzionalità più affidabili tramite la combinazione del test e l'assegnazione in un'unica istruzione. Usare la
sintassi dei criteri di ricerca qualora possibile.
.NET Compiler Platform SDK
28/01/2021 • 13 minutes to read • Edit Online

I compilatori creano un modello dettagliato del codice dell'applicazione durante la convalida della sintassi e
della semantica di tale codice. Questo modello viene poi usato per compilare l'output eseguibile dal codice
sorgente. .NET Compiler Platform SDK consente l'accesso a questo modello. Sempre più spesso, per ottenere
una maggiore produttività ci si affida a funzionalità dell'ambiente di sviluppo integrato (IDE) quali IntelliSense,
refactoring, ridenominazione intelligente, "Trova tutti i riferimenti" e "Vai a definizione". Si fa affidamento agli
strumenti di analisi codice per migliorare la qualità del codice e ai generatori di codice per facilitare la
costruzione dell'applicazione. Questi strumenti, man mano che diventano più intelligenti, devono accedere un
numero sempre maggiore di elementi del modello che viene creato solo dai compilatori durante l'elaborazione
del codice dell'applicazione. Questa è la principale missione delle API Roslyn: aprendo le caselle opache e
consentendo agli strumenti e agli utenti finali di condividere la vasta gamma di compilatori di informazioni sul
codice. Anziché svolgere il ruolo di traduttori da codice sorgente opaco a codice di output basato su oggetti,
tramite Roslyn i compilatori diventano piattaforme, ovvero API utilizzabili per attività correlate al codice negli
strumenti e nelle applicazioni.

Concetti relativi a .NET Compiler Platform SDK


Con .NET Compiler Platform SDK si riduce drasticamente la barriera all'ingresso per la creazione di strumenti e
applicazioni incentrati su codice. Consente di creare numerose opportunità di innovazione in aree quali la
metaprogrammazione, la generazione e la trasformazione del codice, l'uso interattivo dei linguaggi C# e Visual
Basic e l'incorporamento di C# e Visual Basic in linguaggi specifici del dominio.
Il .NET Compiler Platform SDK consente di compilare analizzatori _ e _correzioni del codice*_ che individuano e
correggono gli errori di codifica. Gli analizzatori comprendono la sintassi (struttura del codice) e la semantica
per rilevare le procedure che devono essere corrette. Le correzioni del codice forniscono una o più correzioni
suggerite per risolvere gli errori di codifica rilevati dagli analizzatori o dalla diagnostica del compilatore. In
genere, un analizzatore e le correzioni del codice associate sono riuniti in un unico progetto.
Gli analizzatori e le correzioni del codice usano l'analisi statica per comprendere il codice. Non eseguono il
codice, né offrono altri vantaggi a livello di test. Possono, tuttavia, sottolineare procedure che spesso causano
bug, codice non gestibile o violazione di linee guida standard.
Oltre agli analizzatori e alle correzioni del codice, il .NET Compiler Platform SDK consente anche di compilare
refactoring del codice. Fornisce inoltre un singolo set di API che consentono di esaminare e comprendere una
codebase C# o Visual Basic. Grazie alla possibilità di usare questa singola codebase, si possono scrivere
analizzatori e correzioni del codice più facilmente sfruttando le API di analisi sintattica e semantica fornite da
.NET Compiler Platform SDK. Non dovendo più dipendere dall'attività onerosa di replica dell'analisi eseguita dal
compilatore, è possibile concentrarsi sull'attività di individuazione e correzione degli errori di scrittura del codice
più comuni per il progetto o la libreria.
Un vantaggio più piccolo deriva dal fatto che gli analizzatori e le correzioni del codice hanno dimensioni minori
e usano molta meno memoria quando vengono caricati in Visual Studio, rispetto a quella che sarebbe
necessaria se si scrivesse una codebase personalizzata per analizzare il codice in un progetto. Sfruttando le
stesse classi usate dal compilatore e da Visual Studio, è possibile creare strumenti di analisi statica
personalizzati. Questo significa che il team può usare gli analizzatori e le correzioni del codice senza un impatto
significativo sulle prestazioni dell'IDE.
Esistono tre scenari principali per la scrittura di analizzatori e correzioni del codice:
1. _Enforce gli standard di codifica team *
2. Includere linee guida nei pacchetti di librerie
3. Fornire indicazioni generali

Imporre standard di scrittura del codice a livello di team


Molti team usano standard di scrittura del codice imposti tramite la revisione del codice da parte di altri membri
del team. Gli analizzatori e le correzioni del codice possono rendere questo processo molto più efficiente. Le
revisioni del codice vengono eseguite quando uno sviluppatore condivide il proprio lavoro con altri utenti del
team. Questo significa che ha già dedicato tutto il tempo necessario per completare una nuova funzionalità
prima di ricevere i commenti ed è possibile che segua per settimane abitudini non corrispondenti alle procedure
stabilite dal team.
Gli analizzatori vengono eseguiti durante la scrittura del codice. Lo sviluppatore ottiene così un riscontro
immediato che promuove il rispetto delle linee guida sin dall'inizio, abituandosi a scrivere codice conforme sin
dalle fasi iniziali di creazione del prototipo. Quando la funzionalità è pronta per la revisione da parte di un
collega, tutte le linee guida standard sono state applicate.
I team possono creare analizzatori e correzioni del codice per individuare le abitudini più comuni che violano le
procedure di scrittura del codice consigliate a livello di team. Questi strumenti possono essere installati nel
computer di ogni sviluppatore per imporre gli standard.

TIP
Prima di creare un analizzatore personalizzato, consultare le interfacce predefinite. Per altre informazioni, vedere regole di
stile del codice.

Includere linee guida nei pacchetti di librerie


Sono disponibili numerose librerie per gli sviluppatori .NET in NuGet. Alcune provengono da Microsoft, alcune
da società di terze parti e altre da membri e volontari della community. Queste librerie ottengono tassi di
adozione maggiori e recensioni migliori quando possono essere usate efficacemente dagli sviluppatori.
Oltre a fornire la documentazione, è possibile includere anche analizzatori e correzioni del codice per
l'individuazione e correzione degli usi errati più comuni della libreria. Questa possibilità di usufruire di
correzioni immediate consentirà agli sviluppatori di lavorare con efficacia più rapidamente.
È possibile includere analizzatori e correzioni del codice nel pacchetto della libreria personalizzata in NuGet. In
questo scenario, ogni sviluppatore che installa il pacchetto NuGet installerà anche il pacchetto dell'analizzatore.
Tutti gli sviluppatori che usano la libreria avranno immediatamente a disposizione le linee guida del team sotto
forma di un riscontro immediato in presenza di errori, con suggerimenti per la correzione.

Fornire indicazioni generali


La community di sviluppatori .NET ha individuato, attraverso l'esperienza, modelli che funzionano bene e
modelli che è preferibile evitare. Diversi membri della community hanno creato analizzatori per l'applicazione di
tali modelli consigliati. Nuove esperienze, inoltre, creano sempre spazio per nuove idee.
Gli analizzatori possono essere caricati in Visual Studio Marketplace e scaricati dagli sviluppatori che usano
Visual Studio. In questo modo i neofiti del linguaggio e della piattaforma possono apprendere rapidamente le
procedure consolidate e diventare più velocemente produttivi nel mondo .NET. Con una più ampia diffusione,
tali procedure vengono inoltre adottate dalla community.

Passaggi successivi
.NET Compiler Platform SDK include i modelli a oggetti del linguaggio più recenti per la generazione, l'analisi e il
refactoring del codice. In questa sezione è disponibile una panoramica concettuale di .NET Compiler Platform
SDK. Ulteriori informazioni sono disponibili nelle sezioni guide introduttive, esempi ed esercitazioni.
Altre informazioni sui concetti di .NET Compiler Platform SDK sono disponibili in questi cinque argomenti:
Esplorare il codice con il visualizzatore di sintassi
Informazioni sul modello delle API del compilatore
Utilizzare la sintassi
Utilizzare la semantica
Utilizzare un'area di lavoro
Per iniziare, è necessario installare .NET Compiler Platform SDK :

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .
Informazioni sul modello di .NET Compiler Platform
SDK
28/01/2021 • 6 minutes to read • Edit Online

I compilatori elaborano il codice scritto seguendo regole strutturate spesso diverse dal modo in cui gli essere
umani leggono e interpretano il codice. Una conoscenza di base del modello usato dai compilatori è essenziale
per comprendere le API usate durante la compilazione di strumenti basati su Roslyn.

Aree funzionali della pipeline del compilatore


.NET Compiler Platform SDK espone l'analisi del codice dei compilatori C# e Visual Basic tramite un livello API
che rispecchia una pipeline del compilatore tradizionale.

Ogni fase della pipeline è un componente separato. Innanzitutto, la fase di analisi suddivide in token e analizza il
testo di origine convertendolo in sintassi conforme alla grammatica del linguaggio. In secondo luogo, la fase di
dichiarazione analizza l'origine e i metadati importati per formare simboli denominati. La fase di associazione
abbina poi gli identificatori nel codice ai simboli. La fase di creazione, infine, genera un assembly con tutte le
informazioni raccolte dal compilatore.

In modo corrispondente a ciascuna di queste fasi, .NET Compiler Platform SDK espone un modello a oggetti che
consente l'accesso alle informazioni in tale fase. La fase di analisi espone un albero della sintassi, la fase di
dichiarazione espone una tabella dei simboli gerarchici, la fase di associazione espone il risultato dell'analisi
semantica del compilatore e la fase di creazione è un'API che produce codici di byte IL.
Ogni compilatore combina questi componenti come singola unità completa.
Queste API sono le stesse usate da Visual Studio. Ad esempio, la struttura del codice e le funzionalità di
formattazione usano gli alberi della sintassi, le funzionalità di Visualizzatore oggetti e di navigazione usano la
tabella dei simboli, i refactoring e Vai a definizione usano il modello semantico e modifica e continuazione
usa tutti questi elementi, inclusa l'API Emit.

Livelli delle API


.NET Compiler SDK è costituito da diversi livelli di API: API del compilatore, API di diagnostica, API di scripting e
API per le aree di lavoro.
API del compilatore
Il livello del compilatore contiene i modelli a oggetti che corrispondono alle informazioni esposte in ogni fase
della pipeline del compilatore, sia semantiche che sintattiche. Il livello del compilatore contiene anche uno
snapshot non modificabile di una singola chiamata di un compilatore, inclusi riferimenti ad assembly, opzioni
del compilatore e file del codice sorgente. Esistono due API distinte che rappresentano il linguaggio C# e il
linguaggio Visual Basic. Le due API sono simili in forma, ma sono personalizzate per la massima fedeltà a ogni
singolo linguaggio. Questo livello non ha dipendenze da componenti di Visual Studio.
API di diagnostica
Nell'ambito dell'analisi, il compilatore può produrre un set di dati diagnostici che coprono tutti gli elementi, da
errori di sintassi, semantica e assegnazione definitiva a diversi avvisi e diagnostica informativa. Il livello dell'API
del compilatore espone i dati diagnostici tramite un'API estensibile che consente di integrare analizzatori definiti
dall'utente nel processo di compilazione. Consente la produzione di diagnostica definita dall'utente, ad esempio
quelle prodotte da strumenti come StyleCop, oltre alla diagnostica definita dal compilatore. La produzione di
diagnostica in questo modo offre il vantaggio di integrarsi naturalmente con strumenti come MSBuild e Visual
Studio, che dipendono dalla diagnostica per esperienze quali l'arresto di una compilazione in base ai criteri e la
visualizzazione di controllo ortografia durante in tempo reale nell'editor e il suggerimento di correzioni del
codice.
API di scripting
Le API di hosting e scripting fanno parte del livello del compilatore. È possibile usarle per eseguire frammenti di
codice e accumulare un contesto di esecuzione di runtime. Il ciclo Read–Eval–Print (REPL) interattivo di C# usa
queste API. Il ciclo REPL consente di usare C# come linguaggio di scripting, eseguendo il codice in modo
interattivo mentre lo si scrive.
API delle aree di lavoro
Il livello delle aree di lavoro contiene l'API Workspace, ovvero il punto di partenza per eseguire l'analisi del
codice e il refactoring su intere soluzioni. Consente di organizzare tutte le informazioni sui progetti in una
soluzione in un unico modello a oggetti, offrendo accesso diretto ai modelli a oggetti del livello del compilatore
senza la necessità di analizzare i file, configurare le opzioni o gestire le dipendenze da progetto a progetto.
Inoltre, il livello delle aree di lavoro espone un set di API usate durante l'implementazione di strumenti di analisi
del codice e refactoring che operano all'interno di un ambiente host come l'IDE di Visual Studio. Sono esempi le
API Trova tutti i riferimenti, Formattazione e Generazione codice.
Questo livello non ha dipendenze da componenti di Visual Studio.
Utilizzare la sintassi
28/01/2021 • 16 minutes to read • Edit Online

L' albero della sintassi è una struttura di dati non modificabile fondamentale esposta dalle API del compilatore.
Questi alberi rappresentano la struttura lessicale e sintattica del codice sorgente e svolgono due funzioni
importanti:
Per consentire agli strumenti, ad esempio un IDE, componenti aggiuntivi, strumenti di analisi del codice e
refactoring, di visualizzare ed elaborare la struttura sintattica del codice sorgente nel progetto di un utente.
Per abilitare strumenti, ad esempio refactoring e IDE, per creare, modificare e ridisporre il codice sorgente in
modo naturale senza dover usare modifiche di testo diretto. Mediante la creazione e modifica degli alberi, gli
strumenti possono facilmente creare e modificare il codice sorgente.

Alberi della sintassi


Gli alberi della sintassi sono la struttura primaria usata per compilazione, analisi codice, binding, refactoring,
funzionalità dell'IDE e generazione di codice. Nessuna parte del codice sorgente può essere compresa senza
prima essere identificata e categorizzata in uno dei numerosi elementi di linguaggio strutturali noti.
Gli alberi della sintassi hanno tre attributi chiave:
Mantengono tutte le informazioni di origine con la massima fedeltà. Per fedeltà completa si intende che
l'albero della sintassi contiene tutte le informazioni contenute nel testo di origine, ogni costrutto
grammaticale, ogni token lessicale e tutti gli altri elementi tra, inclusi spazi vuoti, commenti e direttive per il
preprocessore. Ad esempio, ogni valore letterale nell'origine viene rappresentato esattamente nel modo in
cui è stato digitato. Gli alberi della sintassi acquisiscono anche errori nel codice sorgente quando il
programma è incompleto o non corretto, rappresentando i token ignorati o mancanti.
Possono produrre il testo esatto da cui sono stati analizzati. Da qualsiasi nodo di sintassi è possibile ottenere
la rappresentazione testuale del sottoalbero radice in quel nodo. Questa possibilità significa che è possibile
usare gli alberi della sintassi come modo per costruire e modificare il testo di origine. Creando una struttura
ad albero, in modo implicito, creato il testo equivalente e rendendo un nuovo albero fuori dalle modifiche
apportate a un albero esistente, il testo è stato modificato.
Sono non modificabili e thread-safe. Una volta ottenuto un albero, è uno snapshot dello stato corrente del
codice e non viene mai modificato. In questo modo, più utenti possono interagire con lo stesso albero della
sintassi contemporaneamente in thread diversi, senza causare blocchi o duplicazioni. Dato che gli alberi non
sono modificabili e non è possibile apportare modifiche direttamente a un albero, i metodi factory sono utili
per creare e modificare gli alberi della sintassi mediante la creazione di snapshot aggiuntivi dell'albero.
L'efficienza degli alberi sta nel modo in cui vengono riutilizzati i nodi sottostanti, che consente di ricompilare
una nuova versione velocemente e con una quantità limitata di memoria aggiuntiva.
Un albero della sintassi è letteralmente una struttura dei dati ad albero, in cui gli elementi strutturali non
terminali fungono da padre per altri elementi. Ogni albero della sintassi è composto da nodi, token ed elementi
semplici.

Nodi di sintassi
I nodi di sintassi sono tra gli elementi principali degli alberi della sintassi. Questi nodi rappresentano costrutti
sintattici, ad esempio dichiarazioni, istruzioni, clausole ed espressioni. Ogni categoria di nodi di sintassi è
rappresentata da una classe separata derivata da Microsoft.CodeAnalysis.SyntaxNode. Il set di classi dei nodi
non è estendibile.
Tutti i nodi di sintassi sono i nodi non terminali nell'albero della sintassi, ovvero hanno sempre altri nodi e token
come elementi figlio. In quanto elemento figlio di un altro nodo, ogni nodo ha un nodo padre accessibile tramite
la proprietà SyntaxNode.Parent. Dato che i nodi e gli alberi non sono modificabili, l'elemento padre di un nodo
non cambia mai. La radice dell'albero ha un padre null.
Ogni nodo ha un metodo SyntaxNode.ChildNodes(), che restituisce un elenco di nodi figlio in ordine sequenziale
in base alla posizione nel testo di origine. L'elenco non contiene token. Ogni nodo dispone anche di metodi per
esaminare i discendenti, ad esempio DescendantNodes , DescendantTokens o, DescendantTrivia che
rappresentano un elenco di tutti i nodi, i token o le banalità presenti nel sottoalbero radice da tale nodo.
Inoltre, ogni sottoclasse di nodo della sintassi espone gli stessi elementi figlio tramite proprietà fortemente
tipizzate. Ad esempio, una classe di nodo BinaryExpressionSyntax include tre proprietà aggiuntive specifiche per
gli operatori binari: Left, OperatorToken e Right. Il tipo di Left e Right è ExpressionSyntax e il tipo di
OperatorToken è SyntaxToken.
Alcuni nodi di sintassi hanno elementi figlio facoltativi. Ad esempio, per un elemento IfStatementSyntax è
disponibile un elemento ElseClauseSyntax facoltativo. Se l'elemento figlio non è presente, la proprietà restituisce
null.

Token di sintassi
I token di sintassi sono i terminali della grammatica del linguaggio, che rappresentano i frammenti sintattici più
piccoli del codice. Questi elementi non fungono mai da padre per altri nodi o token. I token di sintassi sono
costituiti da parole chiave, identificatori, valori letterali e punteggiatura.
Per motivi di efficienza, il tipo SyntaxToken è un tipo valore CLR. Pertanto, a differenza dei nodi di sintassi, è
presente una sola struttura per tutti i tipi di token con una combinazione di proprietà che hanno un significato in
base al tipo di token rappresentato.
Ad esempio, un token letterale integer rappresenta un valore numerico. Oltre al testo di origine non elaborato
rappresentato dal token, il token letterale ha una proprietà Value che indica l'esatto valore integer decodificato.
Questa proprietà è tipizzata come Object perché può essere uno di molti tipi primitivi.
La proprietà ValueText offre le stesse informazioni della proprietà Value, tuttavia questa proprietà è sempre
tipizzata come String. Un identificatore nel testo di origine C# può includere caratteri di escape Unicode, ma la
sintassi della sequenza di escape stessa non viene considerata parte del nome dell'identificatore. In tal caso,
anche se il testo non elaborato a cui fa riferimento il token include la sequenza di escape, la proprietà ValueText
non la include e include invece i caratteri Unicode identificati dal carattere di escape. Ad esempio, se il testo di
origine contiene un identificatore scritto come \u03C0 , la proprietà ValueText per questo token restituirà π .

Elementi semplici della sintassi


Gli elementi semplici della sintassi rappresentano le parti del testo di origine in gran parte poco significative per
la normale comprensione del codice, ad esempio spazi vuoti, commenti e direttive del preprocessore. Come i
token di sintassi, gli elementi semplici sono tipi valore. Il singolo tipo Microsoft.CodeAnalysis.SyntaxTrivia viene
usato per descrivere tutti i tipi di elementi semplici.
Dato che gli elementi semplici non fanno parte della sintassi del linguaggio normale e possono trovarsi
ovunque tra due token, non vengono inclusi nell'albero della sintassi come elemento figlio di un nodo. Tuttavia,
dato che sono importanti quando si implementa una funzionalità come il refactoring e per mantenere la
massima fedeltà con il testo di origine, esistono come parte dell'albero della sintassi.
È possibile accedere a Trivia controllando le raccolte o di un token SyntaxToken.LeadingTrivia
SyntaxToken.TrailingTrivia . Quando viene analizzato il testo di origine, le sequenze di elementi semplici vengono
associate ai token. In generale, un token possiede tutti gli elementi semplici dopo di esso sulla stessa riga fino al
token successivo. Tutti gli elementi semplici dopo tale riga vengono associati al token seguente. Il primo token
nel file di origine ottiene tutti gli elementi semplici iniziali e l'ultima sequenza degli elementi semplici nel file
viene associata al token di fine del file, che in caso contrario ha larghezza zero.
A differenza dei nodi e dei token di sintassi, per gli elementi semplici della sintassi non vi sono elementi padre.
Tuttavia, dato che fanno parte dell'albero e ognuno è associato a un singolo token, è possibile accedere al token
associato tramite la proprietà SyntaxTrivia.Token.

Intervalli
Ogni nodo, token o elemento semplice conosce la propria posizione all'interno del testo di origine e il numero di
caratteri di cui è costituito. Una posizione del testo è rappresentata come intero a 32 bit, ovvero un indice char
in base zero. Un oggetto TextSpan rappresenta la posizione di inizio e il conteggio di caratteri, entrambi
rappresentati come valori integer. Se TextSpan ha lunghezza zero, fa riferimento a una posizione tra due
caratteri.
Ogni nodo ha due proprietà TextSpan: Span e FullSpan.
La Span proprietà è l'intervallo di testo dall'inizio del primo token nel sottoalbero del nodo alla fine dell'ultimo
token. Questo intervallo non include alcun elemento semplice iniziale o finale.
La FullSpan proprietà è l'intervallo di testo che include l'intervallo normale del nodo, più l'intervallo di eventuali
Trivia iniziali o finali.
Ad esempio:

if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}

Il nodo dell'istruzione all'interno del blocco ha un intervallo indicato da singole barre verticali (|) e include i
caratteri throw new Exception("Not right."); . L'intervallo completo è indicato dalla doppia barra verticale (||) e
include gli stessi caratteri dell'intervallo, oltre ai caratteri associati agli elementi semplici iniziali e finali.

Tipi
Ogni nodo, token o elemento semplice ha una proprietà SyntaxNode.RawKind di tipo System.Int32, che
identifica l'elemento di sintassi esatto rappresentato. È possibile eseguire il cast di questo valore in
un'enumerazione specifica del linguaggio. Ogni linguaggio, C# o Visual Basic, ha una sola SyntaxKind
enumerazione ( Microsoft.CodeAnalysis.CSharp.SyntaxKind e Microsoft.CodeAnalysis.VisualBasic.SyntaxKind
rispettivamente) che elenca tutti i possibili nodi, token ed elementi Trivia nella grammatica. Questa conversione
può essere eseguita automaticamente mediante l'accesso ai metodi di estensione CSharpExtensions.Kind o
VisualBasicExtensions.Kind.
La proprietà RawKind consente di risolvere facilmente eventuali ambiguità per i tipi di nodi della sintassi che
condividono la stessa classe di nodo. Per i token e gli elementi semplici, questa proprietà è l'unico modo per
distinguere un tipo di elemento da un alto.
Ad esempio, una singola classe BinaryExpressionSyntax include gli elementi figlio Left, OperatorToken e Right.
La proprietà Kind consente di stabilire se si tratta di un nodo di sintassi di tipo AddExpression,
SubtractExpression o MultiplyExpression.
TIP
È consigliabile controllare i tipi usando i IsKind metodi di estensione (per C#) o IsKind (per VB).

Errors
Anche quando il testo di origine contiene errori di sintassi, viene esposto un albero della sintassi completo
riconducibile tramite round-trip all'origine. Quando il parser rileva codice non conforme alla sintassi definita del
linguaggio, usa una delle due tecniche per creare un albero della sintassi:
Se il parser prevede un tipo specifico di token ma non lo trova, può inserire un token mancante
nell'albero della sintassi nella posizione in cui era previsto il token. Un token mancante rappresenta il
token effettivo previsto, ma con intervallo vuoto e la relativa proprietà SyntaxNode.IsMissing restituisce
true .

Il parser può ignorare i token finché non ne trova uno che può continuare l'analisi. In questo caso, i token
ignorati vengono associati come nodo di elementi semplici con il tipo SkippedTokensTrivia.
Utilizzare la semantica
28/01/2021 • 6 minutes to read • Edit Online

Gli alberi della sintassi rappresentano la struttura lessicale e sintattica del codice sorgente. Anche se queste
informazioni da sole sono sufficienti per descrivere tutte le dichiarazioni e la logica nell'origine, non bastano per
identificare gli elementi a cui viene fatto riferimento. Un nome può rappresentare:
Un tipo
Un campo
Un metodo
Una variabile locale
Anche se ognuno di questi elementi è univoco, per determinare l'elemento a cui un identificatore fa
effettivamente riferimento è spesso necessaria una conoscenza approfondita delle regole del linguaggio.
Sono presenti elementi di programma rappresentati nel codice sorgente e i programmi possono anche fare
riferimento a librerie compilate in precedenza, incluse in file di assembly. Anche se per gli assembly non è
disponibile codice sorgente, e pertanto alcun nodo o albero di sintassi, i programmi possono comunque fare
riferimento a elementi contenuti in assembly.
Per queste attività, è necessario il modello semantico .
Oltre a un modello sintattico del codice sorgente, un modello semantico incapsula le regole del linguaggio,
offrendo un modo semplice per abbinare correttamente gli identificatori all'elemento di programma corretto a
cui si fa riferimento.

Compilazione
Una compilazione è una rappresentazione di tutti gli elementi necessari per compilare un programma C# o
Visual Basic, che include tutti i riferimenti ad assembly, le opzioni del compilatore e i file di origine.
Dato che tutte queste informazioni sono raccolte in un'unica posizione, gli elementi contenuti nel codice
sorgente possono essere descritti in maggiore dettaglio. La compilazione rappresenta come simbolo ogni tipo,
membro o variabile dichiarati. La compilazione contiene svariati metodi che consentono di trovare e associare i
simboli che sono stati dichiarati nel codice sorgente o importati come metadati da un assembly.
Analogamente agli alberi della sintassi, le compilazioni non sono modificabili. Dopo aver creato una
compilazione, non può essere modificata dall'utente o da altri utenti con cui è condivisa. Tuttavia, è possibile
creare una nuova compilazione da una compilazione esistente, specificano una modifica con questa operazione.
Ad esempio, è possibile creare una compilazione che è uguale in tutto e per tutto a una compilazione esistente,
tranne per l'aggiunta di un file di origine o un riferimento ad assembly aggiuntivo.

Simboli
Un simbolo rappresenta un elemento distinto dichiarato dal codice sorgente o importato da un assembly come
metadati. Ogni spazio dei nomi, tipo, metodo, proprietà, campo, evento, parametro o variabile locale è
rappresentato da un simbolo.
Un'ampia gamma di metodi e proprietà per il tipo Compilation sono utili per trovare i simboli. Ad esempio, è
possibile trovare un simbolo per un tipo dichiarato in base al nome dei metadati comuni. È anche possibile
accedere all'intera tabella dei simboli come albero dei simboli con lo spazio dei nomi globale come radice.
I simboli contengono anche informazioni aggiuntive che il compilatore determina dall'origine o dai metadati,
come altri simboli di riferimento. Ogni tipo di simbolo è rappresentato da un'interfaccia separata derivata da
ISymbol, ognuna con metodi e proprietà propri che riportano in dettaglio le informazioni raccolte al
compilatore. Molte di queste proprietà fanno riferimento direttamente ad altri simboli. Ad esempio, la
IMethodSymbol.ReturnType proprietà indica il simbolo di tipo effettivo restituito dal metodo.
I simboli presentano una rappresentazione comune di spazi dei nomi, tipi e membri, tra il codice sorgente e i
metadati. Ad esempio, un metodo dichiarato nel codice sorgente e un metodo importato dai metadati sono
rappresentati entrambi da un IMethodSymbol con le stesse proprietà.
I simboli sono concettualmente simili al sistema di tipi CLR, come rappresentato dall'API System.Reflection, ma
sono più completi perché non si limitano alla modellazione dei tipi. Gli spazi dei nomi, le variabili locali e le
etichette sono tutti simboli. Inoltre, i simboli sono una rappresentazione di concetti del linguaggio e non di
concetti CLR. Esistono molte sovrapposizioni, ma anche molte differenze significative. Ad esempio, un metodo
iteratore in C# o Visual Basic è un unico simbolo. Tuttavia, quando il metodo iteratore viene convertito in
metadati CLR, corrisponde a un tipo e più metodi.

Modello semantico
Un modello semantico rappresenta tutte le informazioni semantiche per un singolo file di origine. È possibile
usarlo per individuare quanto segue:
I simboli a cui si fa riferimento in una posizione specifica nell'origine.
Il tipo risultante di qualsiasi espressione.
Tutti i dati diagnostici, ovvero errori e avvisi.
Il flusso delle variabili in ingresso e in uscita dalle aree di origine.
Risposte a domande più speculative.
Utilizzare un'area di lavoro
28/01/2021 • 5 minutes to read • Edit Online

Il livello delle aree di lavoro rappresenta il punto di partenza per eseguire l'analisi del codice e il refactoring su
intere soluzioni. All'interno di questo livello, l'API Workspace consente di organizzare tutte le informazioni sui
progetti in una soluzione in unico modello a oggetti, offrendo accesso diretto ai modelli a oggetti del livello del
compilatore, come il testo di origine, gli alberi di sintassi, i modelli semantici e le compilazioni, senza la necessità
di analizzare file, configurare opzioni o gestire le dipendenze all'interno dei progetti.
Gli ambienti host, ad esempio un ambiente IDE, forniscono un'area di lavoro corrispondente alla soluzione
aperta. È anche possibile usare questo modello al di fuori di un ambiente IDE caricando semplicemente un file di
soluzione.

Area di lavoro
Un'area di lavoro è una rappresentazione attiva della soluzione come una raccolta di progetti, ognuno con una
raccolta di documenti. Un'area di lavoro è in genere associata a un ambiente host che cambia continuamente
durante la digitazione o la modifica delle proprietà.
Workspace consente l'accesso al modello corrente della soluzione. Quando si verifica una modifica
nell'ambiente host, l'area di lavoro genera eventi corrispondenti e la proprietà Workspace.CurrentSolution viene
aggiornata. Ad esempio, quando l'utente digita in un editor di testo corrispondente a uno dei documenti di
origine, l'area di lavoro usa un evento per segnalare che il modello generale della soluzione è stato modificato e
quale documento è stato modificato. È quindi possibile rispondere a queste modifiche analizzando il nuovo
modello per verificarne la correttezza, evidenziando le aree significative o suggerendo una modifica del codice.
È anche possibile creare aree di lavoro autonome disconnesse dall'ambiente host o usate in un'applicazione che
non dispone di alcun ambiente host.

Soluzioni, progetti e documenti


Anche se un'area di lavoro può cambiare ogni volta che viene premuto un tasto, è possibile utilizzare il modello
della soluzione in isolamento.
Una soluzione è un modello non modificabile dei progetti e dei documenti. Ciò significa che il modello può
essere condiviso senza causare blocchi o duplicati. Dopo aver ottenuto un'istanza della soluzione dalla proprietà
Workspace.CurrentSolution, tale istanza non verrà mai modificata. Tuttavia, come nel caso degli alberi della
sintassi e delle compilazioni, è possibile modificare le soluzioni creando nuove istanze in base alle soluzioni
esistenti e a modifiche specifiche. Per fare in modo che l'area di lavoro rispecchi le modifiche, è necessario
applicare in modo esplicito la soluzione modificata nell'area di lavoro.
Un progetto fa parte del modello di soluzione globale non modificabile e rappresenta tutti i documenti di codice
sorgente, le opzioni di analisi e compilazione e sia i riferimenti ad assembly che i riferimenti da progetto a
progetto. Da un progetto è possibile accedere alla compilazione corrispondente senza la necessità di
determinare le dipendenze del progetto o di analizzare i file di origine.
Anche un documento è una parte del modello di soluzione globale non modificabile e rappresenta un singolo
file di origine da cui si può accedere al testo del file, all'albero della sintassi e al modello semantico.
Il diagramma seguente è una rappresentazione delle correlazioni tra l'area di lavoro, l'ambiente host, gli
strumenti e le modalità di modifica.
Riepilogo
Roslyn espone un set di API del compilatore e API delle aree di lavoro che forniscono informazioni dettagliate
sul codice sorgente, in modo del tutto fedele con i linguaggi C# e Visual Basic. Con .NET Compiler Platform SDK
si riduce drasticamente la barriera all'ingresso per la creazione di strumenti e applicazioni incentrati su codice.
Consente di creare numerose opportunità di innovazione in aree quali la metaprogrammazione, la generazione
e la trasformazione del codice, l'uso interattivo dei linguaggi C# e Visual Basic e l'incorporamento di C# e Visual
Basic in linguaggi specifici del dominio.
Esplorare il codice con il visualizzatore di sintassi
Roslyn in Visual Studio
02/11/2020 • 18 minutes to read • Edit Online

Questo articolo offre una panoramica sul visualizzatore di sintassi incluso in .NET Compiler Platform ("Roslyn")
SDK. Il visualizzatore di sintassi è una finestra degli strumenti che consente di controllare ed esaminare gli alberi
della sintassi. È uno strumento fondamentale per esaminare i modelli del codice che si vuole analizzare. Può
anche essere usato per eseguire il debug durante lo sviluppo delle applicazioni che usano .NET Compiler
Platform ("Roslyn") SDK. Aprire questo strumento quando si creano i primi analizzatori. Il visualizzatore consente
di esaminare i modelli usati dalle API. È anche possibile usare strumenti quali SharpLab oppure LINQPad per
controllare il codice e analizzare gli alberi della sintassi.

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .
Per acquisire familiarità con i concetti usati in .NET Compiler Platform SDK, leggere l'articolo relativo alle
informazioni generali. Contiene un'introduzione ad alberi della sintassi, nodi, token ed elementi semplici.

Visualizzatore di sintassi
Il Syntax Visualizer consente di controllare l'albero della sintassi per il file di codice C# o Visual Basic nella
finestra dell'editor attiva corrente all'interno dell'IDE di Visual Studio. Il visualizzatore può essere avviato
facendo clic su Visualizza > altre > Syntax Visualizer di Windows. È anche possibile usare la barra degli
strumenti Avvio veloce nell'angolo in alto a destra. Digitare "syntax" per visualizzare il comando che apre il
visualizzatore di sintassi .
Questo comando apre il visualizzatore di sintassi sotto forma di finestra degli strumenti mobile. Se non si apre
una finestra dell'editore del codice, l'area visualizzata è vuota, come illustrato nella figura seguente.

Ancorare questa finestra degli strumenti in una posizione appropriata all'interno di Visual Studio, ad esempio
sul lato sinistro. Il visualizzatore offre le informazioni sul file di codice corrente.
Creare un nuovo progetto usando il comando file > nuovo progetto . È possibile creare un progetto Visual
Basic o C#. Quando Visual Studio apre il file di codice principale di questo progetto, il visualizzatore ne visualizza
l'albero della sintassi. È possibile aprire qualsiasi file C#/Visual Basic esistente in questa istanza di Visual Studio e
il Visualizzatore Visualizza l'albero della sintassi del file. Se in Visual Studio sono aperti più file di codice, viene
visualizzato l'albero della sintassi del file di codice attualmente attivo, vale a dire il file di codice con lo stato
attivo.
C#
Visual Basic
Come illustrato nelle immagini precedenti, nella finestra degli strumenti del visualizzatore vengono visualizzati
l'albero della sintassi in alto e una griglia delle proprietà in basso. Questa griglia contiene le proprietà
dell'elemento attualmente selezionato nell'albero, comprese le proprietà Type e Kind (SyntaxKind) .NET
dell'elemento.
Gli alberi della sintassi includono tre tipi di elementi: nodi, token ed elementi semplici. Per altre informazioni su
questi tipi di elementi, leggere l'articolo Usare la sintassi. Gli elementi di ogni tipo sono rappresentati da un
colore diverso. Fare clic sul pulsante "Legend" (Leggenda) per informazioni sui colori usati.
Ogni elemento dell'albero indica anche il relativo elemento span . L'elemento span rappresenta gli indici
(posizione iniziale e finale) di tale nodo nel file di testo. Nell'esempio precedente di C# il token "UsingKeyword
[0..5)" selezionato ha un elemento Span di cinque caratteri, rappresentato dalla notazione [0..5)). La notazione "
[..)" indica che l'indice iniziale è parte dello span, mentre non lo è l'indice finale.
Esistono due modi per esplorare l'albero:
Espandere o fare clic sugli elementi dell'albero. Il visualizzatore seleziona automaticamente il testo
corrispondente allo span dell'elemento nell'editor di codice.
Selezionare o fare clic sul testo nell'editor di codice. Nel Visual Basic esempio precedente, se si seleziona la
riga contenente "modulo Module1" nell'editor di codice, il visualizzatore passa automaticamente al nodo
ModuleStatement corrispondente nell'albero.
Il visualizzatore evidenzia l'elemento nell'albero il cui span corrisponde maggiormente allo span del testo
selezionato nell'editor.
Il visualizzatore aggiorna l'albero per visualizzare le modifiche apportate nel file di codice attivo. Aggiungere
una chiamata a Console.WriteLine() all'interno di Main() . Durante la digitazione, il visualizzatore aggiorna
l'albero.
Sospendere la digitazione dopo aver digitato Console. . L'albero include alcuni elementi di colore rosa. Si tratta
di errori nel codice tipizzato, definiti anche "Diagnostica", associati a nodi, token ed elementi semplici nell'albero
della sintassi. Il visualizzatore visualizza gli elementi con errori associati evidenziandone lo sfondo di colore rosa.
È possibile esaminare gli errori associati a tutti gli elementi evidenziati di rosa, passando il puntatore
sull'elemento. Il visualizzatore visualizza solo gli errori sintattici, vale a dire gli errori correlati alla sintassi del
codice tipizzato. Non vengono visualizzati gli errori di tipo semantico.
Grafici di sintassi
Fare clic con il pulsante destro su un elemento qualsiasi dell'albero e fare clic su View Directed Syntax Graph
(Visualizza grafico di sintassi diretto).
C#
Visual Basic
Sarà visualizzata una rappresentazione grafica del sottoalbero corrispondente all'elemento selezionato. Provare
questi passaggi per il nodo MethodDeclaration corrispondente al metodo Main() nell'esempio di C#. Il
grafico di sintassi visualizzato sarà simile al seguente:

Nel Visualizzatore Graph della sintassi è possibile visualizzare una legenda per lo schema di colorazione. È anche
possibile passare il puntatore del mouse sui singoli elementi nel grafico di sintassi per visualizzare le proprietà
che corrispondono a tale elemento.
È possibile visualizzare ripetutamente grafici di sintassi per elementi diversi dell'albero. I grafici saranno sempre
visualizzati nella stessa finestra all'interno di Visual Studio. Tale finestra può essere ancorata in una posizione
appropriata all'interno di Visual Studio in modo che non sia necessario passare da una scheda all'altra per
visualizzare un nuovo grafico di sintassi. È spesso consigliabile posizionarla in basso, sotto le finestre dell'editor
del codice.
Di seguito è raffigurato il layout di ancoraggio da usare con la finestra degli strumenti del visualizzatore e la
finestra del grafico di sintassi:

È anche possibile inserire la finestra del grafico di sintassi in un secondo monitor, in caso di configurazione a
doppio monitor.

Analisi della semantica


Il visualizzatore di sintassi consente di eseguire un'ispezione elementare di simboli e informazioni semantiche.
Digitare double x = 1 + 1; all'interno di Main () nell'esempio di C#. A questo punto, selezionare l'espressione
1 + 1 nella finestra dell'editor del codice. Il visualizzatore evidenzierà il nodo AddExpression nel
visualizzatore. Fare clic con il pulsante destro sul nodo AddExpression e fare clic su View Symbol (if any)
(Visualizza simbolo (se esiste)). Si noti che la maggior parte delle voci di menu usa il qualificatore "se esiste". Il
visualizzatore di sintassi controlla le proprietà di un nodo, incluse le proprietà che possono non essere presenti
per tutti i nodi.
La griglia delle proprietà nel visualizzatore viene aggiornata come illustrato nella figura seguente. Il simbolo
dell'espressione è un oggetto SynthesizedIntrinsicOperatorSymbol con Kind = Method .

Provare a selezionare View TypeSymbol (if any) (Visualizza TypeSymbol (se esiste)) per lo stesso nodo
AddExpression . La griglia delle proprietà nel visualizzatore viene aggiornata come illustrato nella figura
seguente e indica che il tipo dell'espressione selezionata è Int32 .
Provare a selezionare View Conver ted TypeSymbol (if any) (Visualizza TypeSymbol convertito (se esiste))
per lo stesso nodo AddExpression . La griglia delle proprietà viene aggiornata e indica che, sebbene il tipo
dell'espressione sia Int32 , il tipo convertito dell'espressione è Double , come illustrato nella figura seguente.
Questo nodo include le informazioni sul simbolo del tipo convertito perché l'espressione Int32 si trova in un
contesto in cui deve essere convertita in Double . Questa conversione soddisfa il tipo Double specificato per la
variabile x a sinistra dell'operatore di assegnazione.

Infine, provare a selezionare View Constant Value (if any) (Visualizza valore costante (se esiste) per lo stesso
nodo AddExpression . La griglia delle proprietà indica che il valore dell'espressione è una costante in fase di
compilazione con valore 2 .
L'esempio precedente può essere replicato anche in Visual Basic. Digitare Dim x As Double = 1 + 1 un file di
Visual Basic. Selezionare l'espressione 1 + 1 nella finestra dell'editor del codice. Il visualizzatore evidenzierà il
nodo AddExpression corrispondente nel visualizzatore. Ripetere i passaggi precedenti per il nodo
AddExpression . I risultati saranno identici.
Esaminare altro codice in Visual Basic. Aggiornare il file di Visual Basic principale con il codice seguente:

Imports C = System.Console

Module Program
Sub Main(args As String())
C.WriteLine()
End Sub
End Module

Questo codice introduce un alias denominato C che esegue il mapping al tipo System.Console all'inizio del file
e usa l'alias all'interno di Main() . Selezionare l'uso di questo alias ( C in C.WriteLine() ) all'interno del metodo
Main() . Il visualizzatore seleziona il nodo IdentifierName corrispondente nel visualizzatore. Fare clic con il
pulsante destro del mouse su questo nodo e selezionare View Symbol (if any) (Visualizza simbolo (se esiste)).
La griglia delle proprietà indica che questo identificatore è associato al tipo System.Console come illustrato nella
figura seguente:
Provare a selezionare View AliasSymbol (if any)) (Visualizza AliasSymbol (se esiste)) per lo stesso nodo
IdentifierName . La griglia delle proprietà indica che l'identificatore è un alias denominato C e associato alla
destinazione System.Console . In altre parole, la griglia delle proprietà contiene informazioni relative all'oggetto
AliasSymbol che corrisponde all'identificatore C .

Controllare il simbolo che corrisponde a qualsiasi tipo, metodo, proprietà dichiarati. Selezionare il nodo
corrispondente nel visualizzatore e fare clic su View Symbol (if any) (Visualizza simbolo (se esiste)).
Selezionare il metodo Sub Main() , incluso il corpo del metodo. Fare clic su View Symbol (if any) (Visualizza
simbolo (se esiste)) per il nodo SubBlock corrispondente nel visualizzatore. La griglia delle proprietà indica che
il nome di MethodSymbol per il nodo SubBlock è Main con tipo restituito Void .
Gli esempi precedenti Visual Basic possono essere facilmente replicati in C#. Digitare using C = System.Console;
invece di Imports C = System.Console per l'alias. I passaggi precedenti in C# restituiscono gli stessi risultati nella
finestra del visualizzatore.
È possibile eseguire l'ispezione semantica solo sui nodi. Non è disponibile su token o elementi semplici. Non
tutti i nodi contengono informazioni semantiche interessanti da controllare. Quando un nodo non contiene
informazioni semantiche interessanti, facendo clic su View * Symbol (if any) (Visualizza simbolo se esistente)
apparirà una griglia delle proprietà vuota.
Altre informazioni sulle API per l'esecuzione dell'analisi semantica sono disponibile nel documento di anteprima
Usare la semantica.

Chiusura del visualizzatore di sintassi


È possibile chiudere la finestra del visualizzatore se non si sta usando lo strumento per esaminare il codice
sorgente. Il Visualizzatore di sintassi aggiorna la visualizzazione durante l'esplorazione del codice, la modifica e
la modifica dell'origine. Lo strumento può rivelarsi di disturbo se non è usato.
Introduzione all'analisi della sintassi
02/11/2020 • 25 minutes to read • Edit Online

In questa esercitazione verrà esaminata l'API Syntax . L'API Syntax consente l'accesso alle strutture di dati che
descrivono un programma C# o Visual Basic. Queste strutture di dati includono dettagli sufficienti per
rappresentare completamente un programma di qualsiasi dimensione. Queste strutture possono descrivere
programmi completi che vengono compilati ed eseguiti correttamente. Possono anche descrivere programmi
incompleti, durante la scrittura, nell'editor.
Per rendere possibile questa espressione elaborata, le strutture di dati e le API che costituiscono l'API Syntax
sono necessariamente complesse. Per iniziare, verrà esaminato l'aspetto della struttura dei dati per il tipico
programma "Hello World":

using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Esaminando il testo del programma precedente si riconoscono elementi noti. L'intero testo rappresenta un
singolo file di origine, ovvero un'unità di compilazione . Le prime tre righe di tale file di origine sono direttive
using . L'origine rimanente è contenuta in una dichiarazione dello spazio dei nomi . La dichiarazione dello
spazio dei nomi contiene una dichiarazione di classe figlio. La dichiarazione di classe contiene una
dichiarazione di metodo .
L'API Syntax crea una struttura ad albero con la radice che rappresenta l'unità di compilazione. I nodi nell'albero
rappresentano le direttive using, la dichiarazione dello spazio dei nomi e tutti gli altri elementi del programma.
La struttura ad albero continua fino ai livelli più bassi: la stringa "Hello World!" è un token letterale di stringa
che è un discendente di un argomento . L'API Syntax consente l'accesso alla struttura del programma. È
possibile eseguire query per ottenere specifiche procedure consigliate per la scrittura del codice, ripercorrere
l'intera struttura ad albero per comprendere il codice e creare nuovi alberi modificando quello esistente.
Questa breve descrizione offre una panoramica del tipo di informazioni accessibili tramite l'API Syntax. L'API
Syntax non è altro che un'API formale che descrive i costrutti di codice familiari, già noti da C#. Le funzionalità
complete includono informazioni sulla formattazione del codice, inclusi spazi vuoti, interruzioni di riga e rientri.
Usando queste informazioni è possibile rappresentare completamente il codice nel modo in cui viene scritto e
letto dai programmatori umani o dal compilatore. L'uso di questa struttura consente di interagire con il codice
sorgente su un livello molto significativo. Non si tratta più di semplici stringhe di testo, ma di dati che
rappresentano la struttura di un programma C#.
Per iniziare, è necessario installare .NET Compiler Platform SDK :

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .

Informazioni sugli alberi della sintassi


È possibile usare l'API Syntax per qualsiasi analisi della struttura di codice C#. L'API Syntax espone i parser, gli
alberi della sintassi e le utilità per l'analisi e costruzione di alberi della sintassi. Si tratta del modo in cui si
cercano elementi di sintassi specifici nel codice o si legge il codice per un programma.
Un albero della sintassi è una struttura di dati usata dai compilatori C# e Visual Basic per comprendere i
programmi C# e Visual Basic. Gli alberi della sintassi vengono prodotti dallo stesso parser eseguito quando
viene compilato un progetto o uno sviluppatore preme F5. Gli alberi della sintassi garantiscono la totale fedeltà
al linguaggio. Ogni elemento di informazioni in un file di codice è rappresentato nell'albero. La scrittura di un
albero della sintassi in formato di testo riproduce l'esatto testo originale analizzato. Gli alberi di sintassi sono
anche non modificabili , ovvero non possono essere mai modificati dopo la creazione. I consumer degli alberi
possono analizzarli su più thread, senza blocchi o altre misure di concorrenza, dando per scontato che i dati non
cambiano mai. È possibile usare le API per creare nuovi alberi risultanti dalla modifica di un albero esistente.
I quattro principali blocchi predefiniti degli alberi della sintassi sono:
La classe Microsoft.CodeAnalysis.SyntaxTree, un'istanza della quale rappresenta l'intero albero di analisi.
SyntaxTree è una classe astratta con derivati specifici del linguaggio. Usare i metodi Parse della
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree classe (o
Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree ) per analizzare il testo in C# (o Visual Basic).
La classe Microsoft.CodeAnalysis.SyntaxNode, le cui istanze rappresentano costrutti sintattici, come
dichiarazioni, istruzioni, clausole ed espressioni.
La struttura Microsoft.CodeAnalysis.SyntaxToken, che rappresenta parole chiave, identificatori, operatori o
segni di punteggiatura singoli.
E infine la struttura Microsoft.CodeAnalysis.SyntaxTrivia, che rappresenta elementi di informazioni
sintatticamente non significativi, come lo spazio vuoto tra i token, le direttive di pre-elaborazione e i
commenti.
Gli elementi semplici, i token e i nodi sono composti in modo gerarchico per formare un albero che rappresenta
completamente tutti gli elementi in un frammento di codice Visual Basic o C#. È possibile visualizzare questa
struttura usando la finestra Syntax Visualizer (Visualizzatore sintassi). In Visual Studio scegliere Visualizza >
altre finestre > Syntax Visualizer . Ad esempio, il file di origine C# precedente esaminato nella finestra
Syntax Visualizer (Visualizzatore sintassi) ha l'aspetto illustrato nella figura seguente:
SyntaxNode : blu | SyntaxToken : verde | SyntaxTrivia : rosso

Esplorando questa struttura ad albero, è possibile trovare qualsiasi istruzione, espressione, token o spazio vuoto
in un file di codice.
Anche se è possibile trovare qualsiasi elemento in un file di codice usando le API Syntax, la maggior parte degli
scenari d'uso riguarda l'analisi di piccoli frammenti di codice o la ricerca di istruzioni o frammenti specifici. Gli
esempi che seguono mostrano due usi tipici per esplorare la struttura del codice o cercare singole istruzioni.

Attraversamento degli alberi


È possibile esaminare i nodi in un albero della sintassi in due modi. È possibile attraversare l'albero per
esaminare ogni nodo oppure è possibile eseguire query per recuperare elementi o nodi specifici.
Attraversamento manuale
È possibile visualizzare il codice completato per l'esempio nel repository GitHub.

NOTE
I tipi di albero della sintassi usano l'ereditarietà per descrivere i diversi elementi della sintassi validi in posizioni diverse nel
programma. L'uso di queste API spesso significa eseguire il cast di proprietà o membri di raccolte in tipi derivati specifici.
Negli esempi seguenti, l'assegnazione e i cast sono istruzioni separate, con variabili tipizzate in modo esplicito. È possibile
leggere il codice per visualizzare i tipi restituiti dell'API e il tipo di runtime degli oggetti restituiti. In pratica, è più comune
usare variabili tipizzate in modo implicito e basarsi sui nomi delle API per descrivere il tipo di oggetti in corso di analisi.

Creare un nuovo progetto C# Stand-Alone Code Analysis Tool (Strumento di analisi del codice autonomo):
In Visual Studio scegliere file > nuovo > progetto per visualizzare la finestra di dialogo nuovo progetto.
In Visual C# > Extensibility di Visual C# scegliere strumento di analisi del codice autonomo .
Denominare il progetto "SyntaxTreeManualTraversal " e fare clic su OK.
Verrà analizzato il semplice programma "Hello World!" mostrato in precedenza. Aggiungere il testo per il
programma Hello World come costante nella classe Program :

const string programText =


@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";

Aggiungere poi il codice seguente per creare l'albero della sintassi per il testo del codice nella costante
programText . Aggiungere la riga seguente al metodo Main :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);


CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Queste due righe creano l'albero e ne recuperano il nodo radice. È ora possibile esaminare i nodi dell'albero.
Aggiungere queste righe al metodo Main per visualizzare alcune delle proprietà del nodo radice nell'albero:

WriteLine($"The tree is a {root.Kind()} node.");


WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using statements. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
WriteLine($"\t{element.Name}");

Eseguire l'applicazione per vedere cosa ha individuato il codice sul nodo radice in questo albero.
In genere, l'attraversamento del codice viene eseguito per acquisire informazioni sul codice. In questo esempio,
viene analizzato codice noto per esplorare le API. Aggiungere il codice seguente per esaminare il primo membro
del nodo root :

MemberDeclarationSyntax firstMember = root.Members[0];


WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;

Tale membro è di tipo Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax e rappresenta tutto


nell'ambito della dichiarazione namespace HelloWorld . Aggiungere il codice seguente per individuare i nodi
dichiarati all'interno dello spazio dei nomi HelloWorld :

WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");


WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");

Eseguire il programma per verificare quanto appreso.


Ora che è noto che la dichiarazione è un Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax,
dichiarare una nuova variabile di quel tipo per esaminare la dichiarazione di classe. Questa classe contiene solo
un membro: il metodo Main . Aggiungere il codice seguente per trovare il metodo Main ed eseguirne il cast su
Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.

var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];


WriteLine($"There are {programDeclaration.Members.Count} members declared in the
{programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];

Il nodo della dichiarazione del metodo contiene tutte le informazioni sintattiche sul metodo. A questo punto
verranno visualizzati il tipo restituito del metodo Main , il numero e i tipi degli argomenti e il testo del corpo del
metodo. Aggiungere il codice seguente:

WriteLine($"The return type of the {mainDeclaration.Identifier} method is {mainDeclaration.ReturnType}.");


WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count} parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
WriteLine($"The type of the {item.Identifier} parameter is {item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method follows:");
WriteLine(mainDeclaration.Body.ToFullString());

var argsParameter = mainDeclaration.ParameterList.Parameters[0];

Eseguire il programma per visualizzare tutte le informazioni individuate su questo programma:


The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using statements. They are:
System
System.Collections
System.Linq
System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
{
Console.WriteLine("Hello, World!");
}

Metodi di query
Oltre ad attraversare gli alberi, è anche possibile esplorare l'albero della sintassi usando i metodi di query
definiti in Microsoft.CodeAnalysis.SyntaxNode. Questi metodi dovrebbero essere immediatamente familiari a
chiunque abbia familiarità con XPath. Per trovare rapidamente elementi in un albero, è possibile usare questi
metodi con LINQ. SyntaxNode include metodi di query, come DescendantNodes, AncestorsAndSelf e
ChildNodes.
È possibile usare questi metodi di query per trovare l'argomento del metodo Main , in alternativa
all'esplorazione dell'albero. Aggiungere il codice seguente alla fine del metodo Main :

var firstParameters = from methodDeclaration in root.DescendantNodes()


.OfType<MethodDeclarationSyntax>()
where methodDeclaration.Identifier.ValueText == "Main"
select methodDeclaration.ParameterList.Parameters.First();

var argsParameter2 = firstParameters.Single();

WriteLine(argsParameter == argsParameter2);

La prima istruzione usa un'espressione LINQ e il metodo DescendantNodes per individuare lo stesso parametro,
come nell'esempio precedente.
Eseguire il programma. Si può notare che l'espressione LINQ ha trovato lo stesso parametro individuato con
l'esplorazione manuale dell'albero.
L'esempio usa istruzioni WriteLine per visualizzare informazioni sugli alberi della sintassi durante
l'attraversamento. È anche possibile ottenere molte più informazioni eseguendo il programma terminato nel
debugger. È possibile esaminare molti più metodi e proprietà che fanno parte dell'albero della sintassi creato
per il programma Hello World.

Percorrere in modo ricorsivo la sintassi


Spesso è necessario trovare tutti i nodi di un tipo specifico in un albero della sintassi, ad esempio ogni
dichiarazione di proprietà in un file. Con l'estensione della classe
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker e l'override del metodo
VisitPropertyDeclaration(PropertyDeclarationSyntax), si elabora ogni dichiarazione di proprietà in un albero
della sintassi senza conoscerne in anticipo la struttura. CSharpSyntaxWalker è un tipo specifico di
CSharpSyntaxVisitor che visita in modo ricorsivo un nodo e tutti i relativi nodi figlio.
Questo esempio implementa un CSharpSyntaxWalker che esamina un albero della sintassi. Raccoglie le direttive
using individuate, che non importano uno spazio dei nomi System .

Creare un nuovo progetto C# Stand-Alone Code Analysis Tool (Strumento di analisi del codice autonomo) e
denominarlo "SyntaxWalker ."
È possibile visualizzare il codice completato per l'esempio nel repository GitHub. L'esempio su GitHub contiene
entrambi i progetti descritti in questa esercitazione.
Come nell'esempio precedente, è possibile definire una costante stringa per contenere il testo del programma
che verrà analizzato:

const string programText =


@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
using Microsoft;
using System.ComponentModel;

namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;

class Foo { }
}

namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;

class Bar { }
}
}";

Questo testo di origine contiene direttive using distribuite in quattro diverse posizioni: nel livello file, nello
spazio dei nomi di livello superiore e nei due spazi dei nomi annidati. Questo esempio mette in evidenza uno
scenario fondamentale per l'uso della classe CSharpSyntaxWalker per eseguire query nel codice. Sarebbe
un'operazione complessa visitare ogni nodo nell'albero della sintassi radice per trovare le dichiarazioni using. Si
crea invece una classe derivata e si esegue l'override del metodo che viene chiamato solo quando il nodo
corrente nell'albero è una direttiva using. Non vengono eseguite altre operazioni su qualsiasi altro tipo di nodo.
Questo singolo metodo esamina ogni istruzione using e crea una raccolta degli spazi dei nomi non inclusi nello
spazio dei nomi System . Si compila un CSharpSyntaxWalker che esamina tutte le istruzioni using , ma solo le
istruzioni using .
Dopo aver definito il testo del programma, è necessario creare un SyntaxTree e ottenere la radice di tale albero:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);


CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Creare poi una nuova classe. In Visual Studio scegliere progetto > Aggiungi nuovo elemento . Nella finestra
di dialogo Aggiungi nuovo elemento digitare UsingCollector.cs come nome del file.
Implementare la funzionalità del visitatore di using nella classe UsingCollector . Per iniziare, far derivare la
classe UsingCollector da CSharpSyntaxWalker.

class UsingCollector : CSharpSyntaxWalker

È necessario spazio di archiviazione per i nodi dello spazio dei nomi raccolti. Dichiarare una proprietà pubblica di
sola lettura nella classe UsingCollector . Questa variabile viene usata per archiviare i nodi UsingDirectiveSyntax
trovati:

public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();

La classe di base, CSharpSyntaxWalker implementa la logica per visitare ogni nodo nell'albero della sintassi. La
classe derivata esegue l'override dei metodi chiamati per i nodi specifici a cui si è interessati. In questo caso, si è
interessati a qualsiasi direttiva using . Questo significa che è necessario eseguire l'override del metodo
VisitUsingDirective(UsingDirectiveSyntax). L'unico argomento di questo metodo è un oggetto
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax. Ciò rappresenta un vantaggio importante rispetto
all'uso dei visitatori: i metodi sottoposti a override vengono chiamati con argomenti di cui è già stato eseguito il
cast al tipo di nodo specifico. La classe Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax include una
proprietà Name che archivia il nome dello spazio dei nomi importato. Si tratta di un oggetto
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Aggiungere il codice seguente nell'override di
VisitUsingDirective(UsingDirectiveSyntax):

public override void VisitUsingDirective(UsingDirectiveSyntax node)


{
WriteLine($"\tVisitUsingDirective called with {node.Name}.");
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System."))
{
WriteLine($"\t\tSuccess. Adding {node.Name}.");
this.Usings.Add(node);
}
}

Come per l'esempio precedente, sono state aggiunte svariate istruzioni WriteLine per facilitare la
comprensione di questo metodo. È possibile vedere quando viene chiamato e quali argomenti vengono passati
al metodo ogni volta.
Infine, è necessario aggiungere due righe di codice per creare UsingCollector e fare in modo che visiti il nodo
radice raccogliendo tutte le istruzioni using . Aggiungere quindi un ciclo foreach per visualizzare tutte le
istruzioni using trovate dallo strumento di raccolta:

var collector = new UsingCollector();


collector.Visit(root);
foreach (var directive in collector.Usings)
{
WriteLine(directive.Name);
}

Compilare ed eseguire il programma. Viene visualizzato l'output seguente:


VisitUsingDirective called with System.
VisitUsingDirective called with System.Collections.Generic.
VisitUsingDirective called with System.Linq.
VisitUsingDirective called with System.Text.
VisitUsingDirective called with Microsoft.CodeAnalysis.
Success. Adding Microsoft.CodeAnalysis.
VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
Success. Adding Microsoft.CodeAnalysis.CSharp.
VisitUsingDirective called with Microsoft.
Success. Adding Microsoft.
VisitUsingDirective called with System.ComponentModel.
VisitUsingDirective called with Microsoft.Win32.
Success. Adding Microsoft.Win32.
VisitUsingDirective called with System.Runtime.InteropServices.
VisitUsingDirective called with System.CodeDom.
VisitUsingDirective called with Microsoft.CSharp.
Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .

La procedura è stata completata. È stata usata l'API Syntax per individuare tipi specifici di istruzioni e
dichiarazioni C# in codice sorgente C#.
Introduzione all'analisi semantica
18/03/2020 • 16 minutes to read • Edit Online

In questa esercitazione si presuppone una certa familiarità con l'API Syntax. L'articolo Introduzione all'analisi
della sintassi offre informazioni introduttive sufficienti.
In questa esercitazione verranno esplorate le API Symbol e Binding . Queste API offrono informazioni sul
significato semantico di un programma e consentono di porre domande sui tipi rappresentati da qualsiasi
simbolo nel programma e ottenere risposte.
È necessario installare .NET Compiler Platform SDK :

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .

Informazioni su compilazioni e simboli


Man mano che si lavora con .NET Compiler SDK si acquisirà familiarità con le distinzioni tra API Syntax e API
Semantic. L'API Syntax consente di esaminare la struttura di un programma. Tuttavia, spesso servono
informazioni più dettagliate sulla semantica o il significato di un programma. Mentre un file di codice sciolto o
un frammento di codice Visual Basic o C , può essere analizzato sintatticamente in isolamento, non è
significativo porre domande come "qual è il tipo di questa variabile" in un vuoto. Il significato di un nome di tipo
potrebbe essere dipendente da riferimenti ad assembly, importazioni di spazi dei nomi o altri file di codice.
Queste domande possono ottenere risposta tramite l'API Semantic , in particolare la classe
Microsoft.CodeAnalysis.Compilation.
Un'istanza di Compilation è paragonabile a un singolo progetto, dal punto di vista del compilatore e rappresenta
tutti gli elementi necessari per compilare un programma Visual Basic o C#. La compilazione include il set di file
di origine da compilare, i riferimenti agli assembly e le opzioni del compilatore. È possibile ragionare sul
significato del codice usando tutte le altre informazioni in questo contesto. Compilation consente di trovare i
simboli , ovvero entità come i tipi, gli spazi dei nomi, i membri e le variabili a cui fanno riferimento i nomi e altre
espressioni. Il processo di associazione di nomi ed espressioni a simboli viene chiamato associazione .
Come Microsoft.CodeAnalysis.SyntaxTree, Compilation è una classe astratta con derivati specifici del linguaggio.
Quando si crea un'istanza di Compilation, è necessario richiamare un metodo factory sulla classe
Microsoft.CodeAnalysis.CSharp.CSharpCompilation (o
Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Esecuzione di query sui simboli


In questa esercitazione, viene esaminato ancora una volta il programma "Hello World". In questo caso si esegue
una query sui simboli nel programma per scoprire quali tipi rappresentano questi simboli. Verrà eseguita una
query per recuperare i tipi in uno spazio dei nomi e si imparerà a trovare i metodi disponibili per un tipo.
È possibile visualizzare il codice completato per l'esempio nel repository GitHub.

NOTE
I tipi di albero della sintassi usano l'ereditarietà per descrivere i diversi elementi della sintassi validi in posizioni diverse nel
programma. L'uso di queste API spesso significa eseguire il cast di proprietà o membri di raccolte in tipi derivati specifici.
Negli esempi seguenti, l'assegnazione e i cast sono istruzioni separate, con variabili tipizzate in modo esplicito. È possibile
leggere il codice per visualizzare i tipi restituiti dell'API e il tipo di runtime degli oggetti restituiti. In pratica, è più comune
usare variabili tipizzate in modo implicito e basarsi sui nomi delle API per descrivere il tipo di oggetti in corso di analisi.

Creare un nuovo progetto C# Stand-Alone Code Analysis Tool (Strumento di analisi del codice autonomo):
In Visual Studio scegliere File > nuovo > progetto per visualizzare la finestra di dialogo Nuovo progetto.
InEstensibilità di Visual C, > scegliere Strumento di analisi del codice autonomo .
Denominare il progetto "SemanticQuickStar t " e fare clic su OK.
Verrà analizzato il semplice programma "Hello World!" mostrato in precedenza. Aggiungere il testo per il
programma Hello World come costante nella classe Program :

const string programText =


@"using System;
using System.Collections.Generic;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
Aggiungere poi il codice seguente per creare l'albero della sintassi per il testo del codice nella costante
programText . Aggiungere la riga seguente al metodo Main :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Compilare poi un'istanza di CSharpCompilation dall'albero già creato. L'esempio "Hello World" si basa sui tipi
String e Console. È necessario fare riferimento all'assembly che dichiara i due tipi nella compilazione.
Aggiungere la riga seguente al metodo Main per creare una compilazione dell'albero della sintassi, incluso il
riferimento all'assembly appropriato:

var compilation = CSharpCompilation.Create("HelloWorld")


.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);

Il metodo CSharpCompilation.AddReferences aggiunge i riferimenti alla compilazione. Il metodo


MetadataReference.CreateFromFile carica un assembly come riferimento.

Esecuzione di query sul modello semantico


Dopo aver creato un Compilation è possibile richiedere un SemanticModel per qualsiasi SyntaxTree contenuto in
tale Compilation. È possibile considerare il modello semantico come fonte di tutte le informazioni che si
ottengono in genere da IntelliSense. Un oggetto SemanticModel può rispondere a domande come "Quali nomi
sono compresi nell'ambito in questa posizione?", "Quali membri sono accessibili da questo metodo?", "Quali
variabili vengono usate in questo blocco di testo?" e "A cosa fa riferimento questa espressione/questo nome?"
Aggiungere questa istruzione per creare il modello semantico:

SemanticModel model = compilation.GetSemanticModel(tree);

Associazione di un nome
Compilation crea SemanticModel da SyntaxTree. Dopo aver creato il modello, è possibile eseguire query per
trovare la prima direttiva using e recuperare informazioni sui simboli per lo spazio dei nomi System .
Aggiungere queste due righe al metodo Main per creare il modello semantico e recuperare il simbolo per la
prima istruzione using:

// Use the syntax tree to find "using System;"


UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:


SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

Il codice precedente illustra come associare il nome nella prima direttiva using per recuperare un
Microsoft.CodeAnalysis.SymbolInfo per lo spazio dei nomi System . Il codice precedente dimostra anche che si
usa il modello della sintassi per trovare la struttura del codice e il modello semantico per comprenderne il
significato. Il modello della sintassi individua la stringa System nell'istruzione using. Il modello semantico
include tutte le informazioni sui tipi definiti nello spazio dei nomi System .
Dall'oggetto SymbolInfo è possibile ottenere Microsoft.CodeAnalysis.ISymbol usando la proprietà
SymbolInfo.Symbol. Questa proprietà restituisce il simbolo a cui fa riferimento questa espressione. Per le
espressioni che non fanno riferimento ad alcun elemento (ad esempio, i valori letterali numerici) questa
proprietà è null . Quando SymbolInfo.Symbol non è null, ISymbol.Kind indica il tipo del simbolo. In questo
esempio, la proprietà ISymbol.Kind è un SymbolKind.Namespace. Aggiungere il codice seguente al metodo
Main . Recupera il simbolo per lo spazio dei nomi System e quindi visualizza tutti gli spazi dei nomi figlio
dichiarati nello spazio dei nomi System :

var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;


foreach (INamespaceSymbol ns in systemSymbol.GetNamespaceMembers())
{
Console.WriteLine(ns);
}

Eseguire il programma. L'output dovrebbe essere il seguente:

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

NOTE
L'output non include ogni spazio dei nomi figlio dello spazio dei nomi System . Viene visualizzato ogni spazio dei nomi
presente in questa compilazione, che fa riferimento solo all'assembly in cui è dichiarato System.String . Gli eventuali
spazi dei nomi dichiarati in altri assembly non sono noti per questa compilazione

Associazione di un'espressione
Il codice precedente mostra come trovare un simbolo mediante l'associazione a un nome. Esistono altre
espressioni in un programma C# che possono essere associate e non sono nomi. Per illustrare questa
funzionalità, si esaminerà l'associazione a un semplice valore letterale stringa.
Il programma "Hello World" contiene un Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, ovvero
la stringa "Hello, World!" visualizzata nella console.
È possibile trovare la stringa "Hello, World!" individuando il singolo valore letterale stringa nel programma.
Dopo aver individuato il nodo della sintassi, è possibile ottenere le informazioni sul tipo per tale nodo dal
modello semantico. Aggiungere il codice seguente al metodo Main :

// Use the syntax model to find the literal string:


LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:


TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

Lo struct Microsoft.CodeAnalysis.TypeInfo include una proprietà TypeInfo.Type che consente l'accesso alle
informazioni semantiche sul tipo del valore letterale. In questo esempio, si tratta del tipo string . Aggiungere
una dichiarazione che assegna questa proprietà a una variabile locale:

var stringTypeSymbol = (INamedTypeSymbol)literalInfo.Type;

Per completare questa esercitazione, verrà definita una query LINQ che crea una sequenza di tutti i metodi
pubblici dichiarati nel tipo string che restituiscono string . Questa query diventa complessa, quindi viene
creata riga per riga per poi essere ricostruita come singola query. L'origine per questa query è la sequenza di
tutti i membri dichiarati nel tipo string :

var allMembers = stringTypeSymbol.GetMembers();

Questa sequenza di origine contiene tutti i membri, inclusi le proprietà e i campi, quindi filtrarla tramite il
metodo ImmutableArray<T>.OfType per trovare gli elementi corrispondenti a oggetti
Microsoft.CodeAnalysis.IMethodSymbol:

var methods = allMembers.OfType<IMethodSymbol>();

Aggiungere poi un altro filtro per restituire solo i metodi pubblici che restituiscono string :

var publicStringReturningMethods = methods


.Where(m => m.ReturnType.Equals(stringTypeSymbol) &&
m.DeclaredAccessibility == Accessibility.Public);

Selezionare solo la proprietà del nome e solo i nomi distinti rimuovendo qualsiasi overload:

var distinctMethods = publicStringReturningMethods.Select(m => m.Name).Distinct();

È anche possibile creare la query completa usando la sintassi di query LINQ e quindi visualizzare tutti i nomi di
metodo nella console:

foreach (string name in (from method in stringTypeSymbol


.GetMembers().OfType<IMethodSymbol>()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility == Accessibility.Public
select method.Name).Distinct())
{
Console.WriteLine(name);
}

Compilare ed eseguire il programma. Dovrebbe venire visualizzato l'output seguente.


Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

È stata usata l'API Semantic per trovare e visualizzare informazioni sui simboli che fanno parte di questo
programma.
Introduzione alla trasformazione della sintassi
02/11/2020 • 24 minutes to read • Edit Online

Questa esercitazione si basa sui concetti e sulle tecniche descritti nelle guide introduttive Introduzione all'analisi
della sintassi e Introduzione all'analisi semantica. Se non è già stato fatto, consultare tali guide introduttive
prima di procedere con questa.
In questa guida verranno illustrate le tecniche per la creazione e la trasformazione degli alberi della sintassi. In
combinazione con le tecniche apprese nelle guide precedenti, sarà possibile creare il primo refactoring dalla riga
di comando.

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .

Non modificabilità e piattaforma del compilatore .NET


La non modificabilità è un principio fondamentale della piattaforma del compilatore .NET. Le strutture di dati
non modificabili non possono essere alterate dopo essere state create. Le strutture di dati non modificabili
possono essere condivise in modo sicuro e analizzate da più consumer contemporaneamente. Non vi è alcun
rischio che un consumer influisca su un altro in modi imprevedibili. L'analizzatore non richiede blocchi o altre
misure di concorrenza. Questa regola si applica ad alberi della sintassi, compilazioni, simboli, modelli semantici
e tutte le altre strutture di dati. Invece di modificare le strutture esistenti, le API creano nuovi oggetti in base alle
differenze specificate rispetto a quelli precedenti. È possibile applicare questo concetto agli alberi della sintassi
per creare nuovi alberi tramite trasformazioni.

Creare e trasformare alberi


È possibile scegliere tra due strategie per le trasformazioni della sintassi. I metodi factor y sono
particolarmente indicati per la ricerca di nodi specifici da sostituire o di posizioni specifiche in cui inserire nuovo
codice. I rewriter sono la soluzione migliore per analizzare un intero progetto alla ricerca di modelli di codice
da sostituire.
Creare nodi con metodi factory
La prima trasformazione della sintassi illustra i metodi factory. Si sostituirà un'istruzione
using System.Collections; con un'istruzione using System.Collections.Generic; . Questo esempio illustra come
creare oggetti Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode mediante i metodi factory
Microsoft.CodeAnalysis.CSharp.SyntaxFactory. Per ogni tipo di nodo , token o elemento semplice è
disponibile un metodo factory che crea un'istanza del tipo corrispondente. È possibile creare alberi della sintassi
componendo gerarchicamente i nodi dal basso verso l'alto. Si trasformerà quindi il programma esistente
sostituendo i nodi esistenti con il nuovo albero creato.
Avviare Visual Studio e creare un nuovo progetto C# Stand-Alone Code Analysis Tool (Strumento di analisi
del codice autonomo). In Visual Studio scegliere file > nuovo > progetto per visualizzare la finestra di dialogo
nuovo progetto. In Visual C# > Extensibility di Visual C# scegliere uno strumento di analisi del codice
autonomo . Questa guida include due progetti di esempio, quindi denominare la soluzione
SyntaxTransformationQuickStar t e il progetto ConstructionCS . Fare clic su OK .
Questo progetto usa i metodi della classe Microsoft.CodeAnalysis.CSharp.SyntaxFactory per costruire un
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax che rappresenta lo spazio dei nomi
System.Collections.Generic .

Aggiungere la seguente direttiva using all'inizio dell'oggetto Program.cs .

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;


using static System.Console;

Verranno creati i nodi della sintassi dei nomi per creare l'albero che rappresenta l'istruzione
using System.Collections.Generic; . NameSyntax è la classe di base per quattro tipi di nomi disponibili in C#.
Questi quattro tipi di nomi possono essere combinati per creare qualsiasi nome disponibile nel linguaggio C#:
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax, che rappresenta i nomi di singoli identificatori semplici,
come System e Microsoft .
Microsoft.CodeAnalysis.CSharp.Syntax.GenericNameSyntax, che rappresenta un nome di tipo o di metodo
generico, come List<int> .
Microsoft.CodeAnalysis.CSharp.Syntax.QualifiedNameSyntax, che rappresenta un nome completo nel
formato <left-name>.<right-identifier-or-generic-name> , come System.IO .
Microsoft.CodeAnalysis.CSharp.Syntax.AliasQualifiedNameSyntax, che rappresenta un nome usando un alias
assembly extern, come LibraryV2::Foo .
Usare il metodo IdentifierName(String) per creare un nodo NameSyntax. Aggiungere il codice seguente nel
metodo Main in Program.cs :

NameSyntax name = IdentifierName("System");


WriteLine($"\tCreated the identifier {name}");
Il codice precedente crea un oggetto IdentifierNameSyntax e lo assegna alla variabile name . Molte API Roslyn
restituiscono classi di base per agevolare l'uso dei tipi correlati. La variabile name , un oggetto NameSyntax, può
essere riutilizzata durante la creazione di QualifiedNameSyntax. Non usare l'inferenza del tipo durante la
creazione dell'esempio. Tale passaggio verrà automatizzato in questo progetto.
Il nome è stato creato. A questo punto, è possibile creare altri nodi nell'albero compilando un oggetto
QualifiedNameSyntax. Il nuovo albero usa name a sinistra del nome e una nuova IdentifierNameSyntax per lo
spazio dei nomi Collections come lato destro dell'oggetto QualifiedNameSyntax. Aggiungere il codice
seguente a program.cs :

name = QualifiedName(name, IdentifierName("Collections"));


WriteLine(name.ToString());

Eseguire nuovamente il codice e visualizzare i risultati. Si sta creando un albero dei nodi che rappresenta il
codice. Si continuerà con questo modello per creare l'oggetto QualifiedNameSyntax per lo spazio dei nomi
System.Collections.Generic . Aggiungere il codice seguente a Program.cs :

name = QualifiedName(name, IdentifierName("Generic"));


WriteLine(name.ToString());

Eseguire nuovamente il programma per verificare che sia stata compilata la struttura ad albero del codice da
aggiungere.
Creare una struttura modificata
È stato creato un albero della sintassi di piccole dimensioni che contiene una sola istruzione. Le API per la
creazione di nuovi nodi sono la soluzione ideale per creare singole istruzioni o altri piccoli blocchi di codice.
Tuttavia, per creare blocchi di codice di maggiori dimensioni, è necessario usare metodi che sostituiscono i nodi
o inseriscono i nodi in una struttura esistente. Tenere presente che gli alberi della sintassi non sono modificabili.
L'API Syntax non offre alcun meccanismo per la modifica di un albero della sintassi esistente dopo la
costruzione. Fornisce invece metodi che producono nuovi alberi in base alle modifiche a quelli esistenti. I metodi
With* sono definiti nelle classi concrete che derivano da SyntaxNode o nei metodi di estensione dichiarati nella
classe SyntaxNodeExtensions. Questi metodi creano un nuovo nodo applicando le modifiche alle proprietà figlio
del nodo esistente. Inoltre, il metodo di estensione ReplaceNode può essere usato per sostituire un nodo
discendente in un sottoalbero. Anche questo metodo aggiorna l'elemento padre in modo da puntare
all'elemento figlio appena creato e ripete il processo per l'intero albero: un processo noto come re-spinning
dell'albero.
Il passaggio successivo consiste nel creare un albero che rappresenta un intero programma (di piccole
dimensioni) e quindi modificarlo. Aggiungere il codice seguente all'inizio della classe Program :
private const string sampleCode =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";

NOTE
Nell'esempio di codice viene usato lo spazio dei nomi System.Collections , invece dello spazio dei nomi
System.Collections.Generic .

Aggiungere quindi il codice seguente alla fine del metodo Main per analizzare il testo e creare un albero:

SyntaxTree tree = CSharpSyntaxTree.ParseText(sampleCode);


var root = (CompilationUnitSyntax)tree.GetRoot();

In questo esempio viene usato il metodo WithName(NameSyntax) per sostituire il nome in un nodo
UsingDirectiveSyntax con quello creato nel codice precedente.
Creare un nuovo nodo UsingDirectiveSyntax usando il metodo WithName(NameSyntax) per aggiornare il nome
System.Collections con il nome creato nel codice precedente. Aggiungere il codice seguente alla fine del
metodo Main :

var oldUsing = root.Usings[1];


var newUsing = oldUsing.WithName(name);
WriteLine(root.ToString());

Eseguire il programma ed esaminare attentamente l'output. newusing non è stato inserito nell'albero della
radice. La struttura originale non è stata modificata.
Aggiungere il codice seguente usando il metodo di estensione ReplaceNode per creare un nuovo albero. Il
nuovo albero è il risultato della sostituzione dell'importazione esistente con il nodo newUsing aggiornato.
Questo nuovo albero viene assegnato alla variabile root esistente:

root = root.ReplaceNode(oldUsing, newUsing);


WriteLine(root.ToString());

Eseguire di nuovo il programma. Questa volta l'albero importa correttamente lo spazio dei nomi
System.Collections.Generic .

Trasformare alberi tramite SyntaxRewriters

I metodi With* e ReplaceNode offrono una pratica soluzione per trasformare singoli rami di un albero della
sintassi. La classe Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter esegue più trasformazioni in una
struttura della sintassi. La classe Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter è una sottoclasse di
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>. CSharpSyntaxRewriter applica una
trasformazione a un tipo specifico di SyntaxNode. È possibile applicare trasformazioni a vari tipi di oggetti
SyntaxNode ogni volta che compaiono in un albero della sintassi. Il secondo progetto in questa guida
introduttiva crea un refactoring dalla riga di comando che rimuove i tipi espliciti nelle dichiarazioni di variabili
locali ovunque sia possibile usare l'inferenza del tipo.
Creare un nuovo progetto dello strumento di analisi del codice in C# autonomo. In Visual Studio fare
doppio clic sul nodo SyntaxTransformationQuickStart della soluzione. Scegliere Aggiungi > nuovo progetto
per visualizzare la finestra di dialogo nuovo progetto . In Visual C# > Extensibility di Visual C# scegliere
strumento di analisi del codice autonomo . Assegnare al progetto il nome TransformationCS e fare clic su
OK.
Il primo passaggio consiste nella creazione di una classe che deriva da CSharpSyntaxRewriter per eseguire le
trasformazioni. Aggiungere un nuovo file di classe al progetto. In Visual Studio scegliere progetto > Aggiungi
classe .... Nella finestra di dialogo Aggiungi nuovo elemento Digitare TypeInferenceRewriter.cs come nome
file.
Aggiungere le direttive using seguenti al file TypeInferenceRewriter.cs :

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

Impostare quindi la classe TypeInferenceRewriter in modo da estendere la classe CSharpSyntaxRewriter:

public class TypeInferenceRewriter : CSharpSyntaxRewriter

Aggiungere il codice seguente per dichiarare un campo privato di sola lettura che contiene un SemanticModel e
inizializzarlo nel costruttore. Questo campo sarà necessario in un secondo momento per determinare dove è
possibile usare l'inferenza del tipo:

private readonly SemanticModel SemanticModel;

public TypeInferenceRewriter(SemanticModel semanticModel) => SemanticModel = semanticModel;

Eseguire l'override del metodo VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax):

public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)


{

NOTE
Molte API Roslyn dichiarano tipi restituiti che sono classi di base degli effettivi tipi di runtime restituiti. In molti scenari un
tipo di nodo può essere interamente sostituito da un altro tipo di nodo o può essere addirittura rimosso. In questo
esempio, il metodo VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) restituisce un SyntaxNode, anziché il
tipo derivato LocalDeclarationStatementSyntax. Il rewriter restituisce un nuovo nodo LocalDeclarationStatementSyntax
basato su quello esistente.

Questa guida introduttiva gestisce le dichiarazioni di variabili locali. È possibile estenderla ad altre dichiarazioni,
ad esempio cicli foreach , cicli for , espressioni LINQ ed espressioni lambda. Inoltre, il rewriter trasformerà solo
le dichiarazioni nella forma più semplice:

Type variable = expression;

Se si vuole esplorare in autonomia, considerare di estendere l'esempio completato per questi tipi di
dichiarazioni di variabili:

// Multiple variables in a single declaration.


Type variable1 = expression1,
variable2 = expression2;
// No initializer.
Type variable;

Aggiungere il codice seguente nel corpo del metodo VisitLocalDeclarationStatement per ignorare la riscrittura
di queste forme di dichiarazioni:

if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}

Il metodo indica che non viene eseguita alcuna riscrittura restituendo il parametro node senza modifiche. Se
nessuna di queste espressioni if è true, il nodo rappresenta una possibile dichiarazione con l'inizializzazione.
Aggiungere queste istruzioni per estrarre il nome del tipo specificato nella dichiarazione ed eseguirne il binding
usando il campo SemanticModel per ottenere un simbolo di tipo:

var declarator = node.Declaration.Variables.First();


var variableTypeName = node.Declaration.Type;

var variableType = (ITypeSymbol)SemanticModel


.GetSymbolInfo(variableTypeName)
.Symbol;

A questo punto, aggiungere l'istruzione seguente per eseguire il binding dell'espressione dell'inizializzatore:

var initializerInfo = SemanticModel.GetTypeInfo(declarator.Initializer.Value);

Infine, aggiungere l'istruzione if seguente per sostituire il nome del tipo esistente con la parola chiave var se
il tipo di espressione dell'inizializzatore corrisponde al tipo specificato:
if (SymbolEqualityComparer.Default.Equals(variableType, initializerInfo.Type))
{
TypeSyntax varTypeName = SyntaxFactory.IdentifierName("var")
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

return node.ReplaceNode(variableTypeName, varTypeName);


}
else
{
return node;
}

L'operazione condizionale è necessaria perché la dichiarazione può esegue il cast dell'espressione


dell'inizializzatore su un'interfaccia o una classe di base. Se si vuole ottenere tale risultato, i tipi sul lato sinistro e
destro dell'assegnazione non corrispondono. La rimozione del tipo esplicito in questi casi comporterebbe la
modifica della semantica di un programma. var viene specificato come un identificatore anziché come una
parola chiave poiché var è una parola chiave contestuale. Gli elementi semplici iniziali e finali (spazio) vengono
trasferiti dal nome del tipo precedente alla parola chiave var per mantenere lo spazio vuoto verticale e il
rientro. È più semplice usare ReplaceNode anziché With* per trasformare LocalDeclarationStatementSyntax,
perché il nome del tipo è di fatto il nipote dell'istruzione di dichiarazione.
TypeInferenceRewriter è stato completato. A questo punto, eseguire la restituzione nel file Program.cs per
completare l'esempio. Creare un Compilation di test e ottenere SemanticModel da quest'ultimo. Usare tale
SemanticModel per provare TypeInferenceRewriter . Questo passaggio verrà eseguito per ultimo. Nel frattempo
dichiarare una variabile segnaposto che rappresenta la compilazione di test:

Compilation test = CreateTestCompilation();

Dopo qualche istante, dovrebbe essere visualizzata una sottolineatura ondulata di errore che indica che non è
presente alcun metodo CreateTestCompilation . Premere CTRL+PUNTO per aprire la lampadina e quindi
premere INVIO per richiamare il comando Genera stub di metodo . Questo comando genererà uno stub di
metodo per il metodo CreateTestCompilation nella classe Program . Si tornerà a questo metodo per la
compilazione in un secondo momento:

Scrivere il codice seguente per eseguire l'iterazione su ogni SyntaxTree nel Compilation di test. Per ognuno,
inizializzare un nuovo TypeInferenceRewriter con il SemanticModel per l'albero corrispondente:
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
SemanticModel model = test.GetSemanticModel(sourceTree);

TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());

if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}

All'interno dell'istruzione foreach che è stata creata, aggiungere il codice seguente per eseguire la
trasformazione su ogni albero di origine. Questo codice scrive in modo condizionale il nuovo albero trasformato
se sono state apportate modifiche. Il rewriter deve modificare un albero solo se rileva una o più dichiarazioni di
variabili locali che potrebbero essere semplificate tramite l'inferenza del tipo:

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());

if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}

Dovrebbero essere visualizzate sottolineature ondulate sotto il codice File.WriteAllText . Selezionare la


lampadina e aggiungere l'istruzione using System.IO; necessaria.
Ci siamo quasi. È stato lasciato un solo passaggio: creazione di un test Compilation . Poiché non è stata usata
l'inferenza del tipo, questa guida introduttiva rappresenterebbe un test case perfetto. Purtroppo, la creazione di
una compilazione da un file di progetto C# esula dall'ambito di questa procedura dettagliata. Tuttavia, se le
istruzioni sono state seguite con attenzione, esiste una possibilità. Sostituire il contenuto del metodo
CreateTestCompilation con il codice riportato di seguito. Tale codice crea una compilazione di test che
casualmente corrisponde al progetto descritto in questa guida introduttiva:
String programPath = @"..\..\..\Program.cs";
String programText = File.ReadAllText(programPath);
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);

String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";


String rewriterText = File.ReadAllText(rewriterPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);

SyntaxTree[] sourceTrees = { programTree, rewriterTree };

MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);

MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };

return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));

Eseguire il progetto. In Visual Studio scegliere debug > Avvia debug . Visual Studio dovrebbe segnalare che i
file nel progetto sono stati modificati. Fare clic su Sì a tutti per ricaricare i file modificati. Esaminarli per
osservare i risultati ottenuti. Si noti quanto risulta più pulito il codice senza tutti gli identificatori di tipi espliciti e
ridondanti.
Congratulazioni! Sono state usate le API del compilatore per scrivere un refactoring che cerca alcuni modelli
sintattici in tutti i file in un progetto C#, analizza la semantica del codice sorgente che corrisponde a tali modelli
e la trasforma. Sei ora ufficialmente un autore del refactoring.
Esercitazione: compilare il primo analizzatore con
correzione del codice
02/11/2020 • 45 minutes to read • Edit Online

.NET Compiler Platform SDK fornisce gli strumenti necessari per creare avvisi personalizzati destinati a codice
C# o Visual Basic. L'analizzatore contiene il codice che riconosce le violazioni della regola. La correzione del
codice contiene il codice che corregge la violazione. Le regole implementate possono essere di qualsiasi tipo:
struttura del codice, stile di codifica, convenzioni di denominazione e altro ancora. .NET Compiler Platform
fornisce il framework per l'esecuzione di analisi mentre gli sviluppatori scrivono il codice e tutte le funzionalità
dell'interfaccia utente di Visual Studio per la correzione del codice: visualizzazione di linee ondulate nell'editor,
informazioni dell'Elenco errori di Visual Studio, creazione di suggerimenti "lampadina" e visualizzazione di
un'anteprima dettagliata delle correzioni suggerite.
In questa esercitazione verrà esaminata la creazione di un analizzatore e una correzione del codice
associato usando le API Roslyn. Un analizzatore è un modo per eseguire analisi del codice sorgente e segnalare
un problema all'utente. Facoltativamente, un analizzatore può anche fornire una correzione del codice che
rappresenta una modifica del codice sorgente per l'utente. In questa esercitazione verrà creato un analizzatore
per trovare le dichiarazioni di variabili locali che potrebbero essere dichiarate tramite il modificatore const ma
che lo non sono. La correzione del codice associata modifica tali dichiarazioni per aggiungere il modificatore
const .

Prerequisiti
Visual Studio 2019 versione 16,7 o successiva
È necessario installare .NET Compiler Platform SDK tramite il programma di installazione di Visual Studio:

Istruzioni di installazione: Programma di installazione di Visual Studio


Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di
Visual Studio :
Eseguire l'installazione con il Programma di installazione di Visual Studio: visualizzazione dei carichi di lavoro
.NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro Sviluppo di
estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare il carico di lavoro Sviluppo di estensioni di Visual Studio .
4. Aprire il nodo Sviluppo di estensioni di Visual Studio nell'albero di riepilogo.
5. Selezionare la casella di controllo per .NET Compiler Platform SDK . È l'ultima voce dei componenti
facoltativi.
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
2. Selezionare la casella per l'editor DGML
Eseguire l'installazione usando il Programma di installazione di Visual Studio: scheda Singoli componenti
1. Eseguire il programma di installazione di Visual Studio .
2. Selezionare Modifica
3. Selezionare la scheda Singoli componenti
4. Selezionare la casella di controllo per .NET Compiler Platform SDK . È la prima voce nella sezione
Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è possibile installare anche l'editor DGML per visualizzare i grafici nel visualizzatore:
1. Selezionare la casella di controllo per Editor DGML . La voce è disponibile nella sezione Strumenti per il
codice .
La creazione e la convalida dell'analizzatore comprendono diversi passaggi:
1. Creare la soluzione.
2. Registrare il nome e la descrizione dell'analizzatore.
3. Segnalare gli avvisi e i suggerimenti dell'analizzatore.
4. Implementare la correzione del codice per accettare i suggerimenti.
5. Migliorare l'analisi tramite unit test.

Esplorare il modello dell'analizzatore


L'analizzatore segnala all'utente eventuali dichiarazioni di variabili locali che possono essere convertite in
costanti locali. Si consideri il codice di esempio seguente:

int x = 0;
Console.WriteLine(x);

Nel codice precedente, a x viene assegnato un valore costante che non viene mai modificato. Può essere
dichiarato usando il modificatore const :

const int x = 0;
Console.WriteLine(x);

Per determinare se una variabile può essere resa una costante, è necessaria un'analisi che richiede un'analisi
sintattica, l'analisi delle costanti dell'espressione dell'inizializzatore e l'analisi del flusso di dati per garantire che
non vengano mai eseguite operazioni di scrittura nella variabile. .NET Compiler Platform fornisce API che
rendono più semplice l'esecuzione di questa analisi. Il primo passaggio consiste nel creare un nuovo progetto di
analizzatore con correzione del codice C#.
In Visual Studio scegliere File > Nuovo > Progetto per visualizzare la finestra di dialogo Nuovo progetto.
In Visual C# > Estendibilità scegliere Analyzer with code fix (.NET Standard) (Analizzatore con
correzione del codice - .NET Standard).
Assegnare al progetto il nome "MakeConst " e fare clic su OK.
Il modello di analizzatore con correzione del codice crea tre progetti: uno contiene l'analizzatore e la correzione
del codice, il secondo è un progetto unit test e il terzo è il progetto VSIX. Il progetto di avvio predefinito è il
progetto VSIX. Premere F5 per avviare il progetto VSIX. Verrà avviata una seconda istanza di Visual Studio e sarà
caricato il nuovo analizzatore.
TIP
Quando si esegue l'analizzatore, si avvia una seconda copia di Visual Studio. Questa seconda copia usa un diverso hive del
Registro di sistema per archiviare le impostazioni. Questo consente di distinguere le impostazioni di visualizzazione nelle
due copie di Visual Studio. È possibile scegliere un tema diverso per l'esecuzione sperimentale di Visual Studio. Inoltre, non
eseguire il roaming delle impostazioni o accedere all'account di Visual Studio usando l'istanza sperimentale di Visual
Studio. Ciò consente di mantenere diverse le impostazioni.

Nella seconda istanza di Visual Studio appena avviata creare un nuovo progetto di applicazione console C#
(.NET Core o .NET Framework progetto funzionerà--Analyzers funziona a livello di origine). Passare il puntatore
del mouse sul token con una sottolineatura ondulata e viene visualizzato il testo di avviso fornito da un
analizzatore.
Il modello crea un analizzatore che genera un avviso per ogni dichiarazione di tipo in cui il nome del tipo
contiene lettere minuscole, come illustrato nella figura seguente:

Il modello fornisce inoltre una correzione del codice che converte in lettere maiuscole qualsiasi nome di un tipo
contenente caratteri minuscoli. È possibile fare clic sulla lampadina visualizzata con il messaggio di avviso per
vedere le modifiche suggerite. Accettando le modifiche suggerite, vengono aggiornati il nome del tipo e tutti i
riferimenti a tale tipo nella soluzione. Dopo aver osservato l'analizzatore iniziale in azione, chiudere la seconda
istanza di Visual Studio e tornare al progetto dell'analizzatore.
Non è necessario avviare una seconda copia di Visual Studio e creare nuovo codice per testare ogni modifica
nell'analizzatore. Il modello crea automaticamente anche un progetto unit test. Tale progetto contiene due test.
TestMethod1 illustra il formato tipico di un test che analizza il codice senza attivare la diagnostica. TestMethod2
illustra il formato di un test che attiva la diagnostica e quindi applica una correzione del codice suggerita.
Durante la creazione dell'analizzatore e della correzione del codice, si scriveranno i test per le diverse strutture di
codice per verificare il proprio lavoro. Gli unit test per gli analizzatori sono molto più rapidi rispetto ai test in
modalità interattiva con Visual Studio.

TIP
Gli unit test per gli analizzatori sono uno strumento molto utile quando si sa quali costrutti di codice devono attivare o
meno l'analizzatore. Il caricamento dell'analizzatore in un'altra copia di Visual Studio è una soluzione ideale per esplorare e
trovare costrutti a cui ancora non si è pensato.

Creare registrazioni per l'analizzatore


Il modello crea la classe DiagnosticAnalyzer iniziale nel file MakeConstAnalyzer.cs . Questo analizzatore
iniziale mostra due importanti proprietà di ogni analizzatore.
Ogni analizzatore diagnostico deve fornire un attributo [DiagnosticAnalyzer] che descrive il linguaggio su
cui opera.
Ogni analizzatore diagnostico deve derivare dalla classe DiagnosticAnalyzer.
Il modello illustra anche le funzionalità di base che fanno parte di qualsiasi analizzatore:
1. Registrare le azioni. Le azioni rappresentano le modifiche del codice che devono attivare l'analizzatore per
esaminare le violazioni del codice. Quando Visual Studio rileva modifiche del codice che corrispondono a
un'azione registrata, esegue una chiamata al metodo registrato dell'analizzatore.
2. Creare la diagnostica. Quando l'analizzatore rileva una violazione, crea un oggetto di diagnostica usato da
Visual Studio per notificare all'utente la violazione.
Le azioni vengono registrate nell'override del metodo DiagnosticAnalyzer.Initialize(AnalysisContext). In questa
esercitazione verranno esaminati i nodi di sintassi alla ricerca delle dichiarazioni locali, per determinare quali
di queste hanno valori costanti. Se una dichiarazione può essere costante, l'analizzatore crea e segnala una
diagnostica.
Il primo passaggio consiste nell'aggiornare le costanti di registrazione e il metodo Initialize , in modo che
queste costanti specifichino l'analizzatore "MakeConst". La maggior parte delle costanti di stringa è definita nel
file della risorsa stringa. È consigliabile attenersi a tale pratica per semplificare l'individuazione. Aprire il file
Resources.resx per il progetto dell'analizzatore MakeConst . Verrà visualizzato l'editor di risorse. Aggiornare le
risorse stringa come segue:
Modificare AnalyzerTitle in "Variable can be made constant".
Modificare AnalyzerMessageFormat in "Can be made constant".
Modificare AnalyzerDescription in "Make Constant".

Modificare inoltre l'elenco a discesa Modificatore di accesso in public . Ciò rende più semplice usare queste
costanti negli unit test. Al termine, l'editor di risorse dovrebbe apparire come mostrato di seguito:

Le modifiche rimanenti sono da apportare al file dell'analizzatore. Aprire MakeConstAnalyzer.cs in Visual


Studio. Modificare l'azione registrata da una che opera sui simboli in una che opera sulla sintassi. Nel metodo
MakeConstAnalyzerAnalyzer.Initialize trovare la riga che esegue la registrazione dell'azione sui simboli:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Sostituirla con la riga seguente:

context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze |
GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Dopo questa modifica, è possibile eliminare il metodo AnalyzeSymbol . Questo analizzatore esamina le istruzioni
SyntaxKind.LocalDeclarationStatement, non le istruzioni SymbolKind.NamedType. È possibile notare che sotto
AnalyzeNode sono visualizzate delle linee ondulate. Il codice appena aggiunto fa riferimento a un metodo
AnalyzeNode che non è stato dichiarato. Dichiarare tale metodo usando il codice seguente:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)


{
}
Modificare Category in "Usage" in MakeConstAnalyzer.cs , come illustrato nel codice seguente:

private const string Category = "Usage";

Trovare le dichiarazioni locali che potrebbero essere costanti


È ora possibile scrivere la prima versione del metodo AnalyzeNode . Deve cercare una singola dichiarazione
locale che può essere const ma non lo è, come ad esempio il codice seguente:

int x = 0;
Console.WriteLine(x);

Il primo passaggio è trovare le dichiarazioni locali. Aggiungere il codice seguente a AnalyzeNode in


MakeConstAnalyzer.cs :

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Questo cast ha sempre esito positivo, perché l'analizzatore ha eseguito la registrazione per le modifiche delle
dichiarazioni locali e solo per le dichiarazioni locali. Nessun altro tipo di nodo attiva una chiamata al metodo
AnalyzeNode . Controllare quindi se nella dichiarazione sono presenti modificatori const . Se vengono rilevati,
restituire immediatamente un risultato. Il codice seguente cerca eventuali modificatori const nella
dichiarazione locale:

// make sure the declaration isn't already const:


if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}

Infine, è necessario verificare che la variabile possa essere const . In altre parole, occorre verificare che non
venga mai assegnata dopo l'inizializzazione.
Si eseguiranno alcune analisi semantiche usando SyntaxNodeAnalysisContext. Verrà usato l'argomento
context per determinare se la dichiarazione di variabile locale può essere resa const . Un oggetto
Microsoft.CodeAnalysis.SemanticModel rappresenta tutte le informazioni semantiche in un singolo file di
origine. Per altre informazioni, vedere l'articolo relativo ai modelli semantici. Si userà
Microsoft.CodeAnalysis.SemanticModel per eseguire l'analisi del flusso dei dati nell'istruzione della
dichiarazione locale. Sarà quindi possibile usare i risultati di questa analisi del flusso di dati per assicurarsi che
non venga mai eseguita la scrittura di un nuovo valore nella variabile locale. Chiamare il metodo di estensione
GetDeclaredSymbol per recuperare ILocalSymbol per la variabile e verificare che non sia contenuto nella
raccolta DataFlowAnalysis.WrittenOutside dell'analisi del flusso di dati. Aggiungere il codice seguente alla fine
del metodo AnalyzeNode :
// Perform data flow analysis on the local declaration.
var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
var variable = localDeclaration.Declaration.Variables.Single();
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}

Il codice appena aggiunto garantisce che la variabile non viene modificata e quindi può essere resa const . È ora
possibile generare la diagnostica. Aggiungere il codice seguente come ultima riga in AnalyzeNode :

context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));

È possibile controllare lo stato di avanzamento premendo F5 per eseguire l'analizzatore. È possibile caricare
l'applicazione console creata in precedenza e quindi aggiungere il codice di test seguente:

int x = 0;
Console.WriteLine(x);

Verrà visualizzata la lampadina e l'analizzatore segnalerà la diagnostica. Tuttavia, la lampadina usa ancora la
correzione del codice del modello generato e indica che l'elemento può essere reso maiuscolo. Nella sezione
successiva viene descritto come scrivere la correzione del codice.

Scrivere la correzione del codice


Un analizzatore può fornire una o più correzioni del codice. Una correzione del codice definisce una modifica in
grado di risolvere il problema segnalato. Per l'analizzatore che è stato creato, è possibile fornire una correzione
del codice che inserisce la parola chiave const:

const int x = 0;
Console.WriteLine(x);

L'utente la sceglie dall'interfaccia utente della lampadina dell'editor e Visual Studio modifica il codice.
Aprire il file MakeConstCodeFixProvider.cs aggiunto dal modello. Questa correzione del codice è già collegata
all'ID di diagnostica prodotto dall'analizzatore di diagnostica, ma non implementa ancora la trasformazione del
codice corretta. È innanzitutto necessario rimuovere parte del codice del modello. Modificare la stringa del titolo
in "Make constant":

private const string title = "Make constant";

Eliminare quindi il metodo MakeUppercaseAsync . Tale metodo non è più applicabile.


Tutti i provider di correzione del codice derivano da CodeFixProvider e sostituiscono
CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) per segnalare le correzioni del codice disponibili. In
RegisterCodeFixesAsync modificare il tipo di nodo predecessore in cui eseguire la ricerca in
LocalDeclarationStatementSyntax, in modo che corrisponda alla diagnostica:
var declaration =
root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>
().First();

Modificare quindi l'ultima riga per registrare una correzione del codice. La correzione creerà un nuovo
documento risultante dall'aggiunta del modificatore const a una dichiarazione esistente:

// Register a code action that will invoke the fix.


context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
equivalenceKey: title),
diagnostic);

Si noteranno delle sottolineature rosse ondulate nel codice appena aggiunto sul simbolo MakeConstAsync .
Aggiungere una dichiarazione per MakeConstAsync , come il codice seguente:

private async Task<Document> MakeConstAsync(Document document,


LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}

Il nuovo metodo MakeConstAsync trasforma l'oggetto Document che rappresenta il file di origine dell'utente in
un nuovo Document che ora contiene una dichiarazione const .
Creare un nuovo token di parola chiave const da inserire all'inizio dell'istruzione di dichiarazione. Prestare
attenzione a rimuovere eventuali elementi semplici iniziali dal primo token dell'istruzione di dichiarazione e
associarlo al token const . Aggiungere il codice seguente al metodo MakeConstAsync :

// Remove the leading trivia from the local declaration.


var firstToken = localDeclaration.GetFirstToken();
var leadingTrivia = firstToken.LeadingTrivia;
var trimmedLocal = localDeclaration.ReplaceToken(
firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the leading trivia.


var constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword,
SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));

Aggiungere quindi il token const alla dichiarazione mediante il codice seguente:

// Insert the const token into the modifiers list, creating a new modifiers list.
var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// Produce the new local declaration.
var newLocal = trimmedLocal
.WithModifiers(newModifiers)
.WithDeclaration(localDeclaration.Declaration);

Formattare la nuova dichiarazione in modo che corrisponda alle regole di formattazione di C#. La formattazione
delle modifiche in modo che corrispondano al codice esistente consente di migliorare l'esperienza. Aggiungere
l'istruzione seguente subito dopo il codice esistente:
// Add an annotation to format the new local declaration.
var formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);

È necessario un nuovo spazio dei nomi per il codice. Aggiungere la using direttiva seguente all'inizio del file:

using Microsoft.CodeAnalysis.Formatting;

Il passaggio finale consiste nell'apportare la modifica. Questo processo comprende tre passaggi:
1. Ottenere un handle per il documento esistente.
2. Creare un nuovo documento, sostituendo la dichiarazione esistente con la nuova dichiarazione.
3. Restituire il nuovo documento.
Aggiungere il codice seguente alla fine del metodo MakeConstAsync :

// Replace the old local declaration with the new local declaration.
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

// Return document with transformed tree.


return document.WithSyntaxRoot(newRoot);

A questo punto, è possibile provare la correzione del codice. Premere F5 per eseguire il progetto
dell'analizzatore in una seconda istanza di Visual Studio. Nella seconda istanza di Visual Studio creare un nuovo
progetto di applicazione console C# e aggiungere alcune dichiarazioni di variabili locali inizializzate con valori
costanti al metodo Main. Si noterà che vengono segnalate come avvisi, come indicato di seguito.

Sono stati compiuti notevoli progressi. Sono presenti linee ondulate sotto le dichiarazioni che possono essere
rese const . Ma c'è ancora qualche operazione da eseguire. Questa soluzione funziona se si aggiunge const a
dichiarazioni che iniziano con i , quindi con j e infine con k . Tuttavia, se si aggiunge il modificatore const in
un ordine diverso, a partire da k , l'analizzatore genera errori: k non può essere dichiarato const , a meno che
i e j non siano già entrambi const . È necessario eseguire altre analisi per poter gestire i diversi modi in cui
possono essere dichiarate e inizializzate le variabili.

Compilare test basati sui dati


L'analizzatore e la correzione del codice funzionano nel caso semplice di una singola dichiarazione che può
essere resa const. Vi sono numerose possibili istruzioni di dichiarazione in cui questa implementazione genera
errori. Questi casi possono essere gestiti usando la libreria di unit test scritta dal modello. È molto più veloce che
aprire ripetutamente una seconda copia di Visual Studio.
Aprire il file MakeConstUnitTests.cs nel progetto di unit test. Il modello ha creato due test che seguono i due
modelli comuni per gli unit test di un analizzatore e una correzione del codice. TestMethod1 illustra il modello
per un test che assicura l'analizzatore non segnali una diagnostica quando non deve. TestMethod2 illustra il
modello per la segnalazione di una diagnostica e l'esecuzione della correzione del codice.
Il codice per quasi ogni test dell'analizzatore segue uno di questi due modelli. Per il primo passaggio, è possibile
rielaborare questi test come test basati sui dati. Sarà quindi possibile creare facilmente nuovi test mediante
l'aggiunta di nuove costanti stringa per rappresentare diversi input di test.
Per motivi di efficienza, il primo passaggio consiste nel refactoring dei due test come test basati sui dati. Sarà
quindi sufficiente definire due costanti stringa per ogni nuovo test. Durante il refactoring, rinominare entrambi i
metodi in nomi migliori. Sostituire TestMethod1 con questo test, che assicura che non venga generata la
diagnostica:

[DataTestMethod]
[DataRow("")]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)
{
VerifyCSharpDiagnostic(testCode);
}

È possibile creare una nuova riga di dati per questo test definendo un frammento di codice che non deve
causare l'attivazione di un avviso di diagnostica. Questo overload di VerifyCSharpDiagnostic ha esito positivo
quando non viene attivata alcuna diagnostica per il frammento di codice sorgente.
Sostituire quindi TestMethod2 con questo test, che assicura che venga generata una diagnostica e applicata una
correzione del codice per il frammento di codice sorgente:

[DataTestMethod]
[DataRow(LocalIntCouldBeConstant, LocalIntCouldBeConstantFixed, 10, 13)]
public void WhenDiagnosticIsRaisedFixUpdatesCode(
string test,
string fixTest,
int line,
int column)
{
var expected = new DiagnosticResult
{
Id = MakeConstAnalyzer.DiagnosticId,
Message = new LocalizableResourceString(nameof(MakeConst.Resources.AnalyzerMessageFormat),
MakeConst.Resources.ResourceManager, typeof(MakeConst.Resources)).ToString(),
Severity = DiagnosticSeverity.Warning,
Locations =
new[] {
new DiagnosticResultLocation("Test0.cs", line, column)
}
};

VerifyCSharpDiagnostic(test, expected);

VerifyCSharpFix(test, fixTest);
}

Il codice precedente apporta anche due modifiche al codice che genera il risultato di diagnostica previsto. Usa le
costanti pubbliche registrate nell'analizzatore MakeConst . Usa inoltre due costanti stringa per l'origine di input e
fissa. Aggiungere le costanti stringa seguenti alla classe UnitTest :
private const string LocalIntCouldBeConstant = @"
using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0;
Console.WriteLine(i);
}
}
}";

private const string LocalIntCouldBeConstantFixed = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int i = 0;
Console.WriteLine(i);
}
}
}";

Eseguire i due test per assicurarsi che vengano superati. In Visual Studio aprire Esplora test selezionando Test
> Windows > Esplora test . Selezionare quindi il collegamento Esegui tutto .

Creare test per le dichiarazioni valide


Come regola generale, gli analizzatori devono essere chiusi appena possibile ed eseguire attività minime. Visual
Studio esegue chiamate agli analizzatori registrati mentre l'utente modifica il codice. La velocità di risposta è un
requisito essenziale. Esistono diversi casi di test per il codice che non devono generare la diagnostica.
L'analizzatore gestisce già uno di tali test, il caso in cui una variabile viene assegnata dopo l'inizializzazione.
Aggiungere la seguente costante stringa ai test per rappresentare tale caso:

private const string VariableAssigned = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0;
Console.WriteLine(i++);
}
}
}";

Aggiungere quindi una riga di dati per questo test, come illustrato nel frammento di codice seguente:
[DataTestMethod]
[DataRow(""),
DataRow(VariableAssigned)]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)

Anche questo test ha esito positivo. Aggiungere le costanti per le condizioni che non sono ancora state gestite:
Dichiarazioni che sono già const , perché sono già costanti:

private const string AlreadyConst = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int i = 0;
Console.WriteLine(i);
}
}
}";

Dichiarazioni senza alcun inizializzatore, perché non è presente alcun valore da usare:

private const string NoInitializer = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i;
i = 0;
Console.WriteLine(i);
}
}
}";

Dichiarazioni in cui l'inizializzatore non è una costante, perché non possono essere costanti in fase di
compilazione:

private const string InitializerNotConstant = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = DateTime.Now.DayOfYear;
Console.WriteLine(i);
}
}
}";
Può essere ancora più complicato perché C# consente più dichiarazioni come un'unica istruzione. Prendere in
considerazione la costante stringa di test case seguente:

private const string MultipleInitializers = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0, j = DateTime.Now.DayOfYear;
Console.WriteLine(i, j);
}
}
}";

La variabile i può essere resa costante, ma per la variabile j non è possibile. Questa istruzione non può
quindi essere resa una dichiarazione const. Aggiungere le dichiarazioni DataRow per tutti questi test:

[DataTestMethod]
[DataRow(""),
DataRow(VariableAssigned),
DataRow(AlreadyConst),
DataRow(NoInitializer),
DataRow(InitializerNotConstant),
DataRow(MultipleInitializers)]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)

Eseguendo nuovamente i test, si noterà che i nuovi test case hanno esito negativo.

Aggiornare l'analizzatore per ignorare le dichiarazioni corrette


Sono necessari alcuni miglioramenti del metodo AnalyzeNode dell'analizzatore per filtrare le code che
soddisfano queste condizioni. Sono tutte condizioni correlate, di conseguenza modifiche simili consentiranno di
correggere tutte le condizioni. Modificare AnalyzeNode nel modo seguente:
L'analisi semantica ha esaminato una singola dichiarazione di variabile. Questo codice deve essere inserito in
un ciclo foreach , che esamina tutte le variabili dichiarate nella stessa istruzione.
Ogni variabile dichiarata deve avere un inizializzatore.
L'inizializzatore di ogni variabile dichiarata deve essere una costante in fase di compilazione.
Nel metodo AnalyzeNode sostituire l'analisi semantica originale:

// Perform data flow analysis on the local declaration.


var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
var variable = localDeclaration.Declaration.Variables.Single();
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}

con il frammento di codice seguente:


// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (var variable in localDeclaration.Declaration.Variables)
{
var initializer = variable.Initializer;
if (initializer == null)
{
return;
}

var constantValue = context.SemanticModel.GetConstantValue(initializer.Value);


if (!constantValue.HasValue)
{
return;
}
}

// Perform data flow analysis on the local declaration.


var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

foreach (var variable in localDeclaration.Declaration.Variables)


{
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}
}

Il primo ciclo foreach esamina ogni dichiarazione di variabile tramite l'analisi sintattica. Il primo controllo
garantisce che la variabile disponga di un inizializzatore. Il secondo controllo garantisce che l'inizializzatore sia
una costante. Il secondo ciclo contiene l'analisi semantica originale. I controlli semantici sono in un ciclo distinto
perché ha un maggiore impatto sulle prestazioni. Eseguendo nuovamente i test, dovrebbero tutti avere esito
positivo.

Aggiungere le ultime modifiche


La procedura è quasi terminata. Esistono alcune altre condizioni che l'analizzatore deve gestire. Visual Studio
esegue chiamate agli analizzatori mentre l'utente scrive il codice. Spesso l'analizzatore viene chiamato per
codice che non viene compilato. Il metodo AnalyzeNode dell'analizzatore diagnostico non verifica se il valore
costante può essere convertito nel tipo variabile. Pertanto, l'implementazione corrente convertirà una
dichiarazione errata come int i = "abc"' in una costante locale. Aggiungere una costante stringa di origine per
tale condizione:

private const string DeclarationIsInvalid = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int x = ""abc"";
}
}
}";

Inoltre, i tipi di riferimento non sono gestiti correttamente. L'unico valore costante consentito per un tipo di
riferimento è , tranne che nel caso di System.String, che consente valori letterali stringa. In altre parole,
null
const string s = "abc" è consentito, mentre const object s = "abc" non lo è. Tale condizione viene verificata
da questo frammento di codice:

private const string ReferenceTypeIsntString = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
object s = ""abc"";
}
}
}";

Per completezza, è necessario aggiungere un altro test per assicurarsi che sia possibile creare una dichiarazione
di costante per una stringa. Il frammento di codice seguente definisce sia il codice che genera il messaggio di
diagnostica che il codice dopo l'applicazione della correzione:

private const string ConstantIsString = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
string s = ""abc"";
}
}
}";

private const string ConstantIsStringFixed = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const string s = ""abc"";
}
}
}";

Infine, se una variabile viene dichiarata con la parola chiave var , la correzione esegue l'operazione errata e
genera una dichiarazione const var , che non è supportata dal linguaggio C#. Per correggere questo bug, la
correzione del codice deve sostituire la parola chiave var con il nome del tipo dedotto:
private const string DeclarationUsesVar = @"
using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
var item = 4;
}
}
}";

private const string DeclarationUsesVarFixedHasType = @"


using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int item = 4;
}
}
}";
private const string StringDeclarationUsesVar = @"
using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
var item = ""abc"";
}
}
}";
private const string StringDeclarationUsesVarFixedHasType = @"
using System;

namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const string item = ""abc"";
}
}
}";

Queste modifiche aggiornano le dichiarazioni delle righe di dati per entrambi i test. Il codice seguente illustra
questi test con tutti gli attributi della riga di dati:
//No diagnostics expected to show up
[DataTestMethod]
[DataRow(""),
DataRow(VariableAssigned),
DataRow(AlreadyConst),
DataRow(NoInitializer),
DataRow(InitializerNotConstant),
DataRow(MultipleInitializers),
DataRow(DeclarationIsInvalid),
DataRow(ReferenceTypeIsntString)]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)
{
VerifyCSharpDiagnostic(testCode);
}

[DataTestMethod]
[DataRow(LocalIntCouldBeConstant, LocalIntCouldBeConstantFixed, 10, 13),
DataRow(ConstantIsString, ConstantIsStringFixed, 10, 13),
DataRow(DeclarationUsesVar, DeclarationUsesVarFixedHasType, 10, 13),
DataRow(StringDeclarationUsesVar, StringDeclarationUsesVarFixedHasType, 10, 13)]
public void WhenDiagosticIsRaisedFixUpdatesCode(
string test,
string fixTest,
int line,
int column)
{
var expected = new DiagnosticResult
{
Id = MakeConstAnalyzer.DiagnosticId,
Message = new LocalizableResourceString(nameof(MakeConst.Resources.AnalyzerMessageFormat),
MakeConst.Resources.ResourceManager, typeof(MakeConst.Resources)).ToString(),
Severity = DiagnosticSeverity.Warning,
Locations =
new[] {
new DiagnosticResultLocation("Test0.cs", line, column)
}
};

VerifyCSharpDiagnostic(test, expected);

VerifyCSharpFix(test, fixTest);
}

Fortunatamente, tutti i bug precedenti possono essere corretti con le stesse tecniche appena descritte.
Per correggere il primo bug, aprire DiagnosticAnalyzer.cs e individuare il ciclo foreach in cui viene controllato
ognuno degli inizializzatori della dichiarazione locale per garantire che gli vengano assegnati valori costanti.
Subito prima del primo ciclo foreach, chiamare context.SemanticModel.GetTypeInfo() per recuperare
informazioni dettagliate sul tipo dichiarato della dichiarazione locale:

var variableTypeName = localDeclaration.Declaration.Type;


var variableType = context.SemanticModel.GetTypeInfo(variableTypeName).ConvertedType;

Quindi, all'interno del ciclo foreach , controllare ogni inizializzatore per assicurarsi che possa essere convertito
nel tipo di variabile. Aggiungere il controllo seguente dopo aver verificato che l'inizializzatore sia una costante:
// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
var conversion = context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
return;
}

La modifica successiva si basa su quest'ultima. Prima della parentesi graffa di chiusura del primo ciclo foreach,
aggiungere il codice seguente per verificare il tipo di dichiarazione locale quando la costante è una stringa o
null.

// Special cases:
// * If the constant value is a string, the type of the local declaration
// must be System.String.
// * If the constant value is null, the type of the local declaration must
// be a reference type.
if (constantValue.Value is string)
{
if (variableType.SpecialType != SpecialType.System_String)
{
return;
}
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
return;
}

È necessario scrivere un altro codice nel provider per la correzione del codice per sostituire la var parola chiave
con il nome del tipo corretto. Tornare a CodeFixProvider.cs . Il codice che verrà aggiunto esegue i passaggi
seguenti:
Controllare se la dichiarazione è una dichiarazione var e in tal caso:
Creare un nuovo tipo per il tipo dedotto.
Assicurarsi che la dichiarazione del tipo non sia un alias. In tal caso, è consentito dichiarare const var .
Verificare che var non sia un nome di tipo in questo programma. In tal caso, const var è consentito.
Semplificare il nome completo del tipo
Può sembrare necessaria una notevole quantità di codice, ma non è così. Sostituire la riga che dichiara e
inizializza newLocal con il codice seguente. Deve essere inserita subito dopo l'inizializzazione di newModifiers :
// If the type of the declaration is 'var', create a new type name
// for the inferred type.
var variableDeclaration = localDeclaration.Declaration;
var variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);

// Special case: Ensure that 'var' isn't actually an alias to another type
// (e.g. using var = System.String).
var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
if (aliasInfo == null)
{
// Retrieve the type inferred for var.
var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

// Special case: Ensure that 'var' isn't actually a type named 'var'.
if (type.Name != "var")
{
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
var typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

// Add an annotation to simplify the type name.


var simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation);

// Replace the type in the variable declaration.


variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
}
}
}
// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers)
.WithDeclaration(variableDeclaration);

È necessario aggiungere una using direttiva per usare il Simplifier tipo:

using Microsoft.CodeAnalysis.Simplification;

Eseguire i test. Avranno tutti esito positivo. Per concludere, è possibile eseguire l'analizzatore completato.
Premere CTRL + F5 per eseguire il progetto dell'analizzatore in una seconda istanza di Visual Studio con
l'estensione di anteprima Roslyn caricata.
Nella seconda istanza di Visual Studio creare un nuovo progetto di applicazione console C# e aggiungere
int x = "abc"; al metodo Main. Grazie alla correzione del primo bug, non dovrebbe essere segnalato alcun
messaggio di avviso per questa dichiarazione di variabile locale (anche se si verifica un errore del
compilatore come previsto).
Aggiungere quindi object s = "abc"; al metodo Main. A causa della correzione del secondo bug, non
dovrebbe essere segnalato alcun avviso.
Infine, aggiungere un'altra variabile locale che usa la parola chiave var . Si noterà che viene segnalato un
avviso e viene visualizzato un suggerimento in basso a sinistra.
Spostare il punto di inserimento dell'editor sulla sottolineatura ondulata e premere CTRL + . per visualizzare
la correzione del codice suggerita. Quando si seleziona la correzione del codice, si noti che la var parola
chiave è ora gestita correttamente.
Infine, aggiungere il codice seguente:
int i = 2;
int j = 32;
int k = i + j;

Dopo queste modifiche, vengono visualizzate linee rosse ondulate solo per le prime due variabili. Aggiungere
const sia a i che a j . Verrà visualizzato un nuovo avviso per k , perché può ora essere const .

Congratulazioni! È stata creata la prima estensione .NET Compiler Platform che esegue l'analisi del codice in
tempo reale per rilevare un problema e fornisce una correzione rapida per l'errore. Nel corso di questo processo
sono state descritte molte delle API di codice che fanno parte di .NET Compiler Platform SDK (API Roslyn). È
possibile confrontare il proprio lavoro con l'esempio completato disponibile nel repository GitHub degli esempi.

Altre risorse
Introduzione all'analisi della sintassi
Introduzione all'analisi semantica
Guida per programmatori C#
02/11/2020 • 2 minutes to read • Edit Online

In questa sezione vengono fornite informazioni dettagliate sulle principali funzionalità del linguaggio C# e sulle
funzionalità accessibili a C# tramite .NET.
La maggior parte di questa sezione presuppone che siano già noti i concetti principali relativi a C# e alla
programmazione in generale. Se si è principianti con la programmazione o con C#, è possibile visitare
l'esercitazione Introduzione alle esercitazioni in c# o .NET nel browser, incui non è necessaria alcuna conoscenza
della programmazione precedente.
Per informazioni sulle parole chiave, gli operatori e le direttive per il preprocessore specifici, vedere riferimenti
per C#. Per informazioni sulla specifica del linguaggio C++, vedere Specifiche del linguaggio C#.

Sezioni relative al programma


All'interno di un programma C#
Main () e argomenti della riga di comando

Sezioni relative al linguaggio


Istruzioni, espressioni e operatori
Tipi
Classi e struct
Interfacce
Delegati
Matrici
Stringhe
Proprietà
Indicizzatori
Eventi
Generics
Iterators
Espressioni di query LINQ
Spazi dei nomi
Codice unsafe e puntatori
Commenti relativi alla documentazione XML

Sezioni relative alla piattaforma


Domini applicazione
Assembly in .NET
Attributes (Attributi)
raccolte
Eccezioni e gestione delle eccezioni
File system e Registro di sistema (Guida per programmatori C#)
Interoperabilità
Reflection

Vedere anche
Riferimenti per C#
Contenuto di un programma C#
02/11/2020 • 2 minutes to read • Edit Online

La sezione illustra la struttura generale di un programma C# e include l'esempio "Hello, World!" standard.

Contenuto della sezione


Hello World -- Il primo programma
Struttura generale di un programma C#
Nomi di identificatore
Convenzioni di codifica C#

Sezioni correlate
Introduzione a C#
Guida per programmatori C#
Riferimenti per C#
Esempi ed esercitazioni

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Hello World--il primo programma
02/11/2020 • 7 minutes to read • Edit Online

In questo articolo si userà Visual Studio per creare la tradizionale "Hello World!" tradizionale. Visual Studio è un
ambiente di sviluppo integrato (IDE) professionale con molte funzionalità progettate per lo sviluppo in .NET. Per
creare questo programma, verranno usate solo alcune delle funzionalità di Visual Studio. Per altre informazioni
su Visual Studio, vedere Introduzione con Visual C#.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Creare una nuova applicazione


Windows
macOS
Avviare Visual Studio. In Windows verrà visualizzata l'immagine seguente:

Selezionare Crea un nuovo progetto nell'angolo in basso a destra dell'immagine. Visual Studio Visualizza la
finestra di dialogo nuovo progetto :
NOTE
Se è la prima volta che si avvia Visual Studio, l'elenco dei modelli di progetto recenti è vuoto.

Nella finestra di dialogo nuovo progetto scegliere "app console (.NET Core)" e quindi fare clic su Avanti .
Assegnare un nome al progetto, ad esempio "HelloWorld", quindi fare clic su Crea .
Visual Studio apre il progetto. Si tratta già di una "Hello World!" di base. standard. Premere Ctrl + F5 per
eseguire il progetto. Visual Studio compila il progetto, convertendo il codice sorgente in un file eseguibile. Viene
quindi avviata una finestra di comando che esegue la nuova applicazione. Nella finestra dovrebbe essere
visualizzato il testo seguente:

Hello World!

C:\Program Files\dotnet\dotnet.exe (process 11964) exited with code 0.


Press any key to close this window . . .

Premere un tasto per chiudere la finestra.

Elementi di un programma C#
Esaminiamo le parti importanti di questo programma. La prima riga contiene un commento. I caratteri //
convertono il resto della riga in un commento.

// A Hello World! program in C#.

È anche possibile commentare un blocco di testo racchiudendolo tra i caratteri /* e */ , come illustrato
nell'esempio seguente.
/* A "Hello World!" program in C#.
This program displays the string "Hello World!" on the screen. */

È necessario che un'applicazione console C# contenga un metodo Main , in cui il controllo inizia e finisce. Il
metodo Main consente di creare oggetti e di eseguire altri metodi.
Il metodo Main è un metodo di tipo static che si trova all'interno di una classe o di uno struct. Nell'esempio
"Hello World!" precedente si trovava in una classe denominata Hello . È possibile dichiarare il metodo Main in
uno dei modi seguenti:
Può restituire un valore void . Ciò significa che il programma non restituisce un valore.

static void Main()


{
//...
}

Può restituire anche un valore intero. Il valore integer è il codice di uscita per l'applicazione.

static int Main()


{
//...
return 0;
}

Con entrambi i tipi restituiti, può accettare argomenti.

static void Main(string[] args)


{
//...
}

-oppure-

static int Main(string[] args)


{
//...
return 0;
}

Il parametro del metodo Main , args , è una matrice string che contiene gli argomenti della riga di comando
usati per richiamare il programma.
Per ulteriori informazioni sull'utilizzo degli argomenti della riga di comando, vedere gli esempi in Main () e
argomenti della riga di comando.

Input e output
I programmi C# usano in genere i servizi di input/output forniti dalla libreria di runtime di .NET. L'istruzione
System.Console.WriteLine("Hello World!"); usa il metodo WriteLine. Questo è uno dei metodi di output della
classe Console nella libreria di runtime. Visualizza il parametro di tipo stringa sul flusso di output standard
seguito da una nuova riga. Sono disponibili altri metodi Console per diverse operazioni di input e output. Se si
include la direttiva using System; all'inizio del programma, è possibile usare direttamente le classi e i metodi
System senza specificarne il nome completo. Ad esempio, è possibile chiamare Console.WriteLine anziché
System.Console.WriteLine :

using System;

Console.WriteLine("Hello World!");

Per altre informazioni sui metodi di input/output, vedere System.IO.

Vedere anche
Guida per programmatori C#
Esempi ed esercitazioni
Main () e argomenti della riga di comando
Guida introduttiva a Visual C#
Struttura generale di un programma C# (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

I programmi C# possono essere costituiti da uno o più file. Ciascun file può contenere zero o più spazi dei nomi.
Uno spazio dei nomi può contenere tipi, quali classi, struct, interfacce, enumerazioni e delegati. Di seguito viene
illustrata la struttura di base di un programma C# che contiene tutti questi elementi.

// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}

struct YourStruct
{
}

interface IYourInterface
{
}

delegate int YourDelegate();

enum YourEnum
{
}

namespace YourNestedNamespace
{
struct YourStruct
{
}
}

class YourMainClass
{
static void Main(string[] args)
{
//Your program starts here...
}
}
}

Sezioni correlate
Per altre informazioni:
Classi
Struct
Namespaces (Spazi dei nomi)
Interfacce
Delegati

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
All'interno di un programma C#
Riferimenti per C#
Nomi di identificatore
18/03/2020 • 2 minutes to read • Edit Online

Un identificatore è il nome assegnato a un tipo (classe, interfaccia, struct, delegato o enumerazione), un


membro, una variabile o uno spazio dei nomi. Per essere validi gli identificatori devono rispettare le regole
seguenti:
Gli identificatori devono iniziare con una lettera o con _ .
Gli identificatori possono contenere caratteri alfabetici Unicode, cifre decimali, caratteri di connessione
Unicode, caratteri combinati Unicode o caratteri di formattazione Unicode. Per altre informazioni sulle
categorie Unicode, vedere Unicode Category Database (Database categorie Unicode). È possibile dichiarare
gli identificatori che corrispondono a parole chiave C# usando il prefisso @ nell'identificatore. @ non fa
parte del nome dell'identificatore. Ad esempio, @if dichiara un identificatore denominato if . Questi
identificatori verbatim servono principalmente per garantire l'interoperabilità con gli identificatori dichiarati
in altri linguaggi.
Per una definizione completa degli identificatori validi, vedere l'argomento sugli identificatori nelle specifiche del
linguaggio C#.

Convenzioni di denominazione
Oltre alle regole, esiste una serie di convenzioni di denominazione per gli identificatori, seguite in tutte le API
.NET. Per convenzione, i programmi C# usano PascalCase per nomi di tipo, spazi dei nomi e tutti i membri
pubblici. Sono anche comuni le convenzioni seguenti:
I nomi di interfaccia iniziano con un carattere I maiuscolo.
I tipi di attributo terminano con la parola Attribute .
I tipi enumerazione usano un sostantivo singolare per non flag e uno plurale per flag.
Gli identificatori non devono contenere due caratteri _ consecutivi. Tali nomi sono riservati per gli
identificatori generati dal compilatore.

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Contenuto di un programma C#
Guida di riferimento a C
Classi
Tipi di struttura
Spazi dei nomi
Interfacce
Delegati
Convenzioni di codifica C# (Guida per
programmatori C#)
02/11/2020 • 14 minutes to read • Edit Online

Le convenzioni di codifica hanno gli scopi seguenti:


Creano un aspetto coerente per il codice in modo che chi legge possa concentrarsi sul contenuto, non sul
layout.
Consentono a chi legge di comprendere il codice più rapidamente, formulando presupposti basati sulle
esperienze precedenti.
Facilitano la copia, la modifica e la gestione del codice.
Illustrano procedure consigliate di C#.
Le linee guida riportate in questo articolo sono utilizzate da Microsoft per sviluppare esempi e documentazione.

Convenzioni di denominazione
Negli esempi brevi che non includono direttive using, usare qualifiche dello spazio dei nomi. Se si è certi
che uno spazio dei nomi viene importato per impostazione predefinita in un progetto, non è necessario
specificare in modo completo i nomi da tale spazio dei nomi. I nomi completi possono essere interrotti
dopo un punto (.) se sono troppo lunghi per una singola riga, come illustrato nell'esempio seguente.

var currentPerformanceCounterCategory = new System.Diagnostics.


PerformanceCounterCategory();

Non è necessario modificare i nomi degli oggetti creati usando gli strumenti di progettazione di Visual
Studio per adattarli ad altre linee guida.

Convenzioni di layout
Un layout appropriato usa la formattazione per mettere in evidenza la struttura del codice e per facilitare la
lettura del codice. Gli esempi Microsoft sono conformi alle convenzioni seguenti:
Usare le impostazioni dell'Editor di codice predefinite (rientri intelligenti, rientri di quattro caratteri,
tabulazioni salvate come spazi). Per altre informazioni, vedere Opzioni, Editor di testo, C#, Formattazione.
Scrivere una sola istruzione per riga.
Scrivere una sola dichiarazione per riga.
Se le righe di continuazione non sono rientrate automaticamente, impostare un rientro con un punto di
tabulazione (quattro spazi).
Aggiungere almeno una riga vuota tra le definizioni di metodo e proprietà.
Usare le parentesi per rendere visibili le clausole in un'espressione, come illustrato nel codice seguente.
if ((val1 > val2) && (val1 > val3))
{
// Take appropriate action.
}

Convenzioni relative ai commenti


Posizionare il commento su una riga separata, non alla fine di una riga di codice.
Iniziare il commento con una lettera maiuscola.
Terminare il commento con un punto finale.
Inserire uno spazio tra i delimitatori di commento (//) e il testo del commento, come illustrato
nell'esempio seguente.

// The following declaration creates a query. It does not run


// the query.

Non creare blocchi formattati di asterischi intorno ai commenti.

Linee guida della lingua


Nelle sezioni seguenti vengono descritte le procedure che il team C# deve seguire per preparare campioni ed
esempi di codice.
Tipo di dati String
Usare l'interpolazione di stringhe per concatenare stringhe brevi, come illustrato nel codice seguente.

string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";

Per accodare stringhe nei cicli, specialmente quando si lavora con grandi quantità di testo, usare un
oggetto StringBuilder.

var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";


var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);

Variabili locali tipizzate in modo implicito


Usare la tipizzazione implicita per le variabili locali quando il tipo della variabile è ovvio dal lato destro
dell'assegnazione o il tipo preciso non è importante.

// When the type of a variable is clear from the context, use var
// in the declaration.
var var1 = "This is clearly a string.";
var var2 = 27;

Non usare var quando il tipo non è evidente dal lato destro dell'assegnazione.
// When the type of a variable is not clear from the context, use an
// explicit type. You generally don't assume the type clear from a method name.
// A variable type is considered clear if it's a new operator or an explicit cast.
int var3 = Convert.ToInt32(Console.ReadLine());
int var4 = ExampleClass.ResultSoFar();

Non basarsi sul nome della variabile per specificare il tipo della variabile. Potrebbe non essere corretto.

// Naming the following variable inputInt is misleading.


// It is a string.
var inputInt = Console.ReadLine();
Console.WriteLine(inputInt);

Evitare l'uso di var al posto di dynamic.


Usare la tipizzazione implicita per determinare il tipo della variabile del ciclo nei cicli for .
Nell'esempio seguente viene usata la tipizzazione implicita in un'istruzione for .

var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";


var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);

Non usare la tipizzazione implicita per determinare il tipo della variabile del ciclo nei cicli foreach .
Nell'esempio seguente viene usata la tipizzazione esplicita in un' foreach istruzione.

foreach (char ch in laugh)


{
if (ch == 'h')
Console.Write("H");
else
Console.Write(ch);
}
Console.WriteLine();

NOTE
Prestare attenzione a non modificare accidentalmente un tipo di elemento della raccolta iterable. Ad esempio, è
facile passare da System.Linq.IQueryable a System.Collections.IEnumerable in un' foreach istruzione, che
modifica l'esecuzione di una query.

Tipi di dati non firmati


In generale, usare int anziché tipi non firmati. L'utilizzo di int è comune in C# ed è più facile interagire con
altre librerie, quando si usa int .
Matrici
Usare la sintassi concisa quando si inizializzano le matrici nella riga della dichiarazione.
// Preferred syntax. Note that you cannot use var here instead of string[].
string[] vowels1 = { "a", "e", "i", "o", "u" };

// If you use explicit instantiation, you can use var.


var vowels2 = new string[] { "a", "e", "i", "o", "u" };

// If you specify an array size, you must initialize the elements one at a time.
var vowels3 = new string[5];
vowels3[0] = "a";
vowels3[1] = "e";
// And so on.

Delegati
Usare la sintassi concisa per creare istanze di un tipo delegato.

// First, in class Program, define the delegate type and a method that
// has a matching signature.

// Define the type.


public delegate void Del(string message);

// Define a method that has a matching signature.


public static void DelMethod(string str)
{
Console.WriteLine("DelMethod argument: {0}", str);
}

// In the Main method, create an instance of Del.

// Preferred: Create an instance of Del by using condensed syntax.


Del exampleDel2 = DelMethod;

// The following declaration uses the full syntax.


Del exampleDel1 = new Del(DelMethod);

Istruzioni try-catch e using nella gestione delle eccezioni


Usare un'istruzione try-catch per la gestione della maggior parte delle eccezioni.

static string GetValueFromArray(string[] array, int index)


{
try
{
return array[index];
}
catch (System.IndexOutOfRangeException ex)
{
Console.WriteLine("Index is out of range: {0}", index);
throw;
}
}

Semplificare il codice usando l'istruzione using di C#. Se si ha un'istruzione try-finally in cui l'unico codice
nel blocco finally è una chiamata al metodo Dispose, usare invece un'istruzione using .
// This try-finally statement only calls Dispose in the finally block.
Font font1 = new Font("Arial", 10.0f);
try
{
byte charset = font1.GdiCharSet;
}
finally
{
if (font1 != null)
{
((IDisposable)font1).Dispose();
}
}

// You can do the same thing with a using statement.


using (Font font2 = new Font("Arial", 10.0f))
{
byte charset = font2.GdiCharSet;
}

Operatori && e ||
Per evitare eccezioni e migliorare le prestazioni ignorando i confronti non necessari, utilizzare && invece di & e
|| anziché | quando si eseguono confronti, come illustrato nell'esempio seguente.

Console.Write("Enter a dividend: ");


var dividend = Convert.ToInt32(Console.ReadLine());

Console.Write("Enter a divisor: ");


var divisor = Convert.ToInt32(Console.ReadLine());

// If the divisor is 0, the second clause in the following condition


// causes a run-time error. The && operator short circuits when the
// first expression is false. That is, it does not evaluate the
// second expression. The & operator evaluates both, and causes
// a run-time error when divisor is 0.
if ((divisor != 0) && (dividend / divisor > 0))
{
Console.WriteLine("Quotient: {0}", dividend / divisor);
}
else
{
Console.WriteLine("Attempted division by 0 ends up here.");
}

Operatore New
Usare il modulo conciso della creazione dell'istanza di oggetto, con la tipizzazione implicita, come
illustrato nella dichiarazione seguente.

var instance1 = new ExampleClass();

La riga precedente è equivalente alla dichiarazione seguente.

ExampleClass instance2 = new ExampleClass();

Usare gli inizializzatori di oggetto per semplificare la creazione di un oggetto.


// Object initializer.
var instance3 = new ExampleClass { Name = "Desktop", ID = 37414,
Location = "Redmond", Age = 2.3 };

// Default constructor and assignment statements.


var instance4 = new ExampleClass();
instance4.Name = "Desktop";
instance4.ID = 37414;
instance4.Location = "Redmond";
instance4.Age = 2.3;

Gestione di eventi
Se si definisce un gestore eventi che non è necessario rimuovere successivamente, usare un'espressione
lambda.

public Form2()
{
// You can use a lambda expression to define an event handler.
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}

// Using a lambda expression shortens the following traditional definition.


public Form1()
{
this.Click += new EventHandler(Form1_Click);
}

void Form1_Click(object sender, EventArgs e)


{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}

Membri static
Chiamare i membri static usando il nome della classe: ClassName.StaticMember. Questa pratica rende più
leggibile il codice semplificando l'accesso statico. Non qualificare un membro statico definito in una classe base
con il nome di una classe derivata. Nonostante il codice venga compilato, la leggibilità del codice è fuorviante
mentre il codice potrebbe essere interrotto in futuro, se si aggiunge un membro statico con lo stesso nome alla
classe derivata.
Query LINQ
Usare nomi significativi per le variabili di query. Nell'esempio seguente viene usato seattleCustomers per
i clienti che si trovano a Seattle.

var seattleCustomers = from customer in customers


where customer.City == "Seattle"
select customer.Name;

Usare gli alias per assicurarsi che i nomi delle proprietà di tipi anonimi siano scritti correttamente in
maiuscolo, usando la convenzione Pascal.
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { Customer = customer, Distributor = distributor };

Rinominare le proprietà quando i nomi delle proprietà nel risultato potrebbero risultare ambigui. Ad
esempio, se la query restituisce un nome cliente un ID del server di distribuzione, anziché lasciarli come
Name e ID nei risultati, rinominarli per spiegare che Name è il nome di un cliente e ID è l'ID di un
server di distribuzione.

var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { CustomerName = customer.Name, DistributorID = distributor.ID };

Usare la tipizzazione implicita nella dichiarazione di variabili di query e variabili di intervallo.

var seattleCustomers = from customer in customers


where customer.City == "Seattle"
select customer.Name;

Allineare le clausole di query sotto la clausola from, come illustrato negli esempi precedenti.
Usare le clausole where prima delle altre clausole di query, per garantire che le clausole di query
successive agiscano su un set di dati ridotto e filtrato.

var seattleCustomers2 = from customer in customers


where customer.City == "Seattle"
orderby customer.Name
select customer;

Usare più clausole from invece di una clausola join per accedere a raccolte interne. Ad esempio, ogni
raccolta di oggetti Student potrebbe contenere una raccolta di punteggi del test. Quando viene eseguita
la query seguente, viene restituito ogni punteggio superiore a 90, e il cognome dello studente che ha
ricevuto il punteggio.

// Use a compound from to access the inner sequence within each element.
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };

Sicurezza
Seguire le indicazioni in Linee guida per la generazione di codice sicuro.

Vedere anche
Convenzioni di codifica di Visual Basic
Linee guida per la generazione di codice sicuro
Main() e argomenti della riga di comando (Guida
per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Il metodo Main è il punto di ingresso di un'applicazione C#. (Le librerie e i servizi non richiedono un Main
metodo come punto di ingresso). Quando l'applicazione viene avviata, il Main metodo è il primo metodo
richiamato.
In un programma C# può essere presente un solo punto di ingresso. Se si dispone di più di una classe che
dispone di un Main metodo, è necessario compilare il programma con l' -main opzione del compilatore per
specificare il Main metodo da usare come punto di ingresso. Per ulteriori informazioni, vedere -Main (opzioni
del compilatore C#).

class TestClass
{
static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}

Panoramica
Il metodo Main è il punto di ingresso di un programma eseguibile, ovvero il punto in cui il controllo del
programma inizia e termina.
Main viene dichiarato in una classe o in un tipo struct. Main deve essere static e non public. Nell'esempio
precedente riceve l'accesso predefinito di private. La classe o lo struct contenitore non deve essere statico.
Il tipo restituito da Main può essere void , int o, a partire da C# 7.1, Task o Task<int> .
Se e solo se Main restituisce Task o Task<int> , la dichiarazione di Main può includere il async
modificatore. Si noti che questo esclude specificamente un metodo async void Main .
Il metodo Main può essere dichiarato con o senza un parametro string[] contenente argomenti della riga
di comando. Quando si usa Visual Studio per creare applicazioni Windows, è possibile aggiungere il
parametro manualmente oppure usare il GetCommandLineArgs() metodo per ottenere gli argomenti della
riga di comando. I parametri vengono letti come argomenti della riga di comando a indice zero.
Diversamente da C e C++, il nome del programma non viene trattato come primo argomento della riga di
comando nella args matrice, ma è il primo elemento del GetCommandLineArgs() metodo.
Di seguito è riportato un elenco di Main firme valide:

public static void Main() { }


public static int Main() { }
public static void Main(string[] args) { }
public static int Main(string[] args) { }
public static async Task Main() { }
public static async Task<int> Main() { }
public static async Task Main(string[] args) { }
public static async Task<int> Main(string[] args) { }

Negli esempi precedenti viene usato il modificatore della funzione di accesso public. Tipico, ma non
obbligatorio.
L'aggiunta dei tipi restituiti async , Task e Task<int> semplifica il codice del programma quando è necessario
avviare le applicazioni console e per operazioni asincrone await in Main .

Specifiche del linguaggio C#


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

Vedere anche
Compilazione dalla riga di comando con csc.exe
Guida per programmatori C#
Metodi
All'interno di un programma C#
Argomenti della riga di comando (Guida per
programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

È possibile inviare argomenti al metodo Main definendo il metodo in uno dei modi seguenti:

static int Main(string[] args)

static void Main(string[] args)

NOTE
Per abilitare gli argomenti della riga di comando nel Main metodo in una Windows Forms Application, è necessario
modificare manualmente la firma di Main in Program.cs. Il codice generato da Progettazione Windows Form crea un
Main senza un parametro di input. È anche possibile usare Environment.CommandLine o
Environment.GetCommandLineArgs per accedere agli argomenti della riga di comando da qualsiasi punto in
un'applicazione console o Windows.

Il parametro del metodo Main è una matrice String che rappresenta gli argomenti della riga di comando. In
genere si determina se gli argomenti esistono eseguendo il test della proprietà Length , ad esempio:

if (args.Length == 0)
{
System.Console.WriteLine("Please enter a numeric argument.");
return 1;
}

TIP
La args matrice non può essere null. Quindi, è sicuro accedere alla Length proprietà senza il controllo null.

È anche possibile convertire gli argomenti stringa in tipi numerici con la classe Convert o il metodo Parse . Ad
esempio, l'istruzione seguente converte string in un numero long con il metodo Parse:

long num = Int64.Parse(args[0]);

È anche possibile usare il tipo C# long , che fa da alias per Int64 :

long num = long.Parse(args[0]);

Nella classe Convert il metodo ToInt64 consente di eseguire la stessa operazione:

long num = Convert.ToInt64(s);


Per altre informazioni, vedere Parse e Convert.

Esempio
L'esempio seguente illustra come usare gli argomenti della riga di comando in un'applicazione console.
L'applicazione accetta un argomento in fase di esecuzione, lo converte in un numero intero e calcola il fattoriale
del numero. Se non viene specificato nessun argomento, l'applicazione produce un messaggio che descrive
l'uso corretto del programma.
Per compilare ed eseguire l'applicazione al prompt dei comandi, seguire questa procedura:
1. Incollare il codice seguente in qualsiasi editor di testo, quindi salvare il file come file di testo con il nome
Factorial.cs.
// Add a using directive for System if the directive isn't already present.

public class Functions


{
public static long Factorial(int n)
{
// Test for invalid input.
if ((n < 0) || (n > 20))
{
return -1;
}

// Calculate the factorial iteratively rather than recursively.


long tempResult = 1;
for (int i = 1; i <= n; i++)
{
tempResult *= i;
}
return tempResult;
}
}

class MainClass
{
static int Main(string[] args)
{
// Test if input arguments were supplied.
if (args.Length == 0)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}

// Try to convert the input arguments to numbers. This will throw


// an exception if the argument is not a number.
// num = int.Parse(args[0]);
int num;
bool test = int.TryParse(args[0], out num);
if (!test)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}

// Calculate factorial.
long result = Functions.Factorial(num);

// Print result.
if (result == -1)
Console.WriteLine("Input must be >= 0 and <= 20.");
else
Console.WriteLine($"The Factorial of {num} is {result}.");

return 0;
}
}
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.

2. Dalla schermata Star t o dal menu Star t , aprire una finestra Prompt dei comandi per gli
sviluppatori di Visual Studio e selezionare la cartella contenente il file appena creato.
3. Immettere il seguente comando per compilare l'applicazione.
csc Factorial.cs

Se l'applicazione non presenta errori di compilazione, viene creato un file eseguibile denominato
Factorial.exe .
4. Immettere il comando seguente per calcolare il fattoriale di 3:
Factorial 3

5. Il comando produce il seguente output: The factorial of 3 is 6.

NOTE
Quando si esegue un'applicazione in Visual Studio, è possibile specificare argomenti della riga di comando nella Pagina
Debug, Progettazione progetti.

Vedere anche
System.Environment
Guida per programmatori C#
Main () e argomenti della riga di comando
Come visualizzare gli argomenti della riga di comando
Valori restituiti da Main()
Classi
Come visualizzare gli argomenti della riga di
comando (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Gli argomenti specificati per un file eseguibile nella riga di comando sono accessibili tramite un parametro
facoltativo per Main . Gli argomenti vengono specificati sotto forma di una matrice di stringhe. Ogni elemento
della matrice contiene un solo argomento. Gli spazi vuoti tra gli argomenti vengono rimossi. Si considerino ad
esempio le chiamate seguenti della riga di comando di un file eseguibile fittizio:

IN P UT N EL L A RIGA DI C O M A N DO M AT RIC E DI ST RIN GH E PA SSAT E A M A IN

executable.exe a b c "a"

"b"

"c"

executable.exe one two "one"

"two"

executable.exe "one two" three "one two"

"three"

NOTE
Quando si esegue un'applicazione in Visual Studio, è possibile specificare argomenti della riga di comando nella Pagina
Debug, Progettazione progetti.

Esempio
In questo esempio vengono visualizzati gli argomenti della riga di comando passati a un'applicazione della riga
di comando. L'output visualizzato è per la prima voce nella tabella precedente.
class CommandLine
{
static void Main(string[] args)
{
// The Length property provides the number of array elements.
Console.WriteLine($"parameter count = {args.Length}");

for (int i = 0; i < args.Length; i++)


{
Console.WriteLine($"Arg[{i}] = [{args[i]}]");
}
}
}
/* Output (assumes 3 cmd line args):
parameter count = 3
Arg[0] = [a]
Arg[1] = [b]
Arg[2] = [c]
*/

Vedere anche
Guida per programmatori C#
Compilazione dalla riga di comando con csc.exe
Main () e argomenti della riga di comando
Valori restituiti da Main()
Valori restituiti da Main() (Guida per programmatori
C#)
02/11/2020 • 5 minutes to read • Edit Online

Il metodo Main può restituire void :

static void Main()


{
//...
}

Può anche restituire int :

static int Main()


{
//...
return 0;
}

Se il valore restituito da Main non viene usato, la restituzione di void consente di semplificare leggermente il
codice. Tuttavia, la restituzione di un valore intero consente al programma di comunicare le informazioni sullo
stato ad altri programmi o script che richiamano il file eseguibile. Il valore restituito da Main viene considerato
come codice di uscita per il processo. Se void viene restituito da Main , il codice di uscita sarà implicitamente
0 . L'esempio seguente illustra come è possibile accedere al valore restituito da Main .

Esempio
In questo esempio vengono usati gli strumenti da riga di comando di .NET Core . Se non si ha familiarità con gli
strumenti da riga di comando di .NET Core, è possibile ottenere informazioni su di essi in questo articolo
introduttivo.
Convertire il metodo Main in program.cs come indicato di seguito:

// Save this program as MainReturnValTest.cs.


class MainReturnValTest
{
static int Main()
{
//...
return 0;
}
}

Quando si esegue un programma in ambiente Windows, qualsiasi valore restituito dalla funzione Main viene
archiviato in una variabile di ambiente. Questa variabile di ambiente può essere recuperata usando ERRORLEVEL
da un file batch o $LastExitCode da PowerShell.
È possibile compilare l'applicazione usando il comando dotnet CLI dotnet build .
Successivamente, creare uno script di PowerShell per eseguire l'applicazione e visualizzare il risultato. Incollare il
codice seguente in un file di testo e salvarlo come test.ps1 nella cartella che contiene il progetto. Eseguire lo
script di PowerShell digitando test.ps1 al prompt di PowerShell.
Poiché il codice restituisce zero, il file batch indicherà un esito positivo. Tuttavia, se si modifica
MainReturnValTest.cs per restituire un valore diverso da zero e quindi si ricompila il programma, l'esecuzione
successiva dello script di PowerShell segnalerà un errore.

dotnet run

if ($LastExitCode -eq 0) {
Write-Host "Execution succeeded"
} else
{
Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode

Output di esempio
Execution succeeded
Return value = 0

Valori restituiti da Async Main


I valori restituiti da Async Main trasferiscono il codice boilerplate necessario per la chiamata di metodi asincroni
in Main a codice generato dal compilatore. In precedenza era necessario scrivere questo costrutto per chiamare
codice asincrono e verificare che il programma rimanesse in esecuzione fino al completamento dell'operazione
asincrona:

public static void Main()


{
AsyncConsoleWork().GetAwaiter().GetResult();
}

private static async Task<int> AsyncConsoleWork()


{
// Main body here
return 0;
}

Ora è possibile usare la sintassi seguente:

static async Task<int> Main(string[] args)


{
return await AsyncConsoleWork();
}

Il vantaggio della nuova sintassi è che il compilatore genera sempre il codice corretto.

Codice generato dal compilatore


Quando il punto di ingresso dell'applicazione restituisce Task o Task<int> il compilatore genera un nuovo
punto di ingresso che chiama il metodo del punto di ingresso dichiarato nel codice dell'applicazione.
Supponendo che questo punto di ingresso sia denominato $GeneratedMain , il compilatore genera il codice
seguente per questi punti di ingresso:
static Task Main() : il compilatore produce l'equivalente di
private static void $GeneratedMain() => Main().GetAwaiter().GetResult();
static Task Main(string[]) : il compilatore produce l'equivalente di
private static void $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();
static Task<int> Main() : il compilatore produce l'equivalente di
private static int $GeneratedMain() => Main().GetAwaiter().GetResult();
static Task<int> Main(string[]) : il compilatore produce l'equivalente di
private static int $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();

NOTE
Se negli esempi si fosse usato il modificatore async sul metodo Main il compilatore avrebbe generato lo stesso codice.

Vedi anche
Guida per programmatori C#
Riferimenti per C#
Main () e argomenti della riga di comando
Come visualizzare gli argomenti della riga di comando
Concetti di programmazione (C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa sezione illustra i concetti della programmazione in linguaggio C#.

Contenuto della sezione


T ITO LO DESC RIZ IO N E

Assembly in .NET Viene descritto come creare e usare gli assembly.

Programmazione asincrona con async e await (C#) Viene illustrato come creare una soluzione asincrona usando
le parole chiave async e await in C#. È inclusa una procedura
dettagliata.

Attributi (C#) Viene spiegato come offrire informazioni aggiuntive su


elementi di programmazione quali tipi, campi, metodi e
proprietà usando gli attributi.

Raccolte (C#) Vengono descritti alcuni dei tipi di raccolte disponibili in .NET.
Viene mostrato come usare raccolte semplici e raccolte di
coppie chiave/valore.

Covarianza e controvarianza (C#) Viene mostrato come abilitare la conversione implicita di


parametri di tipo generico in interfacce e delegati.

Alberi delle espressioni (C#) Viene illustrato come è possibile usare gli alberi delle
espressioni per abilitare la modifica dinamica del codice
eseguibile.

Iteratori (C#) Vengono descritti gli iteratori, che consentono di scorrere le


raccolte e restituire gli elementi uno alla volta.

LINQ (Language-Integrated Query) (C#) Vengono discusse le potenti funzionalità di query della
sintassi del linguaggio C# e viene descritto il modello per
l'esecuzione di query nei database relazionali, nei documenti
XML, nei set di dati e nelle raccolte in memoria.

Reflection (C#) Viene illustrato in che modo è possibile usare la reflection


per creare dinamicamente un'istanza di un tipo, associare il
tipo a un oggetto esistente oppure ottenere il tipo da un
oggetto esistente e richiamarne i metodi o accedere ai
relativi campi e proprietà.

Serializzazione (C#) Vengono descritti i concetti chiave della serializzazione


binaria, XML e SOAP.

Sezioni correlate
Suggerimenti sulle prestazioni Vengono discusse diverse regole base che consentono di
migliorare le prestazioni dell'applicazione.
Programmazione asincrona con async e await
02/11/2020 • 22 minutes to read • Edit Online

Il modello di programmazione asincrona (TAP) dell'attività fornisce un'astrazione sul codice asincrono. Scrivere il
codice come sequenza di istruzioni secondo la normale procedura. È possibile leggere il codice come se ogni
istruzione venisse completata prima che venga iniziata quella successiva. Il compilatore esegue una serie di
trasformazioni poiché alcune delle istruzioni potrebbero essere eseguite e restituire Task che rappresenta il
lavoro in corso.
L'obiettivo di questa sintassi consiste nell'abilitare un codice che viene letto come una sequenza di istruzioni ma
viene eseguito in un ordine più complesso in base all'allocazione delle risorse esterne e al completamento
dell'attività. Si tratta di un funzionamento analogo a quello in cui gli utenti specificano istruzioni per i processi
che includono attività asincrone. In questo articolo si userà un esempio di istruzioni per la creazione di una
colazione per vedere come le async await parole chiave e semplificano la motivazione del codice, che include
una serie di istruzioni asincrone. Si procederà a scrivere istruzioni come quelle dell'elenco seguente per
descrivere come preparare una colazione:
1. Versare una tazza di caffè.
2. Scaldare una padella e friggere due uova.
3. Friggere tre fette di pancetta.
4. Tostare due fette di pane.
5. Aggiungere burro e marmellata alla fetta di pane tostata.
6. Versare un bicchiere di succo d'arancia.
Se si ha esperienza in cucina, queste istruzioni verranno eseguite in modo asincrono . Si inizierà a scaldare la
padella per le uova e si inizierà a cuocere la pancetta. Si inserirà il pane nel tostapane, quindi si inizieranno a
cuocere le uova. A ogni passaggio del processo si inizia un'attività, quindi ci si dedica alle attività che man mano
richiedono attenzione.
La preparazione della colazione è un buon esempio di lavoro asincrono non parallelo. Tutte le attività possono
essere gestite da una sola persona (o thread). Continuando con l'analogia della colazione, una sola persona può
preparare la colazione in modo asincrono iniziando l'attività successiva prima che l'attività precedente venga
completata. La preparazione procede indipendentemente dal fatto che venga controllata da qualcuno. Non
appena si inizia a scaldare la padella per le uova, è possibile iniziare a friggere la pancetta. Dopo aver iniziato a
cuocere la pancetta, è possibile inserire il pane nel tostapane.
In un algoritmo parallelo sarebbero necessari più cuochi (o thread). Un cuoco cucinerebbe le uova, un cuoco
cucinerebbe la pancetta e così via. Ogni cuoco si dedicherebbe a una singola attività. Ogni cuoco (o thread)
verrebbe bloccato in modo sincrono in attesa che la pancetta sia pronta per essere girata o che la tostatura del
pane venga completata.
A questo punto, prendere in esame le stesse istruzioni scritte sotto forma di istruzioni C#:

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
class Program
{
static void Main(string[] args)
{
Coffee cup = PourCoffee();
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Egg eggs = FryEggs(2);


Console.WriteLine("eggs are ready");

Bacon bacon = FryBacon(3);


Console.WriteLine("bacon is ready");

Toast toast = ToastBread(2);


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

private static Juice PourOJ()


{
Console.WriteLine("Pouring orange juice");
return new Juice();
}

private static void ApplyJam(Toast toast) =>


Console.WriteLine("Putting jam on the toast");

private static void ApplyButter(Toast toast) =>


Console.WriteLine("Putting butter on the toast");

private static Toast ToastBread(int slices)


{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
Task.Delay(3000).Wait();
Console.WriteLine("Remove toast from toaster");

return new Toast();


}

private static Bacon FryBacon(int slices)


{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
Task.Delay(3000).Wait();
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
Task.Delay(3000).Wait();
Console.WriteLine("Put bacon on plate");

return new Bacon();


}

private static Egg FryEggs(int howMany)


{
Console.WriteLine("Warming the egg pan...");
Task.Delay(3000).Wait();
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
Task.Delay(3000).Wait();
Console.WriteLine("Put eggs on plate");

return new Egg();


return new Egg();
}

private static Coffee PourCoffee()


{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}

La colazione preparata in modo sincrono richiede circa 30 minuti perché il totale è la somma di ogni singola
attività.

NOTE
Le Coffee Egg classi,, Bacon , Toast e Juice sono vuote. Sono semplicemente classi marcatore per lo scopo della
dimostrazione, non contengono proprietà e non servono altri scopi.

I computer non interpretano le istruzioni allo stesso modo delle persone. Il computer si bloccherà in
corrispondenza di ogni istruzione fino a quando non verrà completata prima di passare all'istruzione successiva.
In questo modo non verrà preparata una colazione soddisfacente. Le attività successive non verranno iniziate
prima del completamento delle attività precedenti. La preparazione della colazione richiederà più tempo e alcuni
alimenti si raffredderanno prima di essere serviti.
Se si vuole che il computer esegua le istruzioni precedenti in modo asincrono, è necessario scrivere codice
asincrono.
Queste considerazioni sono importanti per l'attuale scrittura dei programmi. Quando si scrivono programmi
client, si vuole che l'interfaccia utente risponda all'input dell'utente. L'applicazione non deve bloccare l'uso del
telefono durante il download di dati dal Web. Quando si scrivono programmi server, non si vuole che i thread
vengano bloccati. I thread potrebbero essere impegnati a rispondere ad altre richieste. L'uso di codice sincrono
quando sono presenti alternative asincrone riduce la possibilità di aumentare le istanze in modo meno costoso. I
thread bloccati hanno un costo.
Per applicazioni moderne efficienti è necessario creare codice asincrono. Senza supporto del linguaggio, la
scrittura di codice asincrono richiedeva callback, eventi di completamento o altri elementi che nascondevano la
finalità originale del codice. Il vantaggio del codice sincrono è che le azioni dettagliate consentono di analizzare e
comprendere facilmente. Nei modelli asincroni tradizionali era necessario porre l'attenzione sulla natura
asincrona del codice anziché sulle azioni fondamentali del codice.

Non bloccare, ma attendere


Il codice precedente illustra una prassi non corretta, ovvero la costruzione di codice sincrono per eseguire
operazioni asincrone. Come previsto, questo codice impedisce al thread che lo esegue di eseguire altre
operazioni. Non verrà interrotto mentre un'attività è in corso. Equivale a mettersi a osservare il tostapane dopo
avere inserito il pane. E a ignorare qualsiasi interlocutore fino a quando il pane non è pronto.
Si procederà ora ad aggiornare il codice in modo che il thread non venga bloccato mentre sono in esecuzione
altre attività. La parola chiave await consente di iniziare un'attività senza alcun blocco e di continuare
l'esecuzione al completamento dell'attività. Una versione asincrona semplice del codice della preparazione della
colazione sarebbe simile al frammento seguente:

static async Task Main(string[] args)


{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Egg eggs = await FryEggsAsync(2);


Console.WriteLine("eggs are ready");

Bacon bacon = await FryBaconAsync(3);


Console.WriteLine("bacon is ready");

Toast toast = await ToastBreadAsync(2);


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

IMPORTANT
Il tempo totale trascorso è approssimativamente uguale a quello della versione sincrona iniziale. Il codice è ancora in uso
per sfruttare alcune delle funzionalità principali della programmazione asincrona.

TIP
I corpi dei metodi di FryEggsAsync , FryBaconAsync e ToastBreadAsync sono stati aggiornati per restituire
Task<Egg> Task<Bacon> rispettivamente, e Task<Toast> . I metodi vengono rinominati dalla versione originale per
includere il suffisso "Async". Le rispettive implementazioni vengono visualizzate come parte della versione finale più avanti
in questo articolo.

Questo codice non si blocca durante la cottura delle uova o della pancetta. Il codice tuttavia non inizia altre
attività. Si inserisce il pane nel tostapane e si rimane a osservarlo fino al completamento della cottura. Ma
almeno si risponde a un interlocutore che richiede attenzione. In un ristorante in cui vengono fatte più
ordinazioni, il cuoco può iniziare a preparare un'altra colazione mentre la prima è in cottura.
Il thread impegnato nella preparazione della colazione non è bloccato in attesa che venga completata un'attività
iniziata. Per alcune applicazioni, questa modifica è tutto ciò che serve. Un'applicazione GUI risponde sempre
all'utente solo con questa modifica. Tuttavia, per questo scenario si desidera un altro funzionamento. Non si
vuole che ogni attività del componente venga eseguita in modo sequenziale. È preferibile iniziare ogni attività
del componente prima del completamento dell'attività precedente.

Iniziare più attività contemporaneamente


In molti scenari si vuole iniziare immediatamente più attività indipendenti. Quindi, man mano che ogni attività
viene terminata, è possibile passare ad altre operazioni da eseguire. Nell'analogia della colazione, questa
modalità consente di preparare la colazione più rapidamente. Inoltre, tutte le operazioni vengono terminate
quasi nello stesso momento. Si otterrà una colazione calda.
System.Threading.Tasks.Task e i tipi correlati sono classi che è possibile usare per motivare le attività in corso. in
questo modo è possibile scrivere codice più simile al modo in cui effettivamente si prepara una colazione. Si
inizia a cuocere uova, pancetta e pane contemporaneamente. Man mano che ogni attività richiederà un'azione, si
porrà l'attenzione su quell'attività, quindi sull'azione successiva e infine si rimarrà in attesa di altra attività da
eseguire.
Si inizia un'attività e la si mantiene nell'oggetto Task che rappresenta il lavoro. Si rimarrà in attesa ( await ) di
ogni attività prima di utilizzarne il risultato.
Verranno ora apportate queste modifiche al codice della colazione. Il primo passaggio consiste nell'archiviare le
attività delle operazioni quando vengono iniziate, anziché rimanere in attesa di esse:

Coffee cup = PourCoffee();


Console.WriteLine("coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);


Egg eggs = await eggsTask;
Console.WriteLine("eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);


Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(2);


Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");

Successivamente, è possibile spostare le istruzioni await della pancetta e delle uova alla fine del metodo, prima
di servire la colazione:
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);


Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");

Egg eggs = await eggsTask;


Console.WriteLine("eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");

Console.WriteLine("Breakfast is ready!");

La preparazione in modo asincrono ha richiesto circa 20 minuti perché alcune attività erano in grado di essere
eseguite contemporaneamente.
Il codice precedente ha un funzionamento migliore. Tutte le attività asincrone vengono iniziate
contemporaneamente. Si rimane in attesa di ogni attività solo quando è necessario avere a disposizione il
risultato dell'attività. Il codice precedente potrebbe essere simile al codice di un'applicazione Web che effettua le
richieste di diversi microservizi, quindi unisce i risultati in una singola pagina. Si eseguiranno tutte le richieste
immediatamente, quindi si rimarrà in attesa ( await ) di tutte le attività e si comporrà la pagina Web.

Composizione di attività
Tutti gli alimenti della colazione sono pronti contemporaneamente ad eccezione del pane. La preparazione del
pane rappresenta la composizione di un'operazione asincrona (tostatura del pane) e di operazioni sincrone
(aggiunta del burro e della marmellata). L'aggiornamento di questo codice illustra un concetto importante:
IMPORTANT
La composizione di un'operazione asincrona, seguita da un lavoro sincrono è un'operazione asincrona. In altre parole, se
una parte di un'operazione è asincrona, l'intera operazione è asincrona.

Il codice precedente ha mostrato che è possibile usare gli oggetti Task o Task<TResult> per attività in esecuzione.
Si rimane in attesa ( await ) di ogni attività prima di usarne il risultato. Il passaggio successivo consiste nel
creare metodi che rappresentano la combinazione di altre operazioni. Prima di servire la colazione, si vuole
attendere l'attività che rappresenta la tostatura del pane prima dell'aggiunta del butto e della marmellata. È
possibile rappresentare queste operazioni con il codice seguente:

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)


{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);

return toast;
}

Il metodo precedente include il modificatore async nella firma. Il modificatore segnala al compilatore che il
metodo contiene un'istruzione await ; contiene operazioni asincrone. Questo metodo rappresenta l'attività di
tostatura del pane, quindi aggiunge il burro e la marmellata. Questo metodo restituisce Task<TResult> che
rappresenta la composizione di queste tre operazioni. Il blocco di codice principale sarà ora il seguente:

static async Task Main(string[] args)


{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

var eggsTask = FryEggsAsync(2);


var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);

var eggs = await eggsTask;


Console.WriteLine("eggs are ready");

var bacon = await baconTask;


Console.WriteLine("bacon is ready");

var toast = await toastTask;


Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

La modifica precedente ha illustrato una tecnica importante per l'uso di codice asincrono. Si compongono le
attività separando le operazioni in un nuovo metodo che restituisce un'attività. È possibile scegliere quando
rimanere in attesa dell'attività. È possibile iniziare altre attività contemporaneamente.

Attendere le attività in modo efficiente


La serie di istruzioni await alla fine del codice precedente può essere migliorata usando i metodi della classe
Task . Una delle API è WhenAll che restituisce Task che viene completata quando tutte le attività del relativo
elenco di argomenti sono state completate, come illustrato nel codice seguente:
await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("eggs are ready");
Console.WriteLine("bacon is ready");
Console.WriteLine("toast is ready");
Console.WriteLine("Breakfast is ready!");

Un'altra opzione consiste nell'usare WhenAny che restituisce Task<Task> che viene completata quando tutti i
relativi argomenti vengono completati. È possibile attendere l'attività restituita, sapendo che è già stata
completata. Il codice seguente illustra come è possibile usare WhenAny per attendere il completamento della
prima attività e quindi elaborarne il risultato. Dopo aver elaborato il risultato dell'attività completata, si rimuove
l'attività completata dall'elenco delle attività passate a WhenAny .

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };


while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
breakfastTasks.Remove(finishedTask);
}

Dopo aver apportato tutte le modifiche, la versione finale del codice è simile alla seguente:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
class Program
{
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

var eggsTask = FryEggsAsync(2);


var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };


while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
breakfastTasks.Remove(finishedTask);
}

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)


{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);

return toast;
}

private static Juice PourOJ()


{
Console.WriteLine("Pouring orange juice");
return new Juice();
}

private static void ApplyJam(Toast toast) =>


Console.WriteLine("Putting jam on the toast");

private static void ApplyButter(Toast toast) =>


Console.WriteLine("Putting butter on the toast");

private static async Task<Toast> ToastBreadAsync(int slices)


{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
Console.WriteLine("Remove toast from toaster");

return new Toast();


}

private static async Task<Bacon> FryBaconAsync(int slices)


{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
await Task.Delay(3000);
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
await Task.Delay(3000);
Console.WriteLine("Put bacon on plate");

return new Bacon();


}

private static async Task<Egg> FryEggsAsync(int howMany)


{
Console.WriteLine("Warming the egg pan...");
await Task.Delay(3000);
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
await Task.Delay(3000);
Console.WriteLine("Put eggs on plate");
Console.WriteLine("Put eggs on plate");

return new Egg();


}

private static Coffee PourCoffee()


{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}

La versione finale della colazione preparata in modo asincrono richiede circa 15 minuti, perché alcune attività
possono essere eseguite simultaneamente e il codice è stato in grado di monitorare più attività
contemporaneamente e di intervenire solo quando necessario.
Il codice finale è asincrono. Riflette con maggior precisione il modo in cui viene preparata una colazione.
Confrontare il codice precedente con il primo esempio di codice di questo articolo. Le azioni principali risultano
ancora chiare dalla lettura del codice. È possibile leggere il codice allo stesso modo in cui si leggerebbero le
istruzioni per preparare una colazione riportate all'inizio di questo articolo. Le funzionalità del linguaggio per
async e await offrono la traduzione che ogni persona farebbe per seguire le istruzioni scritte: iniziare le
attività non appena possibile e non bloccarsi in attesa del completamento delle attività.

Passaggi successivi
Informazioni sul modello di programmazione asincrona delle attività
Modello di programmazione asincrona attività
02/11/2020 • 26 minutes to read • Edit Online

È possibile evitare colli di bottiglia nelle prestazioni e migliorare la risposta generale dell'applicazione
utilizzando la programmazione asincrona. Le tecniche tradizionali per la scrittura di applicazioni asincrone,
tuttavia, possono essere complesse, rendendone difficile la scrittura, il debug e la gestione.
C# 5 introduce un approccio semplificato, la programmazione asincrona, che sfrutta il supporto asincrono in
.NET Framework 4.5 e versioni successive, .NET Core e Windows Runtime. Il compilatore esegue il lavoro difficile
che prima veniva svolto dallo sviluppatore e l'applicazione mantiene una struttura logica simile al codice
sincrono. Di conseguenza, si ottengono tutti i vantaggi della programmazione asincrona con meno lavoro
richiesto.
In questo argomento viene fornita una panoramica di come e quando utilizzare la programmazione asincrona e
vengono forniti collegamenti per supportare gli argomenti contenenti informazioni dettagliate ed esempi.

Async migliora la velocità di risposta


La modalità asincrona è essenziale per le attività che potenzialmente bloccano l'esecuzione, ad esempio
l'accesso al Web. L'accesso a una risorsa Web può essere talvolta lento o ritardato. Se questa attività viene
bloccata in un processo sincrono, l'intera applicazione deve attendere. In un processo asincrono l'applicazione
può invece continuare con un altro lavoro che non dipende dalla risorsa Web finché l'attività di blocco non
termina.
Nella tabella seguente sono mostrate le aree tipiche in cui la programmazione asincrona migliora la risposta. Le
API elencate da .NET e Windows Runtime contengono metodi che supportano la programmazione asincrona.

T IP I W IN DO W S RUN T IM E C O N
A REA DEL L 'A P P L IC A Z IO N E T IP I . N ET C O N M ETO DI A SIN C RO N I M ETO DI A SIN C RO N I

Accesso Web HttpClient Windows.Web.Http.HttpClient


SyndicationClient

Utilizzo dei file JsonSerializer StorageFile


StreamReader
StreamWriter
XmlReader
XmlWriter

Utilizzo di immagini MediaCapture


BitmapEncoder
BitmapDecoder

Programmazione WCF Operazioni sincrone e asincrone

La modalità asincrona è particolarmente importante per le applicazioni che accedono al thread dell'interfaccia
utente poiché tutte le attività correlate all'interfaccia utente in genere condividono un thread. Se un processo è
bloccato in un'applicazione sincrona, tutte le attività saranno bloccate. L'applicazione non risponde e si potrebbe
pensare che si sia verificato un errore mentre si tratta solo di un'applicazione attesa.
Quando si utilizzano i metodi asincroni, l'applicazione continua a rispondere all'interfaccia utente. È possibile ad
esempio ridimensionare o ridurre a icona una finestra oppure è possibile chiudere l'applicazione se non si
desidera attendere il completamento.
L'approccio basato su modalità asincrona aggiunge l'equivalente di una trasmissione automatica all'elenco di
opzioni da cui è possibile scegliere quando si progettano operazioni asincrone. In questo modo si ottengono
tutti i vantaggi della programmazione asincrona tradizionale con meno lavoro richiesto allo sviluppatore.

I metodi asincroni sono facili da scrivere


Le parole chiave async e await in C# sono il punto centrale della programmazione asincrona. Usando queste due
parole chiave, è possibile usare le risorse in .NET Framework, .NET Core o i Windows Runtime per creare un
metodo asincrono con la stessa facilità con cui si crea un metodo sincrono. I metodi asincroni definiti con la
parola chiave async sono denominati metodi asincroni.
Nell'esempio seguente viene illustrato un metodo asincrono. Quasi tutti gli elementi del codice dovrebbero
avere un aspetto familiare.
È possibile trovare un esempio completo di Windows Presentation Foundation (WPF) disponibile per il
download dalla programmazione asincrona con Async e await in C#.

public async Task<int> GetUrlContentLengthAsync()


{
var client = new HttpClient();

Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");

DoIndependentWork();

string contents = await getStringTask;

return contents.Length;
}

void DoIndependentWork()
{
Console.WriteLine("Working...");
}

Dall'esempio precedente è possibile dedurre diverse cose. Partiamo, ad esempio, dalla firma del metodo che
include il modificatore async . Il tipo restituito è Task<int> (Per altre opzioni vedere la sezione "Tipi restituiti e
parametri"). Il nome del metodo finisce con Async . Nel corpo del metodo GetStringAsync restituisce un oggetto
Task<string> . Ciò significa che quando si esegue await sull'attività si ottiene un oggetto string ( contents ).
Prima di mettere in attesa l'attività, è possibile eseguire operazioni che non si basano sull'oggetto string da
GetStringAsync .

Prestare attenzione all'operatore await Sospende GetUrlContentLengthAsync :


GetUrlContentLengthAsync non può continuare finché l'operazione getStringTask non è stata completata.
Nel frattempo il controllo viene restituito al chiamante di GetUrlContentLengthAsync .
Il controllo riprende qui quando l'operazione getStringTask è stata completata.
In seguito l'operatore await recupera il risultato di string da getStringTask .
L'istruzione return specifica un risultato intero. Tutti i metodi che mettono GetUrlContentLengthAsync in attesa
recuperano il valore della lunghezza.
Se GetUrlContentLengthAsync non ha alcuna operazione da eseguire tra la chiamata di GetStringAsync e il
relativo completamento, è possibile semplificare il codice chiamando l'istruzione singola seguente e rimanendo
in attesa.
string contents = await client.GetStringAsync("https://docs.microsoft.com/dotnet");

Le seguenti caratteristiche riepilogano gli elementi che rendono l'esempio precedente un metodo asincrono:
La firma del metodo include un modificatore async .
Il nome di un metodo asincrono termina per convenzione con un suffisso "Async".
Il tipo restituito è uno dei seguenti:
Task<TResult> se nel metodo è presente un'istruzione return in cui l'operando è di tipo TResult .
Task se nel metodo non è presente un'istruzione return oppure è presente un'istruzione return senza
l'operando.
void se si sta scrivendo un gestore eventi asincrono.
Qualsiasi altro tipo che ha un metodo GetAwaiter (a partire da C# 7.0).
Per ulteriori informazioni, vedere la sezione tipi restituiti e parametri .
Il metodo include in genere almeno un'espressione await , che contrassegna un punto in cui il metodo
non può continuare fino a quando l'operazione asincrona in attesa non è stata completata. Nel frattempo,
il metodo viene sospeso e il controllo ritorna al chiamante del metodo. Nella sezione successiva di questo
argomento viene illustrato quello che accade in corrispondenza del punto di sospensione.
Nei metodi asincroni utilizzare le parole chiave e i tipi forniti per indicare l'operazione da eseguire e il
compilatore esegue il resto dell'operazione, inclusa la traccia di cosa deve verificarsi quando il controllo viene
restituito a un punto di attesa in un metodo sospeso. Alcuni processi di routine, come cicli e gestione delle
eccezioni, possono essere difficili da gestire nel codice asincrono tradizionale. In un metodo asincrono scrivere
questi elementi come in una soluzione sincrona e il problema viene risolto.
Per altre informazioni su modalità asincrona nelle versioni precedenti di .NET Framework, vedere TPL e la
programmazione asincrona .NET Framework tradizionale.

Cosa accade in un metodo asincrono


La cosa più importante da capire nella programmazione asincrona è il modo in cui il flusso del controllo si
sposta da un metodo all'altro. Nel diagramma seguente viene descritto il processo:
I numeri nel diagramma corrispondono ai passaggi seguenti, avviati quando un metodo chiamante chiama il
metodo asincrono.
1. Un metodo chiamante chiama e attende il GetUrlContentLengthAsync metodo asincrono.
2. GetUrlContentLengthAsync crea un'istanza di HttpClient e chiama il metodo asincrono GetStringAsync per
scaricare il contenuto di un sito Web come stringa.
3. Si verifica un evento in GetStringAsync che ne sospende lo stato di avanzamento forse perché deve
attendere il termine dello scaricamento di un sito Web o un'altra attività di blocco. Per evitare di bloccare
le risorse, GetStringAsync restituisce il controllo al chiamante GetUrlContentLengthAsync .
GetStringAsync restituisce Task<TResult>, dove TResult è una stringa e GetUrlContentLengthAsync
assegna l'attività alla variabile getStringTask . L'attività rappresenta il processo in corso per la chiamata a
GetStringAsync , con l'impegno di produrre un valore stringa effettivo a completamento del lavoro.

4. Poiché getStringTask non è stata ancora attesa, GetUrlContentLengthAsync può continuare con altro
lavoro che non dipende dal risultato finale ottenuto da GetStringAsync . Tale lavoro è rappresentato da
una chiamata al metodo sincrono DoIndependentWork .
5. DoIndependentWork è un metodo sincrono che esegue il proprio lavoro e lo restituisce al chiamante.
6. GetUrlContentLengthAsync ha esaurito il lavoro che può eseguire senza un risultato da getStringTask .
GetUrlContentLengthAsync deve quindi calcolare e restituire la lunghezza della stringa scaricata, ma il
metodo non può calcolare il valore finché quest'ultimo non contiene la stringa.
Di conseguenza, GetUrlContentLengthAsync utilizza un operatore await per sospendere lo stato di
avanzamento e restituire il controllo al metodo che ha chiamato GetUrlContentLengthAsync .
GetUrlContentLengthAsync restituisce Task<int> al chiamante. L'attività rappresenta l'intenzione di
produrre un risultato di tipo Integer che è la lunghezza della stringa scaricata.
NOTE
Se l'operazione GetStringAsync (e quindi getStringTask ) viene completata prima che
GetUrlContentLengthAsync la metta in attesa, il controllo resta a GetUrlContentLengthAsync .
GetUrlContentLengthAsync Se il processo asincrono chiamato getStringTask è già stato completato e
GetUrlContentLengthAsync non è necessario attendere il risultato finale, le spese per sospendere e tornare a
sarebbero sprecate.

All'interno del metodo chiamante il modello di elaborazione continua. Il chiamante può eseguire altre
attività che non dipendono dal risultato di GetUrlContentLengthAsync prima di attendere tale risultato
oppure può mettersi immediatamente in attesa. Il metodo chiamante è in attesa di
GetUrlContentLengthAsync e GetUrlContentLengthAsync è in attesa di GetStringAsync .

7. GetStringAsync termina e produce un risultato di stringa. Il risultato di stringa non viene restituito dalla
chiamata a GetStringAsync nel modo previsto. Tenere presente che il metodo non ha restituito un'attività
al passaggio 3. Il risultato di stringa viene invece memorizzato nell'attività che rappresenta il
completamento del metodo, ovvero getStringTask . L'operatore await recupera il risultato da
getStringTask . L'istruzione di assegnazione assegna il risultato recuperato a contents .

8. Quando GetUrlContentLengthAsync ha il risultato di stringa, il metodo può calcolare la lunghezza della


stringa. Il lavoro di GetUrlContentLengthAsync è quindi completo e il gestore eventi in attesa può
riprendere l'attività. Nell'esempio completo alla fine dell'argomento è possibile confermare che il gestore
eventi recupera e stampa il valore del risultato di lunghezza. Se non si ha familiarità con la
programmazione asincrona, valutare la differenza tra il comportamento sincrono e asincrono. Viene
restituito un metodo sincrono quando il lavoro è completato (passaggio 5), ma un metodo asincrono
restituisce un valore di attività quando il relativo lavoro viene sospeso (passaggi 3 e 6). Una volta che il
metodo asincrono completa l'operazione, l'attività viene contrassegnata come completata e il risultato, se
disponibile, viene archiviato nell'attività.

Metodi asincroni API


Metodi come GetStringAsync che supportano la programmazione asincrona .NET Framework 4,5 o versione
successiva e .NET Core contengono molti membri che funzionano con async e await . È possibile riconoscerli
dal suffisso "Async" aggiunto al nome del membro e dal tipo restituito di Task o Task<TResult> . Ad esempio, la
classe System.IO.Stream contiene metodi come CopyToAsync, ReadAsync e WriteAsync insieme ai metodi
sincroni CopyTo, Read e Write.
Windows Runtime contiene inoltre molti metodi che è possibile usare con async e await in app Windows. Per
altre informazioni, vedere Threading e programmazione asincrona per lo sviluppo UWP e Programmazione
asincrona (app Windows Store) e Quickstart: Calling asynchronous APIs in C# or Visual Basic (Guida
introduttiva: Chiamata di API asincrone in C# o Visual Basic) se si usano versioni precedenti di Windows
Runtime.

Thread
I metodi asincroni vengono considerati operazioni non bloccanti. Un' await espressione in un metodo
asincrono non blocca il thread corrente quando l'attività attesa è in esecuzione. Al contrario, l'espressione
registra il resto del metodo come continuazione e restituisce il controllo al chiamante del metodo asincrono.
Le parole chiave async e await non determinano la creazione di thread aggiuntivi. I metodi asincroni non
richiedono multithreading perché un metodo asincrono non viene eseguito nel proprio thread. Il metodo viene
eseguito nel contesto di sincronizzazione corrente e utilizza il tempo sul thread solo se il metodo è attivo. È
possibile utilizzare Task.Run per spostare un lavoro associato alla CPU in un thread in background. Quest'ultimo
tuttavia non è di alcun ausilio in un processo che attende solo che i risultati diventino disponibili.
L'approccio alla programmazione asincrona basato su async è quasi sempre preferibile agli approcci esistenti. In
particolare, questo approccio è preferibile alla classe BackgroundWorker per le operazioni di I/O poiché il codice
è più semplice e non è necessario proteggersi da situazioni di race condition. In combinazione con il Task.Run
metodo, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU,
perché la programmazione asincrona separa i dettagli di coordinamento dell'esecuzione del codice dal lavoro
che Task.Run trasferisce al pool di thread.

Async e await
Se si specifica che un metodo è asincrono usando il modificatore async, vengono attivate le due funzionalità
seguenti.
Il metodo asincrono contrassegnato può usare await per definire i punti di sospensione. L'operatore
await indica al compilatore che il metodo asincrono non può continuare oltre un dato punto prima del
completamento del processo asincrono in attesa. Nel frattempo il controllo viene restituito al chiamante
del metodo asincrono.
La sospensione di un metodo asincrono in un' await espressione non costituisce un'uscita dal metodo e
i finally blocchi non vengono eseguiti.
Il metodo asincrono contrassegnato può essere atteso da metodi che lo chiamano.
Un metodo asincrono contiene in genere una o più occorrenze di un await operatore, ma l'assenza di await
espressioni non provoca un errore del compilatore. Se un metodo asincrono non usa un await operatore per
contrassegnare un punto di sospensione, il metodo viene eseguito come metodo sincrono, nonostante il async
modificatore. Il compilatore genera un avviso per tali metodi.
async e await sono parole chiave contestuali. Per ulteriori informazioni ed esempi, vedere gli argomenti
seguenti:
async
await

Tipi restituiti e parametri


Un metodo asincrono restituisce in genere Task o Task<TResult>. In un metodo asincrono un operatore await
viene applicato a un'attività restituita da una chiamata a un altro metodo asincrono.
Specificare Task<TResult> come tipo restituito se il metodo contiene un' return istruzione che specifica un
operando di tipo TResult .
Usare Task come tipo restituito se il metodo non include un'istruzione return o contiene un'istruzione return che
non restituisce un operando.
A partire da C# 7.0, è possibile specificare anche altri tipi restituiti, a condizione che il tipo includa un metodo
GetAwaiter . Un esempio dei tipi in questione è ValueTask<TResult>. disponibile nel pacchetto NuGet
System.Threading.Tasks.Extension.
Nell'esempio seguente viene illustrato come dichiarare e chiamare un metodo che restituisce un oggetto
Task<TResult> o un oggetto Task :
async Task<int> GetTaskOfTResultAsync()
{
int hours = 0;
await Task.Delay(0);

return hours;
}

Task<int> returnedTaskTResult = GetTaskOfTResultAsync();


int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()


{
await Task.Delay(0);
// No return statement needed
}

Task returnedTask = GetTaskAsync();


await returnedTask;
// Single line
await GetTaskAsync();

Ogni attività restituita rappresenta il lavoro attualmente in fase di esecuzione. Un'attività include le informazioni
sullo stato del processo asincrono e, infine, il risultato finale del processo o l'eccezione che il processo genera se
non viene completato.
Un metodo asincrono può avere un tipo restituito void . Il tipo restituito viene utilizzato principalmente per
definire i gestori eventi, dove un tipo restituito void è necessario. I gestori eventi asincroni fungono spesso da
punto di partenza per i programmi asincroni.
Un metodo asincrono che ha un void tipo restituito non può essere atteso e il chiamante di un metodo che
restituisce void non può intercettare le eccezioni generate dal metodo.
Un metodo asincrono non può dichiarare parametri in, ref o out, ma può chiamare metodi con tali parametri.
Analogamente, un metodo asincrono non può restituire un valore tramite riferimento, sebbene possa chiamare
metodi con valori restituiti di riferimento.
Per altre informazioni ed esempi, vedere tipi restituiti asincroni (C#). Per altre informazioni su come intercettare
eccezioni nei metodi asincroni, vedere try-catch.
Nella programmazione Windows Runtime le API asincrone hanno uno dei tipi restituiti seguenti, che sono simili
alle attività:
IAsyncOperation<TResult>, che corrisponde a Task<TResult>
IAsyncAction, che corrisponde a Task
IAsyncActionWithProgress<TProgress>
IAsyncOperationWithProgress<TResult,TProgress>

Convenzione di denominazione
Per convenzione, i metodi che restituiscono tipi comunemente awaitable, ad esempio,,, Task Task<T>
ValueTask ValueTask<T> devono avere nomi che terminano con "Async". I metodi che avviano un'operazione
asincrona, ma non restituiscono un tipo awaitable, non devono avere nomi che terminano con "Async", ma
possono iniziare con "Begin", "Start" o altri verbi per suggerire che questo metodo non restituisce o genera il
risultato dell'operazione.
È possibile ignorare la convenzione se un evento, una classe base o un contratto di interfaccia suggerisce un
nome diverso. Ad esempio, non è necessario rinominare i gestori eventi comuni, ad esempio OnButtonClick .

Argomenti correlati ed esempi (Visual Studio)


T ITO LO DESC RIZ IO N E ESEM P IO

Come eseguire più richieste Web in Viene illustrato come avviare Async Sample: Make Multiple Web
parallelo tramite Async e await (C#) contemporaneamente diverse attività. Requests in Parallel (Esempio di attività
asincrona: Esecuzione di più richieste
Web in parallelo)

Tipi restituiti asincroni (C#) Vengono illustrati i tipi che i metodi


asincroni possono restituire e viene
spiegato quando ogni tipo è
appropriato.

Annulla le attività con un token di Mostra come aggiungere la seguente


annullamento come meccanismo di funzionalità alla soluzione asincrono:
segnalazione.
- Annullare un elenco di attività (C#)
- Annulla le attività dopo un periodo di
tempo (C#)
- Elabora attività asincrona al
completamento (C#)

Uso di Async per l'accesso ai file (C#) Vengono elencati e illustrati i vantaggi
dell'utilizzo di async e await per
accedere ai file.

Modello asincrono basato su attività Descrive un modello asincrono, il


(TAP) modello è basato sui Task tipi e
Task<TResult> .

Video sulla modalità asincrona su Vengono forniti collegamenti a una


Channel 9 serie di video sulla programmazione
asincrona.

Vedere anche
async
await
Programmazione asincrona
Panoramica asincrona
Tipi restituiti asincroni (C#)
02/11/2020 • 14 minutes to read • Edit Online

I metodi asincroni possono avere i seguenti tipi restituiti:


Task, per un metodo asincrono che esegue un'operazione ma non restituisce alcun valore.
Task<TResult>, per un metodo asincrono che restituisce un valore.
void per un gestore eventi.
A partire da C# 7.0, qualsiasi tipo con un metodo GetAwaiter accessibile. L'oggetto restituito dal metodo
GetAwaiter deve implementare l'interfaccia System.Runtime.CompilerServices.ICriticalNotifyCompletion.
A partire da C# 8,0, IAsyncEnumerable<T> , per un metodo asincrono che restituisce un flusso asincrono.
Per altre informazioni sui metodi asincroni, vedere programmazione asincrona con Async e await (C#).
Esistono anche diversi altri tipi specifici dei carichi di lavoro di Windows:
DispatcherOperation, per le operazioni asincrone limitate a Windows.
IAsyncAction, per le azioni asincrone in UWP che non restituiscono un valore.
IAsyncActionWithProgress<TProgress>, per le azioni asincrone in UWP che segnalano lo stato ma non
restituiscono un valore.
IAsyncOperation<TResult>, per le operazioni asincrone in UWP che restituiscono un valore.
IAsyncOperationWithProgress<TResult,TProgress>, per le operazioni asincrone in UWP che segnalano lo
stato di avanzamento e restituiscono un valore.

Tipo restituito dell'attività


I metodi asincroni che non contengono un'istruzione return o che contengono un'istruzione return che non
restituisce un operando hanno in genere il tipo restituito Task. Questi metodi restituiscono void se eseguiti in
modo sincrono. Se si usa un tipo restituito Task per un metodo asincrono, un metodo chiamante può usare un
operatore await per sospendere il completamento del chiamante fino a quando il metodo asincrono chiamato
non abbia terminato l'operazione.
Nell'esempio seguente, il WaitAndApologizeAsync metodo non contiene un' return istruzione, quindi il metodo
restituisce un Task oggetto. La restituzione di un oggetto Task consente WaitAndApologizeAsync a di essere
atteso. Il Task tipo non include una Result proprietà perché non ha alcun valore restituito.
public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();

Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}

static async Task WaitAndApologizeAsync()


{
await Task.Delay(2000);

Console.WriteLine("Sorry for the delay...\n");


}
// Example output:
// Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.

L'attesa per WaitAndApologizeAsync viene impostata usando un'istruzione await invece di un'espressione await,
in modo simile all'istruzione di chiamata per un metodo sincrono che restituisce void. L'applicazione di un
operatore await in questo caso non produce un valore. Per chiarire l'istruzione e l'espressione dei termini, fare
riferimento alla tabella seguente:

T IP O DI AT T ESA ESEM P IO TYPE

. await SomeTaskMethodAsync() Task

Expression T result = await Task<TResult>


SomeTaskMethodAsync<T>();

È possibile separare la chiamata a WaitAndApologizeAsync dall'applicazione di un operatore await, come


illustrato nel codice seguente. Tuttavia, si noti che un tipo Task non ha una proprietà Result e che quando
viene applicato un operatore await a un tipo Task non viene prodotto alcun valore.
Il codice seguente separa la chiamata del metodo WaitAndApologizeAsync dall'attesa dell'attività restituita dal
metodo.

Task waitAndApologizeTask = WaitAndApologizeAsync();

string output =
$"Today is {DateTime.Now:D}\n" +
$"The current time is {DateTime.Now.TimeOfDay:t}\n" +
"The current temperature is 76 degrees.\n";

await waitAndApologizeTask;
Console.WriteLine(output);

<TResult>Tipo restituito dell'attività


Il Task<TResult> tipo restituito viene usato per un metodo asincrono che contiene un'istruzione Return in cui
l'operando è TResult .
Nell'esempio seguente il GetLeisureHoursAsync metodo contiene un' return istruzione che restituisce un
Integer. La dichiarazione del metodo deve quindi specificare un tipo restituito di Task<int> . Il FromResult
Metodo Async è un segnaposto per un'operazione che restituisce un oggetto DayOfWeek .

public static async Task ShowTodaysInfoAsync()


{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";

Console.WriteLine(message);
}

static async Task<int> GetLeisureHoursAsync()


{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);

int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;

return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5

Quando GetLeisureHoursAsync viene chiamato da un'espressione await nel metodo ShowTodaysInfo ,


l'espressione recupera il valore intero (valore di leisureHours ) archiviato nell'attività restituita dal metodo
GetLeisureHours . Per altre informazioni sulle espressioni await, vedere await.

È possibile comprendere meglio il modo in cui await Recupera il risultato da un oggetto Task<T> separando la
chiamata a GetLeisureHoursAsync dall'applicazione di await , come illustrato nel codice seguente. Una chiamata
al metodo GetLeisureHoursAsync che non viene immediatamente attesa restituisce un tipo Task<int> , come ci si
aspetterebbe dalla dichiarazione del metodo. Nell'esempio, l'attività viene assegnata alla variabile
getLeisureHoursTask . Poiché getLeisureHoursTask è un Task<TResult>, contiene una proprietà Result di tipo
TResult . In questo caso, TResult rappresenta un tipo Integer. Quando si applica await a getLeisureHoursTask ,
l'espressione await restituisce il contenuto della proprietà Result di getLeisureHoursTask . Il valore viene
assegnato alla variabile ret .

IMPORTANT
La proprietà Result è una proprietà di blocco. Se si prova ad accedervi prima del completamento dell'attività, il thread
attualmente attivo viene bloccato fino a quando l'attività non viene completata e il valore non è disponibile. Nella maggior
parte dei casi, è consigliabile accedere al valore usando await invece di accedere direttamente alla proprietà.
Nell'esempio precedente è stato recuperato il valore della Result proprietà per bloccare il thread principale in modo che il
Main Metodo potesse stampare sulla message console prima del termine dell'applicazione.

var getLeisureHoursTask = GetLeisureHoursAsync();

string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";

Console.WriteLine(message);
Tipo restituito void
Usare il tipo restituito void nei gestori eventi asincroni, che richiedono un tipo restituito void . Per i metodi
diversi dai gestori eventi che non restituiscono un valore, è invece necessario restituire Task, perché non è
possibile impostare l'attesa per un metodo asincrono che restituisce void . Qualsiasi chiamante di un metodo di
questo tipo deve continuare a completare senza attendere il completamento del metodo asincrono chiamato. Il
chiamante deve essere indipendente dai valori o dalle eccezioni generate dal metodo asincrono.
Il chiamante di un metodo asincrono che restituisce void non può intercettare le eccezioni generate dal metodo
ed è probabile che le eccezioni non gestite provochino un errore dell'applicazione. Se un metodo che restituisce
Task o Task<TResult> genera un'eccezione, l'eccezione viene archiviata nell'attività restituita. L'eccezione viene
generata nuovamente quando l'attività è in attesa. Di conseguenza, verificare che qualsiasi metodo asincrono in
grado di produrre un'eccezione abbia un tipo restituito Task o Task<TResult> e che sia impostata l'attesa per le
chiamate al metodo.
Per altre informazioni su come intercettare le eccezioni nei metodi asincroni, vedere la sezione Exceptions in
async methods dell'articolo try-catch .
L'esempio seguente mostra il comportamento di un gestore dell'evento asincrono. Nel codice di esempio, un
gestore eventi asincrono deve consentire al thread principale di stabilire quando viene completato. Il thread
principale può attendere quindi che un gestore dell'evento asincrono venga completato prima di uscire dal
programma.

using System;
using System.Threading.Tasks;

public class NaiveButton


{
public event EventHandler? Clicked;

public void Click()


{
Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
Clicked?.Invoke(this, EventArgs.Empty);
Console.WriteLine("All listeners are notified.");
}
}

public class AsyncVoidExample


{
static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();

public static async Task MultipleEventHandlersAsync()


{
Task<bool> secondHandlerFinished = s_tcs.Task;

var button = new NaiveButton();

button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;

Console.WriteLine("Before button.Click() is called...");


button.Click();
Console.WriteLine("After button.Click() is called...");

await secondHandlerFinished;
}

private static void OnButtonClicked1(object? sender, EventArgs e)


{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}

private static async void OnButtonClicked2Async(object? sender, EventArgs e)


{
Console.WriteLine(" Handler 2 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 2 is about to go async...");
await Task.Delay(500);
Console.WriteLine(" Handler 2 is done.");
s_tcs.SetResult(true);
}

private static void OnButtonClicked3(object? sender, EventArgs e)


{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 2 is about to go async...
// Handler 3 is starting...
// Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
// Handler 2 is done.

Tipi restituiti asincroni generalizzati e ValueTask<TResult>


A partire da C# 7.0, un metodo asincrono può restituire qualsiasi tipo con un metodo GetAwaiter accessibile.
Dato che Task e Task<TResult> sono tipi riferimento, l'allocazione della memoria in percorsi critici per le
prestazioni, in particolare quando le allocazioni si verificano in cicli ravvicinati, può influire negativamente sulle
prestazioni. Il supporto dei tipi restituiti generalizzati implica la possibilità di restituire un tipo valore leggero
anziché un tipo riferimento per evitare allocazioni di memoria aggiuntive.
.NET offre la struttura System.Threading.Tasks.ValueTask<TResult> come implementazione leggera di un valore
generalizzato per la restituzione di attività. Per usare il tipo System.Threading.Tasks.ValueTask<TResult>, è
necessario aggiungere il pacchetto NuGet System.Threading.Tasks.Extensions al progetto. L'esempio seguente
usa la struttura ValueTask<TResult> per recuperare il valore di due tiri di dadi.
using System;
using System.Threading.Tasks;

class Program
{
static readonly Random s_rnd = new Random();

static async Task Main() =>


Console.WriteLine($"You rolled {await GetDiceRollAsync()}");

static async ValueTask<int> GetDiceRollAsync()


{
Console.WriteLine("Shaking dice...");

int roll1 = await RollAsync();


int roll2 = await RollAsync();

return roll1 + roll2;


}

static async ValueTask<int> RollAsync()


{
await Task.Delay(500);

int diceRoll = s_rnd.Next(1, 7);


return diceRoll;
}
}
// Example output:
// Shaking dice...
// You rolled 8

Flussi asincroni con IAsyncEnumerable<T>


A partire da C# 8,0, un metodo asincrono può restituire un flusso asincrono, rappresentato da
IAsyncEnumerable<T> . Un flusso asincrono fornisce un modo per enumerare gli elementi letti da un flusso
quando gli elementi vengono generati in blocchi con chiamate asincrone ripetute. Nell'esempio seguente viene
illustrato un metodo asincrono che genera un flusso asincrono:

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()


{
string data =
@"This is a line of text.
Here is the second line of text.
And there is one more for good measure.
Wait, that was the penultimate line.";

using var readStream = new StringReader(data);

string line = await readStream.ReadLineAsync();


while (line != null)
{
foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
yield return word;
}

line = await readStream.ReadLineAsync();


}
}

Nell'esempio precedente le righe vengono lette da una stringa in modo asincrono. Una volta letta ogni riga, il
codice enumera ogni parola nella stringa. I chiamanti enumerano ogni parola utilizzando l' await foreach
istruzione. Il metodo attende quando è necessario leggere in modo asincrono la riga successiva dalla stringa di
origine.

Vedere anche
FromResult
Elaborare le attività asincrone quando vengono completate
Programmazione asincrona con Async e await (C#)
async
await
Annullare un elenco di attività (C#)
02/11/2020 • 6 minutes to read • Edit Online

È possibile annullare un'applicazione console asincrona se non si desidera attenderne il completamento.


Seguendo l'esempio in questo argomento, è possibile aggiungere un annullamento a un'applicazione che
Scarica il contenuto di un elenco di siti Web. È possibile annullare molte attività associando l'
CancellationTokenSource istanza a ogni attività. Se si seleziona il tasto invio , verranno annullate tutte le attività
non ancora completate.
Contenuto dell'esercitazione:
Creazione di un'applicazione console .NET
Scrittura di un'applicazione asincrona che supporta l'annullamento
Dimostrazione dell'annullamento della segnalazione

Prerequisiti
Per completare questa esercitazione, è necessario disporre di:
SDK .NET 5,0 o versione successiva
Ambiente di sviluppo integrato (IDE)
Si consiglia di usare Visual Studio, Visual Studio Code o Visual Studio per Mac
Creare un'applicazione di esempio
Creare una nuova applicazione console .NET Core. È possibile crearne uno usando il dotnet new console
comando o da Visual Studio. Aprire il file Program.cs nell'editor di codice preferito.
Sostituisci istruzioni using
Sostituire le istruzioni using esistenti con queste dichiarazioni:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

Aggiungere i campi
Nella Program definizione della classe aggiungere questi tre campi:
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};

CancellationTokenSourceViene usato per segnalare un annullamento richiesto a un CancellationToken .


HttpClient Espone la capacità di inviare richieste HTTP e ricevere risposte http. L'oggetto s_urlList include
tutti gli URL che l'applicazione prevede di elaborare.

Aggiorna punto di ingresso dell'applicazione


Il punto di ingresso principale nell'applicazione console è il Main metodo. Sostituire il metodo esistente con il
codice seguente:

static async Task Main()


{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");

Task cancelTask = Task.Run(() =>


{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}

Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");


s_cts.Cancel();
});

Task sumPageSizesTask = SumPageSizesAsync();

await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });

Console.WriteLine("Application ending.");
}
Il Main metodo aggiornato è ora considerato una principale Async, che consente di specificare un punto di
ingresso asincrono nell'eseguibile. Scrive alcuni messaggi di istruzioni nella console, quindi dichiara un' Task
istanza denominata cancelTask , che leggerà i tratti chiave della console. Se viene premuto il tasto invio , viene
effettuata una chiamata a CancellationTokenSource.Cancel() . Verrà segnalato l'annullamento. Successivamente,
la sumPageSizesTask variabile viene assegnata dal SumPageSizesAsync metodo. Entrambe le attività vengono
quindi passate a Task.WhenAny(Task[]) , che continuerà quando una delle due attività è stata completata.

Creare il metodo di ridimensionamento della pagina Sum asincrono


Sotto il Main metodo aggiungere il SumPageSizesAsync Metodo:

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

Il metodo inizia con la creazione di un'istanza e l'avvio di un oggetto Stopwatch . Esegue quindi il ciclo di ogni
URL in s_urlList e chiama ProcessUrlAsync . Ogni iterazione s_cts.Token viene passata al ProcessUrlAsync
metodo e il codice restituisce un oggetto Task<TResult> , dove TResult è un numero intero:

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}

Aggiungi metodo processo


Aggiungere il metodo seguente al ProcessUrlAsync di sotto del SumPageSizesAsync Metodo:

static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)


{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync();
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}

Per qualsiasi URL specificato, il metodo userà l' client istanza fornita per ottenere la risposta come byte[] . L'
CancellationToken istanza viene passata nei HttpClient.GetAsync(String, CancellationToken) metodi e
HttpContent.ReadAsByteArrayAsync() . token Viene utilizzato per eseguire la registrazione per l'annullamento
richiesto. La lunghezza viene restituita dopo che l'URL e la lunghezza vengono scritti nella console.
Output dell'applicazione di esempio

Application started.
Press the ENTER key to cancel...

https://docs.microsoft.com 37,357
https://docs.microsoft.com/aspnet/core 85,589
https://docs.microsoft.com/azure 398,939
https://docs.microsoft.com/azure/devops 73,663
https://docs.microsoft.com/dotnet 67,452
https://docs.microsoft.com/dynamics365 48,582
https://docs.microsoft.com/education 22,924

ENTER key pressed: cancelling downloads.

Application ending.

Esempio completo
Il codice seguente è il testo completo del file Program.cs per l'esempio.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};

static async Task Main()


{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");

Task cancelTask = Task.Run(() =>


Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}

Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");


s_cts.Cancel();
});

Task sumPageSizesTask = SumPageSizesAsync();

await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });

Console.WriteLine("Application ending.");
}

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)


{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync();
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}
}

Vedere anche
CancellationToken
CancellationTokenSource
Programmazione asincrona con Async e await (C#)

Passaggi successivi
Annullare attività asincrone dopo un periodo di tempo (C#)
Annullare attività asincrone dopo un periodo di
tempo (C#)
02/11/2020 • 3 minutes to read • Edit Online

È possibile annullare un'operazione asincrona dopo un periodo di tempo tramite il


CancellationTokenSource.CancelAfter metodo se non si desidera attendere il completamento dell'operazione.
Questo metodo pianifica l'annullamento di tutte le attività associate che non vengono completate entro il
periodo di tempo definito dall' CancelAfter espressione.
In questo esempio viene aggiunto al codice sviluppato in Annulla un elenco di attività (C#) per scaricare un
elenco di siti Web e per visualizzare la lunghezza del contenuto di ognuno di essi.
Contenuto dell'esercitazione:
Aggiornamento di un'applicazione console .NET esistente
Pianificazione di un annullamento

Prerequisiti
Per completare questa esercitazione, è necessario disporre di:
Si prevede che sia stata creata un'applicazione nell'esercitazione annullare un elenco di attività (C#)
SDK .NET 5,0 o versione successiva
Ambiente di sviluppo integrato (IDE)
Si consiglia di usare Visual Studio, Visual Studio Code o Visual Studio per Mac

Aggiorna punto di ingresso dell'applicazione


Sostituire il Main metodo esistente con il codice seguente:

static async Task Main()


{
Console.WriteLine("Application started.");

try
{
s_cts.CancelAfter(3500);

await SumPageSizesAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}

Console.WriteLine("Application ending.");
}

Il Main metodo aggiornato scrive alcuni messaggi di istruzione nella console. All'interno di try catch, una
chiamata a CancellationTokenSource.CancelAfter(Int32) per pianificare un annullamento. Verrà segnalato
l'annullamento dopo un periodo di tempo.
Successivamente, SumPageSizesAsync viene atteso il metodo. Se l'elaborazione di tutti gli URL si verifica più
velocemente rispetto all'annullamento pianificato, l'applicazione termina. Tuttavia, se l'annullamento pianificato
viene attivato prima dell'elaborazione di tutti gli URL, TaskCanceledException viene generata un'eccezione.
Output dell'applicazione di esempio

Application started.

https://docs.microsoft.com 37,357
https://docs.microsoft.com/aspnet/core 85,589
https://docs.microsoft.com/azure 398,939
https://docs.microsoft.com/azure/devops 73,663

Tasks cancelled: timed out.

Application ending.

Esempio completo
Il codice seguente è il testo completo del file Program.cs per l'esempio.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};

static async Task Main()


{
Console.WriteLine("Application started.");

try
{
s_cts.CancelAfter(3500);

await SumPageSizesAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}

Console.WriteLine("Application ending.");
}

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)


{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}
}

Vedere anche
CancellationToken
CancellationTokenSource
Programmazione asincrona con Async e await (C#)
Annullare un elenco di attività (C#)
Elabora le attività asincrone quando vengono
completate (C#)
28/01/2021 • 7 minutes to read • Edit Online

Usando Task.WhenAny , è possibile avviare più attività contemporaneamente ed elaborarle una alla volta
quando vengono completate, anziché elaborarle nell'ordine in cui sono state avviate.
Nell'esempio seguente viene usata una query per creare una Collection di attività. Ogni attività scarica il
contenuto di un sito Web specificato. In ogni iterazione di un ciclo while, una chiamata attesa a WhenAny
restituisce l'attività nella Collection di attività che completa per prima il download. Questa attività viene rimossa
dalla Collection ed elaborata. Il ciclo si ripete finché la Collection non contiene più attività.

Creare un'applicazione di esempio


Creare una nuova applicazione console .NET Core. È possibile crearne uno usando il comando DotNet New
console o da Visual Studio. Aprire il file Program.cs nell'editor di codice preferito.
Sostituisci istruzioni using
Sostituire le istruzioni using esistenti con queste dichiarazioni:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

Aggiungere i campi
Nella Program definizione della classe aggiungere i due campi seguenti:
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};

HttpClient Espone la capacità di inviare richieste HTTP e ricevere risposte http. L'oggetto s_urlList include
tutti gli URL che l'applicazione prevede di elaborare.

Aggiorna punto di ingresso dell'applicazione


Il punto di ingresso principale nell'applicazione console è il Main metodo. Sostituire il metodo esistente con il
codice seguente:

static Task Main() => SumPageSizesAsync();

Il Main metodo aggiornato è ora considerato una principale Async, che consente di specificare un punto di
ingresso asincrono nell'eseguibile. Viene espressa una chiamata a SumPageSizesAsync .

Creare il metodo di ridimensionamento della pagina Sum asincrono


Sotto il Main metodo aggiungere il SumPageSizesAsync Metodo:
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();

IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

Il metodo inizia con la creazione di un'istanza e l'avvio di un oggetto Stopwatch . Include quindi una query che,
quando eseguita, crea una raccolta di attività. Ogni chiamata a ProcessUrlAsync nel codice seguente restituisce
un Task<TResult>, dove TResult è un valore intero:

IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

A causa dell' esecuzione posticipata con LINQ, si chiama Enumerable.ToList per avviare ogni attività.

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

Il while ciclo esegue i passaggi seguenti per ogni attività nella raccolta:
1. Attende una chiamata a WhenAny per identificare la prima attività nella raccolta che ha terminato il
download.

Task<int> finishedTask = await Task.WhenAny(downloadTasks);

2. Rimuove l'attività dalla Collection.

downloadTasks.Remove(finishedTask);

3. Attende finishedTask , che viene restituito da una chiamata a ProcessUrlAsync . La variabile


finishedTask è un Task<TResult> dove TResult è un valore intero. L'attività è già stata completata, ma è
possibile metterla in attesa per recuperare la lunghezza del sito Web scaricato, come illustrato di seguito.
Se si verifica un errore nell'attività, await in verrà generata la prima eccezione figlio archiviata in
AggregateException , a differenza della lettura della Task<TResult>.Result proprietà, che genererebbe
AggregateException .
total += await finishedTask;

Aggiungi metodo processo


Aggiungere il metodo seguente al ProcessUrlAsync di sotto del SumPageSizesAsync Metodo:

static async Task<int> ProcessUrlAsync(string url, HttpClient client)


{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}

Per qualsiasi URL specificato, il metodo userà l' client istanza fornita per ottenere la risposta come byte[] . La
lunghezza viene restituita dopo che l'URL e la lunghezza vengono scritti nella console.
Eseguire il programma più volte per verificare che le lunghezze scaricate non siano sempre nello stesso ordine.
Cau t i on

È possibile usare WhenAny in un ciclo, come descritto nell'esempio, per risolvere i problemi che includono un
numero limitato di attività. Tuttavia, se ci sono molte attività da elaborare, altri approcci sono più efficienti. Per
ulteriori informazioni ed esempi, vedere elaborazione delle attività quando vengono completate.

Esempio completo
Il codice seguente è il testo completo del file Program.cs per l'esempio.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace ProcessTasksAsTheyFinish
{
class Program
{
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};

static Task Main() => SumPageSizesAsync();

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client)


{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}
}
}
// Example output:
// https://docs.microsoft.com/windows 25,513
// https://docs.microsoft.com/gaming 30,705
// https://docs.microsoft.com/dotnet 69,626
// https://docs.microsoft.com/dynamics365 50,756
// https://docs.microsoft.com/surface 35,519
// https://docs.microsoft.com 39,531
// https://docs.microsoft.com/azure/devops 75,837
// https://docs.microsoft.com/xamarin 60,284
// https://docs.microsoft.com/system-center 43,444
// https://docs.microsoft.com/enterprise-mobility-security 28,946
// https://docs.microsoft.com/microsoft-365 43,278
// https://docs.microsoft.com/visualstudio 31,414
// https://docs.microsoft.com/office 42,292
// https://docs.microsoft.com/azure 401,113
// https://docs.microsoft.com/graph 46,831
// https://docs.microsoft.com/education 25,098
// https://docs.microsoft.com/powershell 58,173
// https://docs.microsoft.com/aspnet/core 87,763
// https://docs.microsoft.com/sql 53,362

// Total bytes returned: 1,249,485


// Elapsed time: 00:00:02.7068725

Vedi anche
WhenAny
Programmazione asincrona con Async e await (C#)
Accesso asincrono ai file (C#)
02/11/2020 • 8 minutes to read • Edit Online

È possibile usare la funzionalità Async per accedere ai file. Con la funzionalità Async è possibile chiamare i
metodi asincroni senza usare callback o suddividere il codice in più metodi o espressioni lambda. Per rendere
asincrono il codice sincrono, è sufficiente chiamare un metodo asincrono anziché un metodo sincrono e
aggiungere alcune parole chiave al codice.
È opportuno considerare i seguenti motivi per l'aggiunta della modalità asincrona alle chiamate di accesso ai
file:
La modalità asincrona rende più reattive le applicazioni dell'interfaccia utente perché il thread dell'interfaccia
utente che avvia l'operazione può eseguire altre operazioni. Se il thread dell'interfaccia utente deve eseguire
codice che richiede molto tempo (ad esempio, più di 50 millisecondi), l'interfaccia utente può bloccarsi fino al
completamento dell'I/O e il thread dell'interfaccia utente può nuovamente elaborare l'input di tastiera e
mouse e altri eventi.
La modalità asincrona migliora la scalabilità di ASP.NET e di altre applicazioni basate su server, riducendo la
necessità di thread. Se l'applicazione usa un thread dedicato per ogni risposta e vengono gestite
contemporaneamente mille richieste, sono necessari mille thread. Le operazioni asincrone spesso non
devono usare un thread durante l'attesa. Usano brevemente il thread di completamento di I/O esistente alla
fine.
La latenza di un'operazione di accesso ai file può essere molto bassa nelle condizioni attuali, ma aumentare
notevolmente in futuro. Ad esempio, un file può essere spostato in tutt'altra parte del mondo.
Il sovraccarico aggiuntivo dovuto all'uso della funzionalità Async è ridotto.
Le attività asincrone possono essere facilmente eseguite in parallelo.

USA classi appropriate


Negli esempi semplici di questo argomento vengono illustrati File.WriteAllTextAsync e File.ReadAllTextAsync . Per
il controllo finito sulle operazioni di I/O dei file, usare la FileStream classe, che ha un'opzione che determina
l'esecuzione di operazioni di i/o asincrone a livello del sistema operativo. Utilizzando questa opzione, è possibile
evitare di bloccare un thread del pool di thread in molti casi. Per abilitare questa opzione, specificare
l'argomento useAsync=true o options=FileOptions.Asynchronous nella chiamata al costruttore.
Non è possibile usare questa opzione con StreamReader e StreamWriter se si aprono direttamente specificando
un percorso di file. È tuttavia possibile usare questa opzione se si fornisce loro un oggetto Stream aperto dalla
classe FileStream. Le chiamate asincrone sono più veloci nelle app dell'interfaccia utente anche se un thread del
pool di thread è bloccato, perché il thread dell'interfaccia utente non è bloccato durante l'attesa.

Scrivi testo
Negli esempi seguenti viene scritto un testo in un file. Ad ogni istruzione await il metodo termina
immediatamente. Completato l'I/O del file, il metodo riprende in corrispondenza dell'istruzione che segue
l'istruzione await. Il modificatore async si trova nella definizione di metodi che usano l'istruzione await.
Esempio semplice
public async Task SimpleWriteAsync()
{
string filePath = "simple.txt";
string text = $"Hello World";

await File.WriteAllTextAsync(filePath, text);


}

Esempio di controllo finito

public async Task ProcessWriteAsync()


{
string filePath = "temp.txt";
string text = $"Hello World{Environment.NewLine}";

await WriteTextAsync(filePath, text);


}

async Task WriteTextAsync(string filePath, string text)


{
byte[] encodedText = Encoding.Unicode.GetBytes(text);

using var sourceStream =


new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);

await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);


}

L'esempio originale usa l'istruzione await sourceStream.WriteAsync(encodedText, 0, encodedText.Length); , che è


una contrazione delle due istruzioni seguenti:

Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);


await theTask;

La prima istruzione restituisce un'attività e determina l'avvio dell'elaborazione dei file. La seconda istruzione con
await induce il metodo a terminare immediatamente e restituire un'attività diversa. Al termine dell'elaborazione
dei file l'esecuzione torna quindi all'istruzione che segue await.

Leggi testo
Gli esempi seguenti leggono il testo da un file.
Esempio semplice

public async Task SimpleReadAsync()


{
string filePath = "simple.txt";
string text = await File.ReadAllTextAsync(filePath);

Console.WriteLine(text);
}

Esempio di controllo finito


Il testo viene memorizzato nel buffer e, in questo caso, inserito in un oggetto StringBuilder. A differenza
dell'esempio precedente, la valutazione di await produce un valore. Il ReadAsync metodo restituisce un Task
<Int32>, quindi la valutazione di await produce un Int32 valore numRead dopo il completamento
dell'operazione. Per altre informazioni, vedere Tipi restituiti asincroni (C#).

public async Task ProcessReadAsync()


{
try
{
string filePath = "temp.txt";
if (File.Exists(filePath) != false)
{
string text = await ReadTextAsync(filePath);
Console.WriteLine(text);
}
else
{
Console.WriteLine($"file not found: {filePath}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

async Task<string> ReadTextAsync(string filePath)


{
using var sourceStream =
new FileStream(
filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true);

var sb = new StringBuilder();

byte[] buffer = new byte[0x1000];


int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}

return sb.ToString();
}

I/O asincrono parallelo


Negli esempi seguenti viene illustrata l'elaborazione parallela mediante la scrittura di 10 file di testo.
Esempio semplice
public async Task SimpleParallelWriteAsync()
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();

for (int index = 11; index <= 20; ++ index)


{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";
string text = $"In file {index}{Environment.NewLine}";

writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}

await Task.WhenAll(writeTaskList);
}

Esempio di controllo finito


Per ogni file, il metodo WriteAsync restituisce un'attività che successivamente viene aggiunta a un elenco di
attività. L'istruzione await Task.WhenAll(tasks); termina il metodo e riprende all'interno del metodo quando
l'elaborazione dei file è completata per tutte le attività.
L'esempio chiude tutte le istanze di FileStream in un blocco finally dopo il completamento delle attività. Se
invece ogni oggetto FileStream è stato creato in un'istruzione using , è possibile che l'oggetto FileStream
venga eliminato prima del completamento dell'attività.
Un incremento delle prestazioni è quasi interamente dovuto all'elaborazione parallela e non all'elaborazione
asincrona. I vantaggi di modalità asincrona sono la mancata associazione di più thread e il collegamento del
thread dell'interfaccia utente.
public async Task ProcessMulitpleWritesAsync()
{
IList<FileStream> sourceStreams = new List<FileStream>();

try
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();

for (int index = 1; index <= 10; ++ index)


{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";

string text = $"In file {index}{Environment.NewLine}";


byte[] encodedText = Encoding.Unicode.GetBytes(text);

var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);

Task writeTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);


sourceStreams.Add(sourceStream);

writeTaskList.Add(writeTask);
}

await Task.WhenAll(writeTaskList);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}

Quando si usano i metodi WriteAsync e ReadAsync, è possibile specificare uno struct CancellationToken, che
consente di annullare l'operazione nel corso del flusso. Per altre informazioni, vedere Annullamento in thread
gestiti.

Vedere anche
Programmazione asincrona con Async e await (C#)
Tipi restituiti asincroni (C#)
Attributi (C#)
02/11/2020 • 9 minutes to read • Edit Online

Gli attributi offrono un metodo efficace per l'associazione di metadati o informazioni dichiarative con il codice
(assembly, tipi, metodi, proprietà e così via). Dopo aver associato un attributo a un'entità di programma, in fase
di esecuzione è possibile eseguire una query su tale attributo usando una tecnica denominata reflection. Per
altre informazioni, vedere Reflection (C#).
Di seguito sono riportate le proprietà degli attributi:
Gli attributi aggiungono metadati al programma. I metadati sono informazioni relative ai tipi definiti in un
programma. Tutti gli assembly .NET contengono un set specificato di metadati che descrive i tipi e membri
dei tipi definiti nell'assembly. È possibile aggiungere attributi personalizzati per specificare altre informazioni
eventualmente necessarie. Per altre informazioni, vedere Creazione di attributi personalizzati (C#).
È possibile applicare uno o più attributi a interi assembly, moduli o elementi di programma di minori
dimensioni, ad esempio classi e proprietà.
Gli attributi possono accettare argomenti nello stesso modo dei metodi e delle proprietà.
Il programma può esaminare i propri metadati oppure i metadati di un altro programma tramite reflection.
Per altre informazioni, vedere Accesso agli attributi tramite reflection (C#).

Utilizzo di attributi
È possibile usare attributi nella maggior parte delle dichiarazioni, anche se la validità di un attributo specifico
può essere limitata ad alcuni tipi di dichiarazione. Per specificare un attributo in C# inserire il nome dell'attributo
racchiuso tra parentesi quadre ([]) sopra la dichiarazione dell'entità a cui è applicato.
Nell'esempio seguente l'attributo SerializableAttribute viene usato per applicare una caratteristica specifica a
una classe:

[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}

Un metodo con l'attributo DllImportAttribute è dichiarato come nell'esempio seguente:

[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();

In una dichiarazione è possibile inserire più attributi, come illustrato nell'esempio seguente:

using System.Runtime.InteropServices;

void MethodA([In][Out] ref double x) { }


void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }

Alcuni attributi possono essere specificati più volte per una stessa entità. Un esempio di attributo multiuso è
ConditionalAttribute:

[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}

NOTE
Per convenzione tutti i nomi di attributo terminano con la parola "Attribute", in modo che sia possibile distinguerli da altri
elementi delle librerie .NET. Tuttavia, quando gli attributi vengono usati nel codice, non è necessario specificare il suffisso
Attribute. Ad esempio, [DllImport] è equivalente a [DllImportAttribute] , ma DllImportAttribute è il nome
effettivo dell'attributo nella libreria di classi .NET.

Parametri degli attributi


Diversi attributi dispongono di parametri, che possono essere posizionali, senza nome o denominati. I parametri
posizionali devono essere specificati in un determinato ordine e non possono essere omessi. I parametri
denominati sono facoltativi e possono essere specificati in qualsiasi ordine. I parametri posizionali vengono
specificati per primi. I tre attributi seguenti, ad esempio, sono equivalenti:

[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

Il primo parametro, ovvero il nome della DLL, è posizionale ed è sempre specificato per primo. Gli altri parametri
sono denominati. In questo caso, entrambi i parametri denominati sono impostati automaticamente su false e
possono quindi essere omessi. I parametri posizionali corrispondono ai parametri del costruttore dell'attributo. I
parametri denominati o facoltativi corrispondono a proprietà o campi dell'attributo. Per informazioni sui valori
predefiniti dei parametri, fare riferimento alla documentazione di ciascun attributo.
Destinazioni degli attributi
La destinazione di un attributo è l'entità a cui tale attributo viene applicato. Un attributo, ad esempio, può essere
applicato a una classe, a un metodo particolare o a un intero assembly. Per impostazione predefinita, un
attributo viene applicato all'elemento che lo segue. È tuttavia possibile identificare in modo esplicito, ad
esempio, se un attributo viene applicato a un metodo, al relativo parametro o al relativo valore restituito.
Per identificare in modo esplicito la destinazione di un attributo, usare la sintassi seguente:

[target : attribute-list]

Nella tabella seguente sono elencati i possibili valori di target .

VA LO RE DI DEST IN A Z IO N E SI A P P L IC A A

assembly Intero assembly

module Modulo di assembly corrente

field Campo in una classe o uno struct

event Evento
VA LO RE DI DEST IN A Z IO N E SI A P P L IC A A

method Metodo o funzioni di accesso alle proprietà get e set

param Parametri del metodo o parametri della funzione di accesso


alla proprietà set

property Proprietà

return Valore restituito di un metodo, un indicizzatore di proprietà


o una funzione di accesso alla proprietà get

type Struct, classe, interfaccia, enumeratore o delegato

È necessario specificare il valore di destinazione field per applicare un attributo al campo sottostante creato
per una proprietà implementata automaticamente.
Nell'esempio seguente viene illustrato come applicare attributi ad assembly e moduli. Per altre informazioni,
vedere Attributi comuni (C#).

using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]

Nell'esempio seguente viene illustrato come applicare gli attributi a metodi, parametri di metodo e valori
restituiti dal metodo in C#.

// default: applies to method


[ValidatedContract]
int Method1() { return 0; }

// applies to method
[method: ValidatedContract]
int Method2() { return 0; }

// applies to return value


[return: ValidatedContract]
int Method3() { return 0; }

NOTE
Indipendentemente dalle destinazioni in cui l'oggetto ValidatedContract è definito come valido, è necessario specificare
la destinazione return , anche se ValidatedContract viene definito in modo da essere valido solo per i valori restituiti.
In altre parole, il compilatore non usa le informazioni AttributeUsage per risolvere le destinazioni degli attributi
ambigue. Per altre informazioni, vedere AttributeUsage (C#).

Usi comuni degli attributi


Di seguito vengono elencati alcuni degli usi comuni degli attributi nel codice:
Contrassegno dei metodi mediante l'attributo WebMethod nei servizi Web per indicare che è possibile
chiamare il metodo tramite il protocollo SOAP. Per altre informazioni, vedere WebMethodAttribute.
Descrizione della procedura di marshalling dei parametri del metodo durante l'interazione con il codice
nativo. Per altre informazioni, vedere MarshalAsAttribute.
Descrizione delle proprietà COM per classi, metodi e interfacce.
Chiamata al codice non gestito che usa la classe DllImportAttribute.
Descrizione dell'assembly con indicazione di titolo, versione, descrizione o marchio.
Descrizione dei membri della classe da serializzare per la persistenza.
Descrizione della procedura di mapping tra membri di una classe e nodi XML per la serializzazione XML.
Descrizione dei requisiti di sicurezza per i metodi.
Definizione delle caratteristiche usate per garantire la sicurezza.
Controllo delle ottimizzazioni tramite il compilatore JIT (Just-In-Time), in modo da garantire un semplice
debug del codice.
Recupero di informazioni relative al chiamante di un metodo.

Sezioni correlate
Per altre informazioni, vedere:
Creazione di attributi personalizzati (C#)
Accessing Attributes by Using Reflection (C#) (Accesso agli attributi con reflection (C#))
Come creare un'Unione C/C++ usando gli attributi (C#)
Attributi comuni (C#)
Informazioni sul chiamante (C#)

Vedere anche
Guida per programmatori C#
Reflection (C#)
Attributes (Attributi)
Uso degli attributi in C#
Creazione di attributi personalizzati (C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile creare attributi personalizzati definendo una classe Attribute, ovvero una classe che deriva
direttamente o indirettamente da Attribute, la quale semplifica e rende più rapida l'identificazione delle
definizioni degli attributi nei metadati. Si supponga di voler contrassegnare i tipi con il nome del
programmatore che ha scritto il tipo. Si potrebbe definire una classe Attribute Author personalizzata:

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
]
public class AuthorAttribute : System.Attribute
{
private string name;
public double version;

public AuthorAttribute(string name)


{
this.name = name;
version = 1.0;
}
}

Il nome della classe AuthorAttribute è il nome dell'attributo, Author , più il Attribute suffisso. La classe deriva
da System.Attribute , quindi è una classe Attribute personalizzata. I parametri del costruttore sono parametri
posizionali dell'attributo personalizzato. In questo esempio name è un parametro posizionale. Tutte le proprietà
o i campi pubblici di lettura/scrittura sono parametri denominati. In questo caso version è l'unico parametro
denominato. Si noti l'uso dell'attributo AttributeUsage per rendere l'attributo Author valido solo per la classe e
le dichiarazioni struct .
È possibile usare questo nuovo attributo come indicato di seguito:

[Author("P. Ackerman", version = 1.1)]


class SampleClass
{
// P. Ackerman's code goes here...
}

AttributeUsage ha un parametro denominato, AllowMultiple , che consente di rendere un attributo


personalizzato monouso o multiuso. Nell'esempio di codice seguente viene creato un attributo multiuso.

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class AuthorAttribute : System.Attribute

Nell'esempio vengono applicati a una classe più attributi dello stesso tipo.
[Author("P. Ackerman", version = 1.1)]
[Author("R. Koch", version = 1.2)]
class SampleClass
{
// P. Ackerman's code goes here...
// R. Koch's code goes here...
}

Vedere anche
System.Reflection
Guida per programmatori C#
Scrittura di attributi personalizzati
Reflection (C#)
Attributi (C#)
Accessing Attributes by Using Reflection (C#) (Accesso agli attributi con reflection (C#))
AttributeUsage (C#)
Accesso agli attributi tramite reflection (C#)
02/11/2020 • 3 minutes to read • Edit Online

La possibilità di definire attributi personalizzati e inserirli nel codice sorgente sarebbe di scarso valore senza un
metodo per recuperare e usare le informazioni. Tramite l'uso di reflection, è possibile recuperare le informazioni
definite con gli attributi personalizzati. Il metodo chiave è GetCustomAttributes , che restituisce una matrice di
oggetti che rappresentano gli equivalenti in fase di esecuzione degli attributi di codice sorgente. Questo metodo
ha versioni diverse sottoposte a overload. Per altre informazioni, vedere Attribute.
Una specifica di attributo come la seguente:

[Author("P. Ackerman", version = 1.1)]


class SampleClass

è concettualmente equivalente a questa:

Author anonymousAuthorObject = new Author("P. Ackerman");


anonymousAuthorObject.version = 1.1;

Il codice non viene tuttavia eseguito fino a quando non vengono eseguite query su SampleClass per gli attributi.
La chiamata a GetCustomAttributes in SampleClass causa la costruzione e l'inizializzazione di un oggetto
Author come illustrato in precedenza. Se la classe ha altri attributi, gli altri oggetti di attributo vengono costruiti
in modo analogo. GetCustomAttributes restituisce quindi l'oggetto Author e tutti gli altri oggetti di attributo in
una matrice. È quindi possibile eseguire l'iterazione di questa matrice, determinare gli attributi applicati in base
al tipo di ogni elemento della matrice ed estrarre informazioni dagli oggetti di attributo.

Esempio
Questo è un esempio completo. Un attributo personalizzato viene definito, applicato a varie entità e recuperato
tramite reflection.

// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class Author : System.Attribute
{
string name;
public double version;

public Author(string name)


{
this.name = name;

// Default value.
version = 1.0;
}

public string GetName()


{
return name;
}
}
// Class with the Author attribute.
[Author("P. Ackerman")]
public class FirstClass
{
// ...
}

// Class without the Author attribute.


public class SecondClass
{
// ...
}

// Class with multiple Author attributes.


[Author("P. Ackerman"), Author("R. Koch", version = 2.0)]
public class ThirdClass
{
// ...
}

class TestAuthorAttribute
{
static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}

private static void PrintAuthorInfo(System.Type t)


{
System.Console.WriteLine("Author information for {0}", t);

// Using reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // Reflection.

// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
}
}
/* Output:
Author information for FirstClass
P. Ackerman, version 1.00
Author information for SecondClass
Author information for ThirdClass
R. Koch, version 2.00
P. Ackerman, version 1.00
*/

Vedere anche
System.Reflection
Attribute
Guida per programmatori C#
Recupero di informazioni memorizzate negli attributi
Reflection (C#)
Attributi (C#)
Creazione di attributi personalizzati (C#)
Come creare un'Unione C/C++ usando gli attributi
(C#)
02/11/2020 • 2 minutes to read • Edit Online

Usando gli attributi, è possibile personalizzare la disposizione dei struct in memoria. Ad esempio, tramite gli
attributi StructLayout(LayoutKind.Explicit) e FieldOffset è possibile creare una struttura che in C/C++ è nota
come unione.

Esempio
In questo segmento di codice tutti i campi di TestUnion iniziano in corrispondenza della stessa posizione di
memoria.

// Add a using directive for System.Runtime.InteropServices.

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;

[System.Runtime.InteropServices.FieldOffset(0)]
public double d;

[System.Runtime.InteropServices.FieldOffset(0)]
public char c;

[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
}

Esempio
Nell'esempio seguente i campi iniziano in corrispondenza di posizioni diverse impostate in modo esplicito.
// Add a using directive for System.Runtime.InteropServices.

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[System.Runtime.InteropServices.FieldOffset(0)]
public long lg;

[System.Runtime.InteropServices.FieldOffset(0)]
public int i1;

[System.Runtime.InteropServices.FieldOffset(4)]
public int i2;

[System.Runtime.InteropServices.FieldOffset(8)]
public double d;

[System.Runtime.InteropServices.FieldOffset(12)]
public char c;

[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
}

I due campi integer, i1 e i2 , condividono le stesse posizioni di memoria di lg . Questo tipo di controllo sul
layout degli struct è utile quando si usa la chiamata di piattaforma.

Vedere anche
System.Reflection
Attribute
Guida per programmatori C#
Attributes (Attributi)
Reflection (C#)
Attributi (C#)
Creazione di attributi personalizzati (C#)
Accessing Attributes by Using Reflection (C#) (Accesso agli attributi con reflection (C#))
Raccolte (C#)
02/11/2020 • 21 minutes to read • Edit Online

Per molte applicazioni è utile creare e gestire gruppi di oggetti correlati. È possibile raggruppare gli oggetti in
due modi: creando matrici di oggetti e creando raccolte di oggetti.
Le matrici sono estremamente utili per la creazione e l'uso di un numero fisso di oggetti fortemente tipizzati. Per
altre informazioni sulle matrici, vedere Matrici.
Le raccolte consentono di lavorare in modo più flessibile con gruppi di oggetti. A differenza delle matrici, il
gruppo di oggetti con cui si lavora può aumentare e diminuire in modo dinamico in base alle esigenze
dell'applicazione. Per alcune raccolte è possibile assegnare una chiave a qualsiasi oggetto inserito nella raccolta
in modo da recuperare rapidamente l'oggetto usando la chiave.
Una raccolta è una classe. Di conseguenza, prima di poter aggiungere elementi a una nuova raccolta è
necessario dichiarare la raccolta.
Se la raccolta contiene elementi di un solo tipo di dati, è possibile usare una delle classi dello spazio dei nomi
System.Collections.Generic. In una raccolta generica viene imposta l'indipendenza dai tipi, in modo da impedire
che vengano aggiunti altri tipi di dati alla raccolta. Quando si recupera un elemento da una raccolta generica,
non è necessario determinarne il tipo di dati né convertirlo.

NOTE
Per gli esempi in questo argomento, includere le direttive using per gli spazi dei nomi System.Collections.Generic e
System.Linq .

Contenuto dell'argomento
Uso di una raccolta semplice
Tipi di raccolte
Classi System.Collections.Generic
Classi System.Collections.Concurrent
Classi System.Collections
Implementazione di una raccolta di coppie chiave/valore
Uso di LINQ per accedere a una raccolta
Ordinamento di una raccolta
Definizione di una raccolta personalizzata
Iterators

Uso di una raccolta semplice


Gli esempi in questa sezione usano la classe generica List<T>, che consente di usare un elenco di oggetti
fortemente tipizzato.
L'esempio seguente crea un elenco di stringhe, quindi esegue l'iterazione nelle stringhe usando un'istruzione
foreach.

// Create a list of strings.


var salmons = new List<string>();
salmons.Add("chinook");
salmons.Add("coho");
salmons.Add("pink");
salmons.Add("sockeye");

// Iterate through the list.


foreach (var salmon in salmons)
{
Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

Se il contenuto di una raccolta è noto in anticipo, si può usare un inizializzatore di raccolta per inizializzare la
raccolta. Per altre informazioni, vedere Inizializzatori di oggetto e di raccolta.
L'esempio seguente è identico all'esempio precedente, ma usa un inizializzatore di raccolta per aggiungere
elementi alla raccolta.

// Create a list of strings by using a


// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

// Iterate through the list.


foreach (var salmon in salmons)
{
Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

È possibile usare un'istruzione for anziché un'istruzione foreach per eseguire l'iterazione in una raccolta.
Questo è possibile mediante l'accesso agli elementi della raccolta dalla posizione di indice. L'indice degli
elementi inizia da 0 e termina in corrispondenza del numero di elementi meno 1.
Nell'esempio seguente l'iterazione negli elementi di una raccolta avviene mediante for anziché mediante
foreach .

// Create a list of strings by using a


// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

for (var index = 0; index < salmons.Count; index++)


{
Console.Write(salmons[index] + " ");
}
// Output: chinook coho pink sockeye

Nell'esempio seguente viene rimosso un elemento dalla raccolta specificando l'oggetto da rimuovere.
// Create a list of strings by using a
// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

// Remove an element from the list by specifying


// the object.
salmons.Remove("coho");

// Iterate through the list.


foreach (var salmon in salmons)
{
Console.Write(salmon + " ");
}
// Output: chinook pink sockeye

Nell'esempio seguente vengono rimossi elementi da un elenco generico. Invece di un'istruzione foreach viene
usata un'istruzione for che esegue l'iterazione in ordine decrescente. Ciò è necessario perché il metodo
RemoveAt fa sì che gli elementi dopo un elemento rimosso abbiano un valore di indice inferiore.

var numbers = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Remove odd numbers.


for (var index = numbers.Count - 1; index >= 0; index--)
{
if (numbers[index] % 2 == 1)
{
// Remove the element by specifying
// the zero-based index in the list.
numbers.RemoveAt(index);
}
}

// Iterate through the list.


// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach(
number => Console.Write(number + " "));
// Output: 0 2 4 6 8

Per il tipo di elementi in List<T>, è possibile anche definire una classe personalizzata. Nell'esempio seguente la
classe Galaxy viene usata dall'oggetto List<T> definito nel codice.
private static void IterateThroughList()
{
var theGalaxies = new List<Galaxy>
{
new Galaxy() { Name="Tadpole", MegaLightYears=400},
new Galaxy() { Name="Pinwheel", MegaLightYears=25},
new Galaxy() { Name="Milky Way", MegaLightYears=0},
new Galaxy() { Name="Andromeda", MegaLightYears=3}
};

foreach (Galaxy theGalaxy in theGalaxies)


{
Console.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears);
}

// Output:
// Tadpole 400
// Pinwheel 25
// Milky Way 0
// Andromeda 3
}

public class Galaxy


{
public string Name { get; set; }
public int MegaLightYears { get; set; }
}

Tipi di raccolte
Molte raccolte comuni vengono fornite da .NET. Ogni tipo di raccolta è progettato per uno scopo specifico.
In questa sezione sono descritte alcune classi di raccolte comuni:
Classi System.Collections.Generic
Classi System.Collections.Concurrent
Classi System.Collections
Classi System.Collections.Generic
È possibile creare una raccolta generica usando una delle classi dello spazio dei nomi
System.Collections.Generic. Una raccolta generica è utile quando ogni elemento al suo interno presenta lo
stesso tipo di dati. Una raccolta generica applica la tipizzazione forte consentendo di aggiungere soltanto i tipi di
dati desiderati.
La tabella seguente elenca alcune delle classi di uso frequente dello spazio dei nomi System.Collections.Generic:

C L A SSE DESC RIZ IO N E

Dictionary<TKey,TValue> Rappresenta una raccolta di coppie chiave/valore organizzate


in base alla chiave.

List<T> Rappresenta un elenco di oggetti accessibile in base


all'indice. Fornisce metodi per la ricerca, l'ordinamento e la
modifica degli elenchi.

Queue<T> Rappresenta una raccolta di oggetti FIFO (First-In First-Out).


C L A SSE DESC RIZ IO N E

SortedList<TKey,TValue> Rappresenta una raccolta di coppie chiave/valore ordinate


per chiave in base all'implementazione IComparer<T>
associata.

Stack<T> Rappresenta una raccolta di oggetti LIFO (Last-In First-Out).

Per altre informazioni, vedere Tipi di raccolte comunemente utilizzate, Selezione di una classe Collection e
System.Collections.Generic.
Classi System.Collections.Concurrent
In .NET Framework 4 e versioni successive, le raccolte nello System.Collections.Concurrent spazio dei nomi
forniscono operazioni thread-safe efficienti per accedere agli elementi della raccolta da più thread.
Le classi dello spazio dei nomi System.Collections.Concurrent devono essere usate in sostituzione dei tipi
corrispondenti negli spazi dei nomi System.Collections.Generic e System.Collections ogni volta che più thread
accedono contemporaneamente alla raccolta. Per altre informazioni, vedere Raccolte thread-safe e
System.Collections.Concurrent.
Alcune classi incluse nello spazio dei nomi System.Collections.Concurrent sono BlockingCollection<T>,
ConcurrentDictionary<TKey,TValue>, ConcurrentQueue<T> e ConcurrentStack<T>.
Classi System.Collections
Le classi dello spazio dei nomi System.Collections non archiviano gli elementi come oggetti tipizzati in modo
specifico, ma come oggetti di tipo Object .
Quando possibile, usare le raccolte generiche degli spazi dei nomi System.Collections.Generic o
System.Collections.Concurrent al posto dei tipi legacy dello spazio dei nomi System.Collections .
La tabella seguente elenca alcune classi di uso frequente nello spazio dei nomi System.Collections :

C L A SSE DESC RIZ IO N E

ArrayList Rappresenta una matrice di oggetti le cui dimensioni sono


incrementate in modo dinamico in base alle esigenze.

Hashtable Rappresenta una raccolta di coppie chiave/valore organizzate


in base al codice hash della chiave.

Queue Rappresenta una raccolta di oggetti FIFO (First-In First-Out).

Stack Rappresenta una raccolta di oggetti LIFO (Last-In First-Out).

Lo spazio dei nomi System.Collections.Specialized offre classi di raccolte fortemente tipizzate e specializzate, ad
esempio raccolte di sole stringhe, dizionari ibridi e dizionari a elenchi collegati.

Implementazione di una raccolta di coppie chiave/valore


La raccolta generica Dictionary<TKey,TValue> consente di accedere agli elementi di una raccolta usando la
chiave di ogni elemento. Ogni aggiunta al dizionario è costituita da un valore e dalla chiave associata
corrispondente. Il recupero di un valore tramite la relativa chiave è un'operazione veloce, perché la classe
Dictionary viene implementata come tabella hash.

L'esempio seguente crea una raccolta Dictionary ed esegue l'iterazione nel dizionario usando un'istruzione
foreach .

private static void IterateThruDictionary()


{
Dictionary<string, Element> elements = BuildDictionary();

foreach (KeyValuePair<string, Element> kvp in elements)


{
Element theElement = kvp.Value;

Console.WriteLine("key: " + kvp.Key);


Console.WriteLine("values: " + theElement.Symbol + " " +
theElement.Name + " " + theElement.AtomicNumber);
}
}

private static Dictionary<string, Element> BuildDictionary()


{
var elements = new Dictionary<string, Element>();

AddToDictionary(elements, "K", "Potassium", 19);


AddToDictionary(elements, "Ca", "Calcium", 20);
AddToDictionary(elements, "Sc", "Scandium", 21);
AddToDictionary(elements, "Ti", "Titanium", 22);

return elements;
}

private static void AddToDictionary(Dictionary<string, Element> elements,


string symbol, string name, int atomicNumber)
{
Element theElement = new Element();

theElement.Symbol = symbol;
theElement.Name = name;
theElement.AtomicNumber = atomicNumber;

elements.Add(key: theElement.Symbol, value: theElement);


}

public class Element


{
public string Symbol { get; set; }
public string Name { get; set; }
public int AtomicNumber { get; set; }
}

Per usare invece un inizializzatore di raccolta per compilare la raccolta Dictionary , è possibile sostituire i
metodi BuildDictionary e AddToDictionary con il metodo seguente.

private static Dictionary<string, Element> BuildDictionary2()


{
return new Dictionary<string, Element>
{
{"K",
new Element() { Symbol="K", Name="Potassium", AtomicNumber=19}},
{"Ca",
new Element() { Symbol="Ca", Name="Calcium", AtomicNumber=20}},
{"Sc",
new Element() { Symbol="Sc", Name="Scandium", AtomicNumber=21}},
{"Ti",
new Element() { Symbol="Ti", Name="Titanium", AtomicNumber=22}}
};
}
L'esempio seguente usa il metodo ContainsKey e la proprietà Item[] di Dictionary per trovare rapidamente un
elemento in base alla chiave. La proprietà Item consente di accedere a un elemento nella raccolta elements
usando il codice elements[symbol] in C#.

private static void FindInDictionary(string symbol)


{
Dictionary<string, Element> elements = BuildDictionary();

if (elements.ContainsKey(symbol) == false)
{
Console.WriteLine(symbol + " not found");
}
else
{
Element theElement = elements[symbol];
Console.WriteLine("found: " + theElement.Name);
}
}

L'esempio seguente usa invece il metodo TryGetValue per individuare rapidamente un elemento in base alla
chiave.

private static void FindInDictionary2(string symbol)


{
Dictionary<string, Element> elements = BuildDictionary();

Element theElement = null;


if (elements.TryGetValue(symbol, out theElement) == false)
Console.WriteLine(symbol + " not found");
else
Console.WriteLine("found: " + theElement.Name);
}

Uso di LINQ per accedere a una raccolta


È possibile usare LINQ (Language-Integrated Query) per accedere alle raccolte. Le query LINQ forniscono
funzionalità di filtro, ordinamento e raggruppamento. Per altre informazioni, vedere Introduzione con LINQ in
C#.
Nell'esempio seguente viene eseguita una query LINQ su un oggetto List generico. La query LINQ restituisce
una raccolta diversa che contiene i risultati.
private static void ShowLINQ()
{
List<Element> elements = BuildList();

// LINQ Query.
var subset = from theElement in elements
where theElement.AtomicNumber < 22
orderby theElement.Name
select theElement;

foreach (Element theElement in subset)


{
Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
}

// Output:
// Calcium 20
// Potassium 19
// Scandium 21
}

private static List<Element> BuildList()


{
return new List<Element>
{
{ new Element() { Symbol="K", Name="Potassium", AtomicNumber=19}},
{ new Element() { Symbol="Ca", Name="Calcium", AtomicNumber=20}},
{ new Element() { Symbol="Sc", Name="Scandium", AtomicNumber=21}},
{ new Element() { Symbol="Ti", Name="Titanium", AtomicNumber=22}}
};
}

public class Element


{
public string Symbol { get; set; }
public string Name { get; set; }
public int AtomicNumber { get; set; }
}

Ordinamento di una raccolta


L'esempio seguente illustra una procedura per ordinare una raccolta. Nell'esempio vengono ordinate le istanze
della classe Car archiviate in un oggetto List<T>. La classe Car implementa l'interfaccia IComparable<T>, che
richiede l'implementazione del metodo CompareTo.
Ogni chiamata al metodo CompareTo effettua un confronto unico che viene usato per l'ordinamento. Il codice
scritto dall'utente presente nel metodo CompareTo restituisce un valore per ogni confronto dell'oggetto corrente
con un altro oggetto. Il valore restituito è minore di zero se l'oggetto corrente è inferiore all'altro oggetto,
maggiore di zero se l'oggetto corrente è superiore all'altro oggetto e zero se sono uguali. In questo modo è
possibile definire nel codice i criteri di maggiore, minore e uguale.
Nel metodo ListCars l'istruzione cars.Sort() ordina l'elenco. Questa chiamata al metodo Sort di List<T>
determina la chiamata automatica al metodo CompareTo per gli oggetti Car in List .

private static void ListCars()


{
var cars = new List<Car>
{
{ new Car() { Name = "car1", Color = "blue", Speed = 20}},
{ new Car() { Name = "car2", Color = "red", Speed = 50}},
{ new Car() { Name = "car3", Color = "green", Speed = 10}},
{ new Car() { Name = "car4", Color = "blue", Speed = 50}},
{ new Car() { Name = "car5", Color = "blue", Speed = 30}},
{ new Car() { Name = "car5", Color = "blue", Speed = 30}},
{ new Car() { Name = "car6", Color = "red", Speed = 60}},
{ new Car() { Name = "car7", Color = "green", Speed = 50}}
};

// Sort the cars by color alphabetically, and then by speed


// in descending order.
cars.Sort();

// View all of the cars.


foreach (Car thisCar in cars)
{
Console.Write(thisCar.Color.PadRight(5) + " ");
Console.Write(thisCar.Speed.ToString() + " ");
Console.Write(thisCar.Name);
Console.WriteLine();
}

// Output:
// blue 50 car4
// blue 30 car5
// blue 20 car1
// green 50 car7
// green 10 car3
// red 60 car6
// red 50 car2
}

public class Car : IComparable<Car>


{
public string Name { get; set; }
public int Speed { get; set; }
public string Color { get; set; }

public int CompareTo(Car other)


{
// A call to this method makes a single comparison that is
// used for sorting.

// Determine the relative order of the objects being compared.


// Sort by color alphabetically, and then by speed in
// descending order.

// Compare the colors.


int compare;
compare = String.Compare(this.Color, other.Color, true);

// If the colors are the same, compare the speeds.


if (compare == 0)
{
compare = this.Speed.CompareTo(other.Speed);

// Use descending order for speed.


compare = -compare;
}

return compare;
}
}

Definizione di una raccolta personalizzata


È possibile definire una raccolta implementando l'interfaccia IEnumerable<T> o IEnumerable.
Sebbene sia possibile definire una raccolta personalizzata, in genere è preferibile usare invece le raccolte incluse
in .NET, descritte nei tipi di raccolte precedenti in questo articolo.
L'esempio seguente definisce una classe di raccolte personalizzata denominata AllColors . Questa classe
implementa l'interfaccia IEnumerable che richiede l'implementazione del metodo GetEnumerator.
Il metodo GetEnumerator restituisce un'istanza della classe ColorEnumerator . ColorEnumerator implementa
l'interfaccia IEnumerator che richiede l'implementazione della proprietà Current e dei metodi MoveNext e Reset.

private static void ListColors()


{
var colors = new AllColors();

foreach (Color theColor in colors)


{
Console.Write(theColor.Name + " ");
}
Console.WriteLine();
// Output: red blue green
}

// Collection class.
public class AllColors : System.Collections.IEnumerable
{
Color[] _colors =
{
new Color() { Name = "red" },
new Color() { Name = "blue" },
new Color() { Name = "green" }
};

public System.Collections.IEnumerator GetEnumerator()


{
return new ColorEnumerator(_colors);

// Instead of creating a custom enumerator, you could


// use the GetEnumerator of the array.
//return _colors.GetEnumerator();
}

// Custom enumerator.
private class ColorEnumerator : System.Collections.IEnumerator
{
private Color[] _colors;
private int _position = -1;

public ColorEnumerator(Color[] colors)


{
_colors = colors;
}

object System.Collections.IEnumerator.Current
{
get
{
return _colors[_position];
}
}

bool System.Collections.IEnumerator.MoveNext()
{
_position++;
return (_position < _colors.Length);
}

void System.Collections.IEnumerator.Reset()
{
_position = -1;
}
}
}

// Element class.
public class Color
{
public string Name { get; set; }
}

Iterators
Un iteratore viene usato per eseguire un'iterazione personalizzata in una raccolta. Un iteratore può essere un
metodo o una funzione di accesso get . Un iteratore usa un'istruzione yield return per restituire ogni elemento
della raccolta, uno alla volta.
Per chiamare un iteratore usare un'istruzione foreach. Ogni iterazione del ciclo foreach chiama l'iteratore.
Quando si raggiunge un'istruzione yield return nell'iteratore, viene restituita un'espressione e viene
mantenuta la posizione corrente nel codice. L'esecuzione viene ripresa a partire da quella posizione la volta
successiva che viene chiamato l'iteratore.
Per altre informazioni, vedere Iteratori (C#).
Nell'esempio seguente viene usato un metodo iteratore. Il metodo iteratore dispone di un'istruzione
yield return all'interno di un ciclo for. Nel metodo ListEvenNumbers ogni iterazione del corpo dell'istruzione
foreach crea una chiamata al metodo iteratore, che procede all'istruzione yield return successiva.

private static void ListEvenNumbers()


{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
Console.WriteLine();
// Output: 6 8 10 12 14 16 18
}

private static IEnumerable<int> EvenSequence(


int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (var number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
yield return number;
}
}
}

Vedere anche
Inizializzatori di oggetto e di raccolta
Concetti di programmazione (C#)
Option Strict Statement
LINQ to Objects (C#)
Parallel LINQ (PLINQ)
Raccolte e strutture di dati
Selezione di una classe Collection
Confronti e ordinamenti all'interno delle raccolte
Quando usare le raccolte generiche
Covarianza e controvarianza (C#)
02/11/2020 • 5 minutes to read • Edit Online

In C#, covarianza e controvarianza abilitano la conversione implicita del riferimento per i tipi di matrice, i tipi
delegati e gli argomenti di tipo generico. La covarianza mantiene la compatibilità dell'assegnazione e la
controvarianza la inverte.
Il codice seguente illustra la differenza tra la compatibilità dell'assegnazione, covarianza e controvarianza.

// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;

// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;

// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;

La covarianza per le matrici consente la conversione implicita di una matrice di un tipo più derivato in una
matrice di un tipo meno derivato. Ma questa operazione non è indipendente dai tipi, come illustrato
nell'esempio di codice seguente.

object[] array = new String[10];


// The following statement produces a run-time exception.
// array[0] = 10;

Il supporto di covarianza e controvarianza per i gruppi di metodi consente la corrispondenza delle firme del
metodo con i tipi delegati. Ciò consente di assegnare ai delegati non solo i metodi con firme corrispondenti, ma
anche i metodi che restituiscono più tipi derivati (covarianza) o accettano parametri con meno tipi derivati
(controvarianza) rispetto a quelli specificati dal tipo delegato. Per altre informazioni, vedere Varianza nei delegati
(C#) e Uso della varianza nei delegati (C#).
L'esempio di codice seguente illustra il supporto di covarianza e controvarianza per i gruppi di metodi.
static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }


static void SetString(string str) { }

static void Test()


{
// Covariance. A delegate specifies a return type as object,
// but you can assign a method that returns a string.
Func<object> del = GetString;

// Contravariance. A delegate specifies a parameter type as string,


// but you can assign a method that takes an object.
Action<string> del2 = SetObject;
}

In .NET Framework 4 e versioni successive, C# supporta la covarianza e la controvarianza nelle interfacce e nei
delegati generici e consente la conversione implicita di parametri di tipo generico. Per altre informazioni, vedere
Varianza nelle interfacce generiche (C#) e Varianza nei delegati (C#).
L'esempio di codice seguente illustra la conversione implicita del riferimento per le interfacce generiche.

IEnumerable<String> strings = new List<String>();


IEnumerable<Object> objects = strings;

Le interfacce o i delegati generici sono detti varianti se i relativi parametri generici vengono dichiarati come
covarianti o controvarianti. C# consente di creare i propri delegati e interfacce varianti. Per altre informazioni,
vedere Creazione di interfacce generiche varianti (C#) e Varianza nei delegati (C#).

Argomenti correlati
T ITO LO DESC RIZ IO N E

Varianza nelle interfacce generiche (C#) Illustra la covarianza e la controvarianza nelle interfacce
generiche e fornisce un elenco di interfacce generiche variant
in .NET.

Creazione di interfacce generiche varianti (C#) Spiega come creare interfacce varianti personalizzate.

Uso della varianza nelle interfacce per le raccolte generiche Spiega come il supporto di covarianza e controvarianza nelle
(C#) interfacce IEnumerable<T> e IComparable<T> consente di
riutilizzare il codice.

Varianza nei delegati (C#) Illustra la covarianza e la controvarianza nei delegati generici
e non generici e fornisce un elenco di delegati generici
Variant in .NET

Uso della varianza nei delegati (C#) Spiega come usare il supporto di covarianza e
controvarianza nei delegati non generici per la
corrispondenza delle firme del metodo con i tipi delegati.

Uso della varianza per i delegati generici Func e Action (C#) Spiega come il supporto di covarianza e controvarianza nei
delegati Func e Action consente di riutilizzare il codice.
Varianza nelle interfacce generiche (C#)
02/11/2020 • 4 minutes to read • Edit Online

In .NET framework 4 è stato introdotto il supporto della varianza per diverse interfacce generiche esistenti. Il
supporto della varianza consente la conversione implicita delle classi che implementano tali interfacce.
A partire da .NET Framework 4, le interfacce seguenti sono varianti:
IEnumerable<T> (T è covariante)
IEnumerator<T> (T è covariante)
IQueryable<T> (T è covariante)
IGrouping<TKey,TElement> ( TKey e TElement sono covarianti)
IComparer<T> (T è controvariante)
IEqualityComparer<T> (T è controvariante)
IComparable<T> (T è controvariante)
A partire da .NET Framework 4.5, le interfacce seguenti sono varianti:
IReadOnlyList<T> (T è covariante)
IReadOnlyCollection<T> (T è covariante)
La covarianza consente a un metodo di avere un tipo restituito più derivato rispetto a quello definito dal
parametro di tipo generico dell'interfaccia. Per illustrare la funzionalità di covarianza, considerare le seguenti
interfacce generiche: IEnumerable<Object> e IEnumerable<String> . L'interfaccia IEnumerable<String> non eredita
l'interfaccia IEnumerable<Object> . Tuttavia, il tipo String eredita il tipo Object e in alcuni casi è opportuno
assegnare gli oggetti di ogni interfaccia all'altra. Vedere il codice di esempio seguente.

IEnumerable<String> strings = new List<String>();


IEnumerable<Object> objects = strings;

Nelle versioni precedenti di .NET Framework, questo codice causa un errore di compilazione in C# e, se
Option Strict è attivato, in Visual Basic. Ora è invece possibile usare strings anziché objects , come illustrato
nell'esempio precedente, poiché l'interfaccia IEnumerable<T> è covariante.
La controvarianza consente a un metodo di avere tipi di argomenti meno derivati rispetto a quelli specificati dal
parametro generico dell'interfaccia. Per illustrare la controvarianza, si supponga di aver creato una classe
BaseComparer per confrontare le istanze della classe BaseClass . La classe BaseComparer implementa l'interfaccia
IEqualityComparer<BaseClass> . Poiché l'interfaccia BaseClass è ora controvariante, è possibile usare
IEqualityComparer<T> per confrontare le istanze delle classi che ereditano la classe BaseComparer . Vedere il
codice di esempio seguente.
// Simple hierarchy of classes.
class BaseClass { }
class DerivedClass : BaseClass { }

// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
public int GetHashCode(BaseClass baseInstance)
{
return baseInstance.GetHashCode();
}
public bool Equals(BaseClass x, BaseClass y)
{
return x == y;
}
}
class Program
{
static void Test()
{
IEqualityComparer<BaseClass> baseComparer = new BaseComparer();

// Implicit conversion of IEqualityComparer<BaseClass> to


// IEqualityComparer<DerivedClass>.
IEqualityComparer<DerivedClass> childComparer = baseComparer;
}
}

Per altri esempi, vedere Uso della varianza nelle interfacce per le raccolte generiche (C#).
La varianza nelle interfacce generiche è supportata solo per i tipi di riferimento. I tipi di valore non supportano
la varianza. Ad esempio, non è possibile convertire IEnumerable<int> in modo implicito in IEnumerable<object>
perché i valori integer sono rappresentati da un tipo di valore.

IEnumerable<int> integers = new List<int>();


// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;

È anche importante ricordare che le classi che implementano le interfacce varianti sono comunque invariabili.
Ad esempio, sebbene List<T> implementi l'interfaccia covariante IEnumerable<T>, non è possibile convertire
List<String> in List<Object> in modo implicito. come illustra l'esempio di codice riportato di seguito.

// The following line generates a compiler error


// because classes are invariant.
// List<Object> list = new List<String>();

// You can use the interface object instead.


IEnumerable<Object> listObjects = new List<String>();

Vedere anche
Uso della varianza nelle interfacce per le raccolte generiche (C#)
Creazione di interfacce generiche varianti (C#)
Interfacce generiche
Varianza nei delegati (C#)
Creazione di interfacce generiche varianti (C#)
02/11/2020 • 8 minutes to read • Edit Online

È possibile dichiarare parametri di tipo generico nelle interfacce come covarianti o controvarianti. La covarianza
consente ai metodi di interfaccia di avere tipi restituiti più derivati rispetto a quelli definiti dai parametri di tipo
generico. La controvarianza consente ai metodi di interfaccia di avere tipi di argomenti meno derivati rispetto a
quelli specificati dai parametri generici. Un'interfaccia generica che ha parametri di tipo generico varianti e
covarianti è definita variante.

NOTE
In .NET framework 4 è stato introdotto il supporto della varianza per diverse interfacce generiche esistenti. Per l'elenco
delle interfacce variant in .NET, vedere varianza nelle interfacce generiche (C#).

Dichiarazione delle interfacce generiche varianti


È possibile dichiarare le interfacce generiche varianti usando le parole chiave in e out per i parametri di tipo
generico.

IMPORTANT
I parametri ref , in e out in C# non possono essere varianti. Anche i tipi di valore non supportano la varianza.

È possibile dichiarare un parametro di tipo generico covariante usando la parola chiave out . Il tipo covariante
deve soddisfare le condizioni seguenti:
Il tipo viene usato solo come tipo restituito dei metodi di interfaccia e non come tipo di argomenti del
metodo. Come illustrato nell'esempio seguente, il tipo R viene dichiarato covariante.

interface ICovariant<out R>


{
R GetSomething();
// The following statement generates a compiler error.
// void SetSomething(R sampleArg);

Esiste un'eccezione a questa regola. Se si usa un delegato generico controvariante come parametro di
metodo, è possibile usare il tipo come parametro di tipo generico per il delegato. Questo aspetto è
illustrato nell'esempio seguente dal tipo R . Per altre informazioni, vedere Uso della varianza nei delegati
(C#) e Uso della varianza per i delegati generici Func e Action (C#).

interface ICovariant<out R>


{
void DoSomething(Action<R> callback);
}

Il tipo non viene usato come vincolo generico per i metodi di interfaccia. Questa situazione è illustrata nel
codice seguente.
interface ICovariant<out R>
{
// The following statement generates a compiler error
// because you can use only contravariant or invariant types
// in generic constraints.
// void DoSomething<T>() where T : R;
}

È possibile dichiarare un parametro di tipo generico controvariante usando la parola chiave in . Il tipo
controvariante può essere usato solo come tipo di argomenti del metodo e non come tipo restituito dei metodi
di interfaccia. Il tipo controvariante può essere usato anche per i vincoli generici. Il codice seguente illustra come
dichiarare un'interfaccia controvariante e usare un vincolo generico per uno dei relativi metodi.

interface IContravariant<in A>


{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}

È anche possibile supportare sia la covarianza che la controvarianza nella stessa interfaccia, ma per parametri di
tipo diverso, come illustrato nell'esempio di codice seguente.

interface IVariant<out R, in A>


{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}

Implementazione di interfacce generiche varianti


Implementare le interfacce generiche varianti nelle classi usando la stessa sintassi usata per le interfacce
invariabili. L'esempio di codice seguente illustra come implementare un'interfaccia covariante in una classe
generica.

interface ICovariant<out R>


{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}

Le classi che implementano le interfacce varianti sono invariabili. Si consideri il codice di esempio seguente.
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.


SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

Estensione di interfacce generiche varianti


Quando si estende un'interfaccia generica variante è necessario usare le parole chiave in e out per specificare
in modo esplicito se l'interfaccia derivata supporta la varianza. Il compilatore non deduce la varianza
dall'interfaccia che viene estesa. Si considerino ad esempio le interfacce seguenti.

interface ICovariant<out T> { }


interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

Nell'interfaccia IInvariant<T> il parametro di tipo generico T è invariabile, mentre in IExtCovariant<out T> il


parametro di tipo è covariante, anche se entrambe le interfacce estendono la stessa interfaccia. La stessa regola
viene applicata ai parametri di tipo generico controvarianti.
È possibile creare un'interfaccia che estende sia l'interfaccia in cui il parametro di tipo generico T è covariante,
sia l'interfaccia in cui è controvariante se nell'interfaccia che viene estesa il parametro di tipo generico T è
invariabile, come illustra l'esempio di codice riportato di seguito.

interface ICovariant<out T> { }


interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

Tuttavia, se un parametro di tipo generico T è dichiarato covariante in una sola interfaccia, non è possibile
dichiararlo controvariante nell'interfaccia da estendere o viceversa, come illustra l'esempio di codice riportato di
seguito.

interface ICovariant<out T> { }


// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

Evitare le ambiguità
Quando si implementano le interfacce generiche varianti, la varianza può talvolta causare ambiguità. Tale
ambiguità dovrebbe essere evitata.
Ad esempio, se si implementa in modo esplicito la stessa interfaccia generica variante con parametri di tipo
generico diversi in una sola classe, è possibile creare ambiguità. Il compilatore non genera un errore in questo
caso, ma non viene specificato quale implementazione dell'interfaccia verrà scelta in fase di esecuzione. Questa
ambiguità potrebbe causare bug sottili nel codice. Osservare l'esempio di codice seguente.
// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity


// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
{
Console.WriteLine("Cat");
// Some code.
return null;
}

IEnumerator IEnumerable.GetEnumerator()
{
// Some code.
return null;
}

IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
{
Console.WriteLine("Dog");
// Some code.
return null;
}
}
class Program
{
public static void Test()
{
IEnumerable<Animal> pets = new Pets();
pets.GetEnumerator();
}
}

In questo esempio non viene specificato in che modo il metodo pets.GetEnumerator sceglie tra Cat e Dog . Ciò
potrebbe causare problemi nel codice.

Vedere anche
Varianza nelle interfacce generiche (C#)
Uso della varianza per i delegati generici Func e Action (C#)
Uso della varianza nelle interfacce per le raccolte
generiche (C#)
02/11/2020 • 3 minutes to read • Edit Online

Un'interfaccia covariante consente ai propri metodi di restituire più tipi derivati rispetto a quelli specificati
nell'interfaccia. Un'interfaccia controvariante consente ai propri metodi di accettare parametri di meno tipi
derivati rispetto a quelli specificati nell'interfaccia.
In .NET Framework 4 diverse interfacce già esistenti sono diventate covarianti e controvarianti. Tra queste sono
inclusi i tipi IEnumerable<T> e IComparable<T>. In questo modo è possibile riutilizzare i metodi che funzionano
con raccolte generiche di tipi di base per le raccolte di tipi derivati.
Per un elenco di interfacce variant in .NET, vedere varianza nelle interfacce generiche (C#).

Convertire le raccolte generiche


L'esempio seguente illustra i vantaggi del supporto di covarianza nell'interfaccia IEnumerable<T>. Il metodo
PrintFullName accetta una raccolta del tipo IEnumerable<Person> come parametro. Tuttavia, è possibile
riutilizzarlo per una raccolta del tipo IEnumerable<Employee> perché Employee eredita Person .

// Simple hierarchy of classes.


public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

class Program
{
// The method has a parameter of the IEnumerable<Person> type.
public static void PrintFullName(IEnumerable<Person> persons)
{
foreach (Person person in persons)
{
Console.WriteLine("Name: {0} {1}",
person.FirstName, person.LastName);
}
}

public static void Test()


{
IEnumerable<Employee> employees = new List<Employee>();

// You can pass IEnumerable<Employee>,


// although the method expects IEnumerable<Person>.

PrintFullName(employees);

}
}

Confrontare le raccolte generiche


L'esempio seguente illustra i vantaggi del supporto di controvarianza nell'interfaccia IComparer<T>. La classe
PersonComparer implementa l'interfaccia IComparer<Person> . Tuttavia, è possibile riutilizzare questa classe per
confrontare una sequenza di oggetti del tipo Employee perché Employee eredita Person .

// Simple hierarchy of classes.


public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

// The custom comparer for the Person type


// with standard implementations of Equals()
// and GetHashCode() methods.
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;
return x.FirstName == y.FirstName && x.LastName == y.LastName;
}
public int GetHashCode(Person person)
{
if (Object.ReferenceEquals(person, null)) return 0;
int hashFirstName = person.FirstName == null
? 0 : person.FirstName.GetHashCode();
int hashLastName = person.LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}

class Program
{

public static void Test()


{
List<Employee> employees = new List<Employee> {
new Employee() {FirstName = "Michael", LastName = "Alexander"},
new Employee() {FirstName = "Jeff", LastName = "Price"}
};

// You can pass PersonComparer,


// which implements IEqualityComparer<Person>,
// although the method expects IEqualityComparer<Employee>.

IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());

foreach (var employee in noduplicates)


Console.WriteLine(employee.FirstName + " " + employee.LastName);
}
}

Vedere anche
Varianza nelle interfacce generiche (C#)
Varianza nei delegati (C#)
02/11/2020 • 9 minutes to read • Edit Online

In .NET framework 3.5 è stato introdotto il supporto della varianza per la corrispondenza delle firme del metodo
con i tipi di delegati in tutti i delegati in C#. Ciò significa che è possibile assegnare ai delegati non solo i metodi
con firme corrispondenti, ma anche i metodi che restituiscono più tipi derivati (covarianza) o accettano
parametri con meno tipi derivati (controvarianza) rispetto a quelli specificati dal tipo di delegato. Sono inclusi sia
i delegati generici che quelli non generici.
Ad esempio, si consideri il codice seguente, che ha due classi e due delegati: generico e non generico.

public class First { }


public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

Quando si creano i delegati dei tipi SampleDelegate o SampleGenericDelegate<A, R> è possibile assegnare uno
qualsiasi dei metodi seguenti ai delegati.

// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }

// The return type is more derived.


public static Second ASecondRSecond(Second second)
{ return new Second(); }

// The argument type is less derived.


public static First AFirstRFirst(First first)
{ return new First(); }

// The return type is more derived


// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }

L'esempio di codice seguente viene illustra la conversione implicita tra la firma del metodo e il tipo di delegato.

// Assigning a method with a matching signature


// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;

// Assigning a method with a matching signature to a generic delegate.


// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

Per altri esempi, vedere Uso della varianza nei delegati (C#) e Uso della varianza per i delegati generici Func e
Action (C#).

Varianza nei parametri di tipo generico


In .NET Framework 4 o versioni successive è possibile abilitare la conversione implicita tra delegati, in modo che
i delegati generici con tipi diversi specificati dai parametri di tipo generico possano essere assegnati l'uno
all'altro, se i tipi vengono ereditati reciprocamente come richiesto dalla varianza.
Per abilitare la conversione implicita, è necessario dichiarare esplicitamente i parametri generici in un delegato
come covariante o controvariante usando la parola chiave in o out .
L'esempio di codice seguente illustra come creare un delegato con un parametro di tipo generico covariante.

// Type T is declared covariant by using the out keyword.


public delegate T SampleGenericDelegate <out T>();

public static void Test()


{
SampleGenericDelegate <String> dString = () => " ";

// You can assign delegates to each other,


// because the type T is declared covariant.
SampleGenericDelegate <Object> dObject = dString;
}

Se si usa solo il supporto della varianza per la corrispondenza delle firme del metodo con i tipi delegati e non si
usano le parole chiave in e out , è possibile che in alcuni casi si possano creare istanze di delegati con metodi
o espressioni lambda identici, ma non è possibile assegnare un delegato a un altro.
Nell'esempio di codice seguente non è possibile convertire in modo esplicito SampleGenericDelegate<String> in
SampleGenericDelegate<Object> , sebbene String erediti Object . È possibile correggere questo problema
contrassegnando il parametro generico T con la parola chiave out .

public delegate T SampleGenericDelegate<T>();

public static void Test()


{
SampleGenericDelegate<String> dString = () => " ";

// You can assign the dObject delegate


// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";

// The following statement generates a compiler error


// because the generic type T is not marked as covariant.
// SampleGenericDelegate <Object> dObject = dString;

Delegati generici con parametri di tipo Variant in .NET


In .NET framework 4 è stato introdotto il supporto della varianza per i parametri di tipo generico in diversi
delegati generici esistenti:
Delegati Action dallo spazio dei nomi System, ad esempio Action<T> e Action<T1,T2>
Delegati Func dallo spazio dei nomi System, ad esempio Func<TResult> e Func<T,TResult>
Delegato Predicate<T>.
Delegato Comparison<T>.
Delegato Converter<TInput,TOutput>.
Per altre informazioni ed esempi, vedere Uso della varianza per i delegati generici Func e Action (C#).
Dichiarare parametri di tipo variante nei delegati generici
Se un delegato generico ha parametri di tipo generico covariante o controvariante, può essere indicato come un
delegato generico variante.
È possibile dichiarare un parametro di tipo generico covariante in un delegato generico usando la parola chiave
out . Il tipo covariante può essere usato solo come tipo restituito del metodo e non come tipo di argomenti del
metodo. Nell'esempio di codice seguente viene illustrato come dichiarare un delegato generico covariante.

public delegate R DCovariant<out R>();

È possibile dichiarare un parametro di tipo generico controvariante in un delegato generico usando la parola
chiave in . Il tipo controvariante può essere usato solo come tipo di argomenti del metodo e non come tipo
restituito del metodo. Nell'esempio di codice seguente viene illustrato come dichiarare un delegato generico
controvariante.

public delegate void DContravariant<in A>(A a);

IMPORTANT
I parametri ref , in e out in C# non possono essere contrassegnati come varianti.

È anche possibile supportare sia la varianza che la covarianza nello stesso delegato, ma per parametri di tipo
diverso. come illustrato nell'esempio seguente.

public delegate R DVariant<in A, out R>(A a);

Creare un'istanza e richiamare delegati generici varianti


È possibile creare un'istanza e richiamare delegati varianti con la stessa procedura con cui si crea un'istanza e si
richiamano i delegati invariabili. Nell'esempio seguente viene creata un'istanza del delegato da un'espressione
lambda.

DVariant<String, String> dvariant = (String str) => str + " ";


dvariant("test");

Combinare delegati generici varianti


Non combinare delegati Variant. Il metodo Combine non supporta la conversione dei delegati varianti e prevede
che i delegati siano esattamente dello stesso tipo. Questo può causare un'eccezione in fase di esecuzione
quando si combinano delegati usando il metodo Combine o l'operatore + , come illustrato nell'esempio di
codice seguente.
Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time.
// Action<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);

Varianza nei parametri di tipo generico per tipi di valore e riferimento


La varianza per i parametri di tipo generico è supportata solo per i tipi di riferimento. Ad esempio,
DVariant<int> non può essere convertito implicitamente in DVariant<Object> o DVariant<long> , poiché integer
è un tipo di valore.
L'esempio seguente dimostra che la varianza nei parametri di tipo generico non è supportata per i tipi di valore.

// The type T is covariant.


public delegate T DVariant<out T>();

// The type T is invariant.


public delegate T DInvariant<T>();

public static void Test()


{
int i = 0;
DInvariant<int> dInt = () => i;
DVariant<int> dVariantInt = () => i;

// All of the following statements generate a compiler error


// because type variance in generic parameters is not supported
// for value types, even if generic type parameters are declared variant.
// DInvariant<Object> dObject = dInt;
// DInvariant<long> dLong = dInt;
// DVariant<Object> dVariantObject = dVariantInt;
// DVariant<long> dVariantLong = dVariantInt;
}

Vedere anche
Generics
Uso della varianza per i delegati generici Func e Action (C#)
Come combinare delegati (delegati multicast)
Uso della varianza nei delegati (C#)
02/11/2020 • 3 minutes to read • Edit Online

Quando si assegna un metodo a un delegato, covarianza e controvarianza offrono flessibilità per la


corrispondenza di un tipo delegato con una firma di metodo. La covarianza consente a un metodo di avere un
tipo restituito più derivato di quello definito nel delegato. La controvarianza consente un metodo con tipi di
parametro meno derivati rispetto a quelli del tipo delegato.

Esempio 1: covarianza
Descrizione
In questo esempio viene illustrato in che modo si possono usare i delegati con i metodi che hanno tipi restituiti
derivati dal tipo restituito nella firma del delegato. Il tipo di dati restituito da DogsHandler è di tipo Dogs , che
deriva dal tipo Mammals definito nel delegato.
Codice

class Mammals {}
class Dogs : Mammals {}

class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();

public static Mammals MammalsHandler()


{
return null;
}

public static Dogs DogsHandler()


{
return null;
}

static void Test()


{
HandlerMethod handlerMammals = MammalsHandler;

// Covariance enables this assignment.


HandlerMethod handlerDogs = DogsHandler;
}
}

Esempio 2: controvarianza
Descrizione
In questo esempio viene illustrato in che modo si possono usare i delegati con i metodi che hanno parametri i
cui tipi rappresentano tipi di base del tipo di parametro della firma del delegato. Con la controvarianza è
possibile usare un solo gestore eventi anziché gestori separati. Nell'esempio seguente vengono usati due
delegati:
Un delegato KeyEventHandler che definisce la firma dell'evento Button.KeyDown. La firma è:
public delegate void KeyEventHandler(object sender, KeyEventArgs e)

Un delegato MouseEventHandler che definisce la firma dell'evento Button.MouseClick. La firma è:

public delegate void MouseEventHandler(object sender, MouseEventArgs e)

Nell'esempio viene definito un gestore eventi con un parametro EventArgs e viene usato per gestire entrambi
gli eventi Button.KeyDown e Button.MouseClick . Ciò è possibile perché EventArgs è un tipo di base sia di
KeyEventArgs che di MouseEventArgs.
Codice

// Event handler that accepts a parameter of the EventArgs type.


private void MultiHandler(object sender, System.EventArgs e)
{
label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
InitializeComponent();

// You can use a method that has an EventArgs parameter,


// although the event expects the KeyEventArgs parameter.
this.button1.KeyDown += this.MultiHandler;

// You can use the same method


// for an event that expects the MouseEventArgs parameter.
this.button1.MouseClick += this.MultiHandler;

Vedere anche
Varianza nei delegati (C#)
Uso della varianza per i delegati generici Func e Action (C#)
Uso della varianza per i delegati generici Func e
Action (C#)
02/11/2020 • 2 minutes to read • Edit Online

In questi esempi viene illustrato come usare la covarianza e la controvarianza nei delegati generici Func e
Action per consentire il riutilizzo dei metodi e offrire maggiore flessibilità nel codice.

Per altre informazioni sulla covarianza e la controvarianza, vedere Varianza nei delegati (C#).

Uso dei delegati con parametri di tipo covariante


L'esempio seguente illustra i vantaggi del supporto di covarianza nei delegati generici Func . Il metodo
FindByTitle accetta un parametro di tipo String e restituisce un oggetto di tipo Employee . Tuttavia, è possibile
assegnare questo metodo al delegato Func<String, Person> perché Employee eredita Person .

// Simple hierarchy of classes.


public class Person { }
public class Employee : Person { }
class Program
{
static Employee FindByTitle(String title)
{
// This is a stub for a method that returns
// an employee that has the specified title.
return new Employee();
}

static void Test()


{
// Create an instance of the delegate without using variance.
Func<String, Employee> findEmployee = FindByTitle;

// The delegate expects a method to return Person,


// but you can assign it a method that returns Employee.
Func<String, Person> findPerson = FindByTitle;

// You can also assign a delegate


// that returns a more derived type
// to a delegate that returns a less derived type.
findPerson = findEmployee;

}
}

Uso dei delegati con parametri di tipo controvariante


L'esempio seguente illustra i vantaggi del supporto di controvarianza nei delegati generici Action . Il metodo
AddToContacts accetta un parametro di tipo Person . Tuttavia, è possibile assegnare questo metodo al delegato
Action<Employee> perché Employee eredita Person .
public class Person { }
public class Employee : Person { }
class Program
{
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}

static void Test()


{
// Create an instance of the delegate without using variance.
Action<Person> addPersonToContacts = AddToContacts;

// The Action delegate expects


// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;

// You can also assign a delegate


// that accepts a less derived parameter to a delegate
// that accepts a more derived parameter.
addEmployeeToContacts = addPersonToContacts;
}
}

Vedere anche
Covarianza e controvarianza (C#)
Generics
Alberi delle espressioni (C#)
02/11/2020 • 7 minutes to read • Edit Online

Gli alberi delle espressioni rappresentano codice in una struttura dei dati simile a un albero, dove ogni nodo è
un'espressione, ad esempio una chiamata al metodo o un'operazione binaria come x < y .
È possibile compilare ed eseguire codice rappresentato dagli alberi delle espressioni. In questo modo è possibile
modificare dinamicamente codice eseguibile, eseguire query LINQ in vari database e creare query dinamiche.
Per altre informazioni sugli alberi delle espressioni in LINQ, vedere come usare gli alberi delle espressioni per la
compilazione di query dinamiche (C#).
Gli alberi delle espressioni vengono utilizzati anche in Dynamic Language Runtime (DLR) per fornire
interoperabilità tra linguaggi dinamici e .NET e consentire ai writer del compilatore di creare alberi delle
espressioni anziché MSIL (Microsoft Intermediate Language). Per altre informazioni su DLR, vedere Dynamic
Language Runtime Overview (Panoramica su Dynamic Language Runtime).
È possibile creare un albero delle espressioni tramite il compilatore di C# o di Visual Basic in base a
un'espressione lambda anonima o creare tali alberi di espressioni manualmente tramite il nome spazio
System.Linq.Expressions.

Creazione di alberi delle espressioni da espressioni lambda


Quando un'espressione lambda viene assegnata a una variabile di tipo Expression<TDelegate>, il compilatore
genera codice per compilare un albero delle espressioni che rappresenta l'espressione lambda.
Il compilatore di C# può generare alberi delle espressioni solo da espressioni lambda, o lambda su una sola riga.
Non possono analizzare espressioni lambda dell'istruzione (o le espressioni lambda a più righe). Per altre
informazioni sulle espressioni lambda in C#, vedere Espressioni lambda.
Gli esempi di codice seguenti illustrano in che modo il compilatore di C# crea un albero delle espressioni che
rappresenta l'espressione lambda num => num < 5 .

Expression<Func<int, bool>> lambda = num => num < 5;

Creazione di alberi delle espressioni tramite l'API


Per creare alberi delle espressioni tramite l'API, usare la classe Expression. Questa classe contiene metodi factory
statici che creano nodi degli alberi delle espressioni di tipi specifici, ad esempio ParameterExpression, che
rappresenta una variabile o un parametro, o MethodCallExpression, che rappresenta una chiamata al metodo.
Anche ParameterExpression, MethodCallExpression e gli altri tipi specifici delle espressioni sono definiti nello
spazio dei nomi System.Linq.Expressions. Questi tipi derivano dal tipo astratto Expression.
L'esempio di codice seguente illustra come creare un albero delle espressioni che rappresenti l'espressione
lambda num => num < 5 usando l'API.
// Add the following using directive to your code file:
// using System.Linq.Expressions;

// Manually build the expression tree for


// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });

In .NET Framework 4 o nelle versioni successive l'API degli alberi delle espressioni supporta anche assegnazioni
ed espressioni del flusso di controllo quali cicli, blocchi condizionali e blocchi try-catch . Tramite l'API è
possibile creare alberi delle espressioni più complessi rispetto a quelli che è possibile creare da espressioni
lambda con il compilatore di C#. L'esempio seguente illustra come creare un albero delle espressioni che calcola
il fattoriale di un numero.

// Creating a parameter expression.


ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.


ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.


LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.


BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);

// Compile and execute an expression tree.


int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

Per altre informazioni, vedere l'articolo Generating Dynamic Methods with Expression Trees in Visual Studio
2010 (Generazione di metodi dinamici con alberi delle espressioni in Visual Studio 2010), valido anche per le
versioni successive di Visual Studio.
Analisi degli alberi delle espressioni
L'esempio di codice seguente illustra come scomporre nei vari componenti l'albero delle espressioni che
rappresenta l'espressione lambda num => num < 5 .

// Add the following using directive to your code file:


// using System.Linq.Expressions;

// Create an expression tree.


Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.


ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",


param.Name, left.Name, operation.NodeType, right.Value);

// This code produces the following output:

// Decomposed expression: num => num LessThan 5

Non modificabilità degli alberi delle espressioni


Gli alberi delle espressioni devono essere non modificabili. Ciò significa che per modificare un albero delle
espressioni è necessario costruirne uno nuovo copiando quello esistente e sostituendone i nodi. È possibile
usare un visitatore dell'albero delle espressioni per attraversare l'albero delle espressioni esistente. Per ulteriori
informazioni, vedere come modificare alberi delle espressioni (C#).

Compilazione degli alberi delle espressioni


Il tipo Expression<TDelegate> fornisce il metodo Compile che compila il codice rappresentato da un albero delle
espressioni in un delegato eseguibile.
L'esempio di codice seguente illustra come compilare un albero delle espressioni ed eseguire il codice risultante.

// Creating an expression tree.


Expression<Func<int, bool>> expr = num => num < 5;

// Compiling the expression tree into a delegate.


Func<int, bool> result = expr.Compile();

// Invoking the delegate and writing the result to the console.


Console.WriteLine(result(4));

// Prints True.

// You can also use simplified syntax


// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));

// Also prints True.

Per altre informazioni, vedere How to Execute Expression Trees (C#).

Vedere anche
System.Linq.Expressions
Come eseguire gli alberi delle espressioni (C#)
Come modificare gli alberi delle espressioni (C#)
Espressioni lambda
Panoramica di Dynamic Language Runtime
Concetti di programmazione (C#)
Come eseguire gli alberi delle espressioni (C#)
02/11/2020 • 2 minutes to read • Edit Online

In questo argomento viene illustrato come eseguire un albero delle espressioni. L'esecuzione di un albero delle
espressioni può restituire un valore o può eseguire solo un'azione, ad esempio la chiamata a un metodo.
Possono essere eseguite solo gli alberi delle espressioni che rappresentano espressioni lambda. Gli alberi delle
espressioni che rappresentano espressioni lambda sono di tipo LambdaExpression o Expression<TDelegate>.
Per eseguire gli alberi delle espressioni, chiamare il metodo Compile per creare un delegato eseguibile e quindi
richiamare il delegato.

NOTE
Se il tipo del delegato non è noto, ovvero l'espressione lambda è di tipo LambdaExpression e non Expression<TDelegate>,
è necessario chiamare il metodo DynamicInvoke sul delegato invece che richiamarlo direttamente.

Se un albero delle espressioni non rappresenta un'espressione lambda, è possibile creare una nuova
espressione lambda con l'albero delle espressioni originale come corpo, chiamando il metodo
Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>). Sarà quindi possibile eseguire
l'espressione lambda come descritto precedentemente in questa sezione.

Esempio
Nell'esempio di codice seguente viene descritto come eseguire un albero delle espressioni che rappresenta
l'elevamento di un numero a una potenza mediante la creazione e l'esecuzione di un'espressione lambda. Verrà
visualizzato il risultato che rappresenta il numero elevato a potenza.

// The expression tree to execute.


BinaryExpression be = Expression.Power(Expression.Constant(2D), Expression.Constant(3D));

// Create a lambda expression.


Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);

// Compile the lambda expression.


Func<double> compiledExpression = le.Compile();

// Execute the lambda expression.


double result = compiledExpression();

// Display the result.


Console.WriteLine(result);

// This code produces the following output:


// 8

Compilazione del codice


Includere lo spazio dei nomi System.Linq.Expressions.

Vedere anche
Alberi delle espressioni (C#)
Come modificare gli alberi delle espressioni (C#)
Come modificare gli alberi delle espressioni (C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo argomento viene illustrato come modificare un albero delle espressioni. Gli alberi delle espressioni
non sono modificabili, il che significa che non possono essere modificati direttamente. Per modificare un albero
delle espressioni, è necessario creare una copia dell'albero esistente e solo in seguito apportare le modifiche
necessarie. È possibile usare la classe ExpressionVisitor per attraversare un albero delle espressioni esistente e
copiare ogni nodo visitato.
Per modificare un albero delle espressioni
1. Creare un nuovo progetto di applicazione console .
2. Aggiungere al file una direttiva using per lo spazio dei nomi System.Linq.Expressions .
3. Aggiungere la classe AndAlsoModifier al progetto.

public class AndAlsoModifier : ExpressionVisitor


{
public Expression Modify(Expression expression)
{
return Visit(expression);
}

protected override Expression VisitBinary(BinaryExpression b)


{
if (b.NodeType == ExpressionType.AndAlso)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);

// Make this binary expression an OrElse operation instead of an AndAlso operation.


return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull,
b.Method);
}

return base.VisitBinary(b);
}
}

La classe eredita la classe ExpressionVisitor ed è specializzata per modificare le espressioni che


rappresentano operazioni AND condizionali. Modifica tali operazioni da un'operazione AND condizionale
a un'operazione OR condizionale. A tale scopo, la classe esegue l'override del metodo VisitBinary del tipo
di base, perché le espressioni AND condizionali sono rappresentate come espressioni binarie. Se
l'espressione che viene passata al metodo VisitBinary rappresenta un'operazione AND condizionale, il
codice costruisce una nuova espressione che contiene l'operatore condizionale OR anziché l'operatore
condizionale AND . Se l'espressione che viene passata a VisitBinary non rappresenta un'operazione
AND condizionale, il metodo rimanda all'implementazione della classe base. I metodi della classe base
costruiscono nodi uguali agli alberi delle espressione passati, ma i sottoalberi dei nodi vengono sostituiti
con gli alberi delle espressioni che vengono generati in modo ricorsivo dal visitatore.
4. Aggiungere al file una direttiva using per lo spazio dei nomi System.Linq.Expressions .
5. Aggiungere codice al metodo Main nel file Program.cs per creare un albero delle espressioni e passarlo
al metodo che lo modificherà.
Expression<Func<string, bool>> expr = name => name.Length > 10 && name.StartsWith("G");
Console.WriteLine(expr);

AndAlsoModifier treeModifier = new AndAlsoModifier();


Expression modifiedExpr = treeModifier.Modify((Expression) expr);

Console.WriteLine(modifiedExpr);

/* This code produces the following output:

name => ((name.Length > 10) && name.StartsWith("G"))


name => ((name.Length > 10) || name.StartsWith("G"))
*/

Il codice crea un'espressione che contiene un'operazione AND condizionale. Viene quindi creata
un'istanza della classe AndAlsoModifier e l'espressione viene passata al metodo Modify della classe. Sia
l'albero delle espressioni originale che quello modificato vengono inclusi nell'output per illustrare le
modifiche.
6. Compilare l'applicazione ed eseguirla.

Vedere anche
Come eseguire gli alberi delle espressioni (C#)
Alberi delle espressioni (C#)
Come usare gli alberi delle espressioni per la
compilazione di query dinamiche (C#)
02/11/2020 • 5 minutes to read • Edit Online

In LINQ gli alberi delle espressioni vengono usati per rappresentare query strutturate destinate alle origini dati
che implementano IQueryable<T>. Il provider LINQ, ad esempio, implementa l'interfaccia IQueryable<T> per
l'esecuzione di query su archivi dati relazionali. Il compilatore C# compila le query destinate a tali origini dati nel
codice di un albero delle espressioni in runtime. Il provider di query può quindi percorrere la struttura dei dati
dell'albero delle espressioni e convertirla in un linguaggio di query adatto all'origine dati.
Gli alberi delle espressioni vengono usati in LINQ anche per rappresentare espressioni lambda assegnate a
variabili di tipo Expression<TDelegate>.
Questo argomento descrive come usare gli alberi delle espressioni per creare query LINQ dinamiche. Le query
dinamiche sono utili quando le specifiche di una query non sono note in fase di compilazione. Supponiamo ad
esempio che un'applicazione offra un'interfaccia utente che consente all'utente finale di specificare uno o più
predicati per filtrare i dati. Per usare LINQ per l'esecuzione di query e creare query LINQ in runtime,
un'applicazione di questo tipo deve usare alberi delle espressioni.

Esempio
Nell'esempio seguente viene illustrato come usare alberi delle espressioni per costruire una query su un'origine
dati IQueryable e quindi eseguirla. Il codice compila un albero delle espressioni per rappresentare la query
seguente:

companies.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))


.OrderBy(company => company)

I metodi factory nello spazio dei nomi System.Linq.Expressions vengono usati per creare alberi delle espressioni
che rappresentano le espressioni che costituiscono la query complessiva. Le espressioni che rappresentano le
chiamate ai metodi degli operatori query standard fanno riferimento alle implementazioni Queryable di questi
metodi. L'albero delle espressioni finale viene passato all'implementazione CreateQuery<TElement>
(Expression) del provider dell'origine dati IQueryable per creare una query eseguibile di tipo IQueryable . I
risultati si ottengono enumerando tale variabile di query.

// Add a using directive for System.Linq.Expressions.

string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power &
Light",
"Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
"Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

// The IQueryable data to query.


IQueryable<String> queryableData = companies.AsQueryable<string>();

// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression pe = Expression.Parameter(typeof(string), "company");

// ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
// Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);

// Create an expression tree that represents the expression 'company.Length > 16'.
left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);

// Combine the expression trees to create an expression tree that represents the
// expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
Expression predicateBody = Expression.OrElse(e1, e2);

// Create an expression tree that represents the expression


// 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
// ***** End Where *****

// ***** OrderBy(company => company) *****


// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType, queryableData.ElementType },
whereCallExpression,
Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****

// Create an executable query from the expression tree.


IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

// Enumerate the results.


foreach (string company in results)
Console.WriteLine(company);

/* This code produces the following output:

Blue Yonder Airlines


City Power & Light
Coho Winery
Consolidated Messenger
Graphic Design Institute
Humongous Insurance
Lucerne Publishing
Northwind Traders
The Phone Company
Wide World Importers
*/

Questo codice usa un numero fisso di espressioni nel predicato passato al metodo Queryable.Where . È tuttavia
possibile scrivere un'applicazione che combini un numero variabile di espressioni di predicato a seconda
dall'input dell'utente. È anche possibile variare gli operatori query standard chiamati nella query in base
all'input dell'utente.

Compilazione del codice


Includere lo spazio dei nomi System.Linq.Expressions.

Vedere anche
Alberi delle espressioni (C#)
Come eseguire gli alberi delle espressioni (C#)
Specificare dinamicamente i filtri dei predicati in fase di esecuzione
Debug degli alberi delle espressioni in Visual Studio
(C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile analizzare la struttura e il contenuto degli alberi delle espressioni durante il debug delle applicazioni.
Per una rapida panoramica della struttura dell'albero delle espressioni, è possibile usare la proprietà DebugView ,
che rappresenta gli alberi delle espressioni usando una sintassi speciale. Si noti che DebugView è disponibile
solo in modalità di debug.

Dato che DebugView è una stringa, è possibile usare il Visualizzatore testo incorporato per visualizzarla su più
righe, selezionando Visualizzatore testo dall'icona della lente di ingrandimento accanto all'etichetta
DebugView .

In alternativa, è possibile installare e usare un visualizzatore personalizzato per gli alberi delle espressioni, ad
esempio:
Le espressioni leggibili (licenza mit, disponibili all' Visual Studio Marketplace), eseguono il rendering
dell'albero delle espressioni come codice C# con tema, con varie opzioni di rendering:

Il Visualizzatore dell'albero delle espressioni (licenza mit) fornisce una visualizzazione albero dell'albero
delle espressioni e dei singoli nodi:
Per aprire un visualizzatore per un albero delle espressioni
1. Fare clic sull'icona della lente di ingrandimento visualizzata accanto all'albero delle espressioni in
Suggerimenti dati , in una finestra Espressione di controllo , nella finestra Automatico o nella
finestra Variabili locali .
Viene visualizzato un elenco dei visualizzatori disponibili:

2. Fare clic sul visualizzatore da usare.

Vedere anche
Alberi delle espressioni (C#)
Debug in Visual Studio
Creare visualizzatori personalizzati
DebugView sintassi
Sintassi DebugView
18/03/2020 • 3 minutes to read • Edit Online

La proprietà DebugView (disponibile solo durante il debug) fornisce un rendering in forma di stringa degli alberi
delle espressioni. La maggior parte della sintassi è piuttosto semplice da comprendere e i casi speciali vengono
descritti nelle sezioni seguenti.
Ogni esempio è seguito da un commento del blocco, che contiene DebugView .

ParameterExpression
I nomi delle variabili System.Linq.Expressions.ParameterExpression vengono visualizzati con un simbolo $
all'inizio.
Se un parametro non ha un nome, viene assegnato un nome generato automaticamente, ad esempio $var1 o
$var2 .

Esempi

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");


/*
$num
*/

ParameterExpression numParam = Expression.Parameter(typeof(int));


/*
$var1
*/

ConstantExpression
Per gli oggetti System.Linq.Expressions.ConstantExpression che rappresentano valori interi, stringhe e null ,
viene visualizzato il valore della costante.
Per i tipi numerici che usano suffissi standard come valori letterali in C#, il suffisso viene aggiunto al valore. La
tabella seguente mostra i suffissi associati ai vari tipi numerici.

TYPE PA RO L A C H IAVE SUF F ISSO

System.UInt32 uint U

System.Int64 Lungo L

System.UInt64 Ulong UL

System.Double Doppia D

System.Single Galleggiante F

System.Decimal Decimale M

Esempi
int num = 10;
ConstantExpression expr = Expression.Constant(num);
/*
10
*/

double num = 10;


ConstantExpression expr = Expression.Constant(num);
/*
10D
*/

BlockExpression
Se il tipo di un oggetto System.Linq.Expressions.BlockExpression differisce dal tipo dell'ultima espressione nel
blocco, il tipo viene visualizzato all'interno di parentesi angolari ( < e > ). In caso contrario, il tipo dell'oggetto
BlockExpression non viene visualizzato.
Esempi

BlockExpression block = Expression.Block(Expression.Constant("test"));


/*
.Block() {
"test"
}
*/

BlockExpression block = Expression.Block(typeof(Object), Expression.Constant("test"));


/*
.Block<System.Object>() {
"test"
}
*/

LambdaExpression
Gli oggetti System.Linq.Expressions.LambdaExpression vengono visualizzati insieme ai rispettivi tipi delegato.
Se un'espressione lamda non ha un nome, viene assegnato un nome generato automaticamente, ad esempio
#Lambda1 o #Lambda2 .

Esempi

LambdaExpression lambda = Expression.Lambda<Func<int>>(Expression.Constant(1));


/*
.Lambda #Lambda1<System.Func'1[System.Int32]>() {
1
}
*/

LambdaExpression lambda = Expression.Lambda<Func<int>>(Expression.Constant(1), "SampleLambda", null);


/*
.Lambda #SampleLambda<System.Func'1[System.Int32]>() {
1
}
*/

LabelExpression
Se si specifica un valore predefinito per l'oggetto System.Linq.Expressions.LabelExpression, questo valore viene
visualizzato prima dell'oggetto System.Linq.Expressions.LabelTarget.
Il token .Label indica l'inizio dell'etichetta. Il token .LabelTarget indica la destinazione alla quale passare.
Se un'etichetta non presenta un nome, ne viene assegnato uno generato automaticamente, ad esempio #Label1
o #Label2 .
Esempi

LabelTarget target = Expression.Label(typeof(int), "SampleLabel");


BlockExpression block = Expression.Block(
Expression.Goto(target, Expression.Constant(0)),
Expression.Label(target, Expression.Constant(-1))
);
/*
.Block() {
.Goto SampleLabel { 0 };
.Label
-1
.LabelTarget SampleLabel:
}
*/

LabelTarget target = Expression.Label();


BlockExpression block = Expression.Block(
Expression.Goto(target),
Expression.Label(target)
);
/*
.Block() {
.Goto #Label1 { };
.Label
.LabelTarget #Label1:
}
*/

Operatori checked
Gli operatori checked vengono visualizzati con il simbolo # davanti all'operatore. Ad esempio, l'operatore di
addizione checked viene visualizzato come #+ .
Esempi

Expression expr = Expression.AddChecked( Expression.Constant(1), Expression.Constant(2));


/*
1 #+ 2
*/

Expression expr = Expression.ConvertChecked( Expression.Constant(10.0), typeof(int));


/*
#(System.Int32)10D
*/
Iteratori (C#)
02/11/2020 • 12 minutes to read • Edit Online

Un iteratore può essere usato per scorrere le raccolte come gli elenchi e le matrici.
Un metodo iteratore o funzione di accesso get esegue un'iterazione personalizzata su una raccolta. Un
iteratore usa l'istruzione yield return per restituire un elemento per volta. Quando viene raggiunta un'istruzione
yield return , la posizione corrente nel codice viene memorizzata. L'esecuzione viene riavviata a partire da
quella posizione la volta successiva che viene chiamata la funzione iteratore.
Si usa un metodo iteratore dal codice client tramite un'istruzione foreach o una query LINQ.
Nell'esempio seguente, la prima iterazione del ciclo foreach fa procedere l'esecuzione nel metodo iteratore
SomeNumbers fino al raggiungimento della prima istruzione yield return . Questa iterazione restituisce un
valore pari a 3 e viene mantenuta la posizione corrente nel metodo iteratore. All'iterazione successiva del ciclo,
l'esecuzione nel metodo iteratore continua da dove è stata interrotta, fermandosi ancora quando raggiunge
un'istruzione yield return . Questa iterazione restituisce un valore pari a 5 e viene ancora mantenuta la
posizione corrente nel metodo iteratore. Il ciclo termina quando si raggiunge la fine del metodo iteratore.

static void Main()


{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}

public static System.Collections.IEnumerable SomeNumbers()


{
yield return 3;
yield return 5;
yield return 8;
}

Il tipo restituito di un metodo iteratore o di una funzione di accesso get può essere IEnumerable,
IEnumerable<T>, IEnumerator o IEnumerator<T>.
È possibile utilizzare un'istruzione yield break per terminare l'iterazione.

NOTE
Per tutti gli esempi in questo argomento, ad eccezione dell'esempio di iteratore semplice, includere le istruzioni using per
gli spazi dei nomi System.Collections e System.Collections.Generic .

Iteratore semplice
L'esempio seguente contiene un'istruzione yield return singola all'interno di un ciclo for. In Main ogni
iterazione del corpo dell'istruzione foreach crea una chiamata alla funzione iteratore, che procede all'istruzione
yield return successiva.
static void Main()
{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int>


EvenSequence(int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
yield return number;
}
}
}

Creazione di una classe Collection


Nell'esempio seguente la classe DaysOfTheWeek implementa l'interfaccia IEnumerable, che richiede un metodo
GetEnumerator. Il compilatore chiama implicitamente il metodo GetEnumerator , che restituisce un IEnumerator.
Il metodo GetEnumerator restituisce una stringa alla volta usando l'istruzione yield return .

static void Main()


{
DaysOfTheWeek days = new DaysOfTheWeek();

foreach (string day in days)


{
Console.Write(day + " ");
}
// Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable


{
private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

public IEnumerator GetEnumerator()


{
for (int index = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}

Nell'esempio seguente viene creata una classe Zoo che contiene una raccolta di animali.
L'istruzione foreach che fa riferimento all'istanza della classe ( theZoo ) chiama implicitamente il metodo
GetEnumerator . Le istruzioni foreach che fanno riferimento alle proprietà Birds e Mammals usano il metodo
iteratore denominato AnimalsForType .
static void Main()
{
Zoo theZoo = new Zoo();

theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");

foreach (string name in theZoo)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros Penguin Warbler

foreach (string name in theZoo.Birds)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Penguin Warbler

foreach (string name in theZoo.Mammals)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros

Console.ReadKey();
}

public class Zoo : IEnumerable


{
// Private members.
private List<Animal> animals = new List<Animal>();

// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
}

public void AddBird(string name)


{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
}

public IEnumerator GetEnumerator()


{
foreach (Animal theAnimal in animals)
{
yield return theAnimal.Name;
}
}

// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}

public IEnumerable Birds


{
get { return AnimalsForType(Animal.TypeEnum.Bird); }
}

// Private methods.
// Private methods.
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
}

// Private class.
private class Animal
{
public enum TypeEnum { Bird, Mammal }

public string Name { get; set; }


public TypeEnum Type { get; set; }
}
}

Uso di iteratori con un elenco generico


Nell'esempio seguente la classe generica Stack<T> implementa l'interfaccia generica IEnumerable<T>. Il
metodo Push assegna valori a una matrice di tipo T . Il metodo GetEnumerator restituisce i valori della matrice
tramite l'istruzione yield return .
Oltre al metodo GetEnumerator generico, è necessario implementare anche il metodo GetEnumerator non
generico, poiché IEnumerable<T> eredita da IEnumerable. L'implementazione non generica rinvia
all'implementazione generica.
L'esempio usa iteratori denominati per supportare diversi modi di iterazione nella stessa raccolta dati. Questi
iteratori denominati sono le proprietà TopToBottom e BottomToTop e il metodo TopN .
La proprietà BottomToTop usa un iteratore in una funzione di accesso get .

static void Main()


{
Stack<int> theStack = new Stack<int>();

// Add items to the stack.


for (int number = 0; number <= 9; number++)
{
theStack.Push(number);
}

// Retrieve items from the stack.


// foreach is allowed because theStack implements IEnumerable<int>.
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0

// foreach is allowed, because theStack.TopToBottom returns IEnumerable(Of Integer).


foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0

foreach (int number in theStack.BottomToTop)


foreach (int number in theStack.BottomToTop)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9

foreach (int number in theStack.TopN(7))


{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3

Console.ReadKey();
}

public class Stack<T> : IEnumerable<T>


{
private T[] values = new T[100];
private int top = 0;

public void Push(T t)


{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
}

// This method implements the GetEnumerator method. It allows


// an instance of the class to be used in a foreach statement.
public IEnumerator<T> GetEnumerator()
{
for (int index = top - 1; index >= 0; index--)
{
yield return values[index];
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public IEnumerable<T> TopToBottom


{
get { return this; }
}

public IEnumerable<T> BottomToTop


{
get
{
for (int index = 0; index <= top - 1; index++)
{
yield return values[index];
}
}
}

public IEnumerable<T> TopN(int itemsFromTop)


{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;

for (int index = top - 1; index >= startIndex; index--)


for (int index = top - 1; index >= startIndex; index--)
{
yield return values[index];
}
}

Informazioni sulla sintassi


Un iteratore può verificarsi come metodo o funzione di accesso get . Un iteratore non può verificarsi in un
evento, in un costruttore di istanze, in un costruttore statico o in un finalizzatore statico.
Una conversione implicita deve esistere dal tipo di espressione nell' yield return istruzione all'argomento tipo
per l'oggetto IEnumerable<T> restituito dall'iteratore.
In C# un metodo iteratore non può avere parametri in , ref o out .
In C#, yield non è una parola riservata e ha un significato speciale solo quando viene usata prima di return
una break parola chiave o.

Implementazione tecnica
Anche se si scrive un iteratore come metodo, il compilatore lo traduce in una classe annidata che, in pratica, è
una macchina a stati. Questa classe tiene traccia della posizione dell'iteratore purché il ciclo foreach nel codice
client sia continuo.
Per verificare le operazioni eseguite dal compilatore, è possibile usare lo strumento Ildsam.exe per visualizzare il
codice Microsoft Intermediate Language generato per un metodo iteratore.
Quando si crea un iteratore per una classe o uno struct, non è necessario implementare l'intera interfaccia
IEnumerator. Quando il compilatore rileva l'iteratore, genera automaticamente i metodi Current , MoveNext e
Dispose dell'interfaccia IEnumerator o IEnumerator<T>.

In ogni iterazione successiva del ciclo foreach (o alla chiamata diretta a IEnumerator.MoveNext ), il corpo di
codice iteratore successivo riprende dopo la precedente istruzione yield return . Prosegue quindi fino alla
successiva istruzione yield return fino a quando non viene raggiunta la fine del corpo dell'iteratore, o fino a
quando non viene rilevata un'istruzione yield break .
Gli iteratori non supportano il metodo IEnumerator.Reset. Per eseguire di nuovo l'iterazione dall'inizio, è
necessario ottenere un nuovo iteratore. La chiamata Reset sull'iteratore restituito da un metodo iteratore genera
NotSupportedException.
Per altre informazioni, vedere Specifiche del linguaggio C#.

Uso degli iteratori


Gli iteratori consentono di mantenere la semplicità di un ciclo foreach quando è necessario usare codice
complesso per popolare una sequenza di elenco. Ciò può risultare utile per eseguire le operazioni seguenti:
Modificare la sequenza di elenco dopo la prima iterazione del ciclo foreach .
Evitare il caricamento completo di un elenco di grandi dimensioni prima della prima iterazione di un ciclo
foreach . Un esempio è un'operazione di recupero di paging per caricare un batch di righe della tabella.
Un altro esempio è il EnumerateFiles metodo, che implementa gli iteratori in .NET.
Incapsulare la generazione dell'elenco nell'iteratore. Nel metodo iteratore è possibile compilare l'elenco e
restituire quindi ogni risultato in un ciclo.
Vedere anche
System.Collections.Generic
IEnumerable<T>
foreach, in
yield
Uso di foreach con matrici
Generics
LINQ (Language-Integrated Query)
02/11/2020 • 7 minutes to read • Edit Online

LINQ (Language-Integrated Query) è il nome di un set di tecnologie basate sull'integrazione delle funzionalità di
query direttamente nel linguaggio C#. In genere, le query sui dati vengono espresse come stringhe semplici
senza il controllo dei tipi in fase di compilazione o il supporto IntelliSense. È anche necessario imparare un
linguaggio di query diverso per ogni tipo di origine dati: database SQL, documenti XML, svariati servizi Web e
così via. Con LINQ, una query è un costrutto del linguaggio di prima classe, come le classi, i metodi e gli eventi. È
possibile scrivere query su insiemi di oggetti fortemente tipizzati usando le parole chiave del linguaggio e gli
operatori comuni. La famiglia di tecnologie LINQ offre coerenza per l'esecuzione di query per oggetti (LINQ to
Objects), database relazionali (LINQ to SQL) e XML (LINQ to XML).
Per uno sviluppatore che scrive query, la parte integrata nel linguaggio più visibile di LINQ è l'espressione di
query. Le espressioni di query vengono scritte con una sintassi di query dichiarativa. Tramite la sintassi di query
è possibile eseguire operazioni di filtro, ordinamento e raggruppamento sulle origini dati usando una quantità
minima di codice. Si utilizzano gli stessi modelli di espressione di query di base per eseguire query e
trasformare i dati in database SQL, set di dati ADO.NET, documenti e flussi XML e raccolte .NET.
È possibile scrivere query LINQ in C# per database SQL Server, documenti XML, set di dati ADO.NET e qualsiasi
raccolta di oggetti che supporta IEnumerable o l'interfaccia IEnumerable<T> generica. Il supporto per LINQ
viene anche offerto da terze parti per numerosi servizi Web e altre implementazioni di database.
L'esempio seguente mostra l'operazione di query completa. L'operazione completa include la creazione di
un'origine dati, la definizione dell'espressione di query e l'esecuzione della query in un'istruzione foreach .

class LINQQueryExpressions
{
static void Main()
{

// Specify the data source.


int[] scores = new int[] { 97, 92, 81, 60 };

// Define the query expression.


IEnumerable<int> scoreQuery =
from score in scores
where score > 80
select score;

// Execute the query.


foreach (int i in scoreQuery)
{
Console.Write(i + " ");
}
}
}
// Output: 97 92 81

Nella figura seguente presa da Visual Studio viene illustrata una query LINQ completata parzialmente su un
database di SQL Server in C# e Visual Basic con controllo completo del tipo e supporto IntelliSense:
Panoramica sulle espressioni di query
Le espressioni di query possono essere usate per eseguire una query e trasformare dati da qualsiasi origine
dati abilitata per LINQ. Una sola query, ad esempio, è in grado di recuperare dati da un database SQL e di
produrre un flusso XML come output.
Le espressioni di query sono facili da gestire perché usano molti costrutti di linguaggio C# di uso comune.
Le variabili presenti in un'espressione di query sono tutte fortemente tipizzate, anche se in molti casi non è
necessario specificare il tipo in modo esplicito perché il compilatore è in grado di dedurlo. Per altre
informazioni, vedere Relazioni tra i tipi nelle operazioni di query LINQ.
Una query non viene eseguita finché non si esegue l'iterazione della variabile di query, ad esempio in
un'istruzione foreach . Per altre informazioni, vedere Introduzione alle query LINQ.
In fase di compilazione, le espressioni di query vengono convertite in chiamate al metodo dell'operatore
query standard secondo le regole definite nella specifica C#. Le query che possono essere espresse usando la
sintassi di query possono essere espresse anche usando la sintassi dei metodi. Nella maggior parte dei casi,
tuttavia, la sintassi di query è più leggibile e concisa. Per altre informazioni, vedere Specifiche del linguaggio
C# e Panoramica degli operatori di query standard.
Come regola di scrittura delle query LINQ, è consigliabile usare la sintassi di query quando possibile e la
sintassi dei metodi quando necessario. Tra le due diverse forme non esiste differenza semantica o a livello di
prestazioni. Le espressioni di query sono spesso più leggibili delle espressioni equivalenti scritte nella
sintassi dei metodi.
Per alcune operazioni di query, ad esempio Count o Max, non è presente una clausola dell'espressione di
query equivalente. Tali espressioni devono quindi essere espresse come chiamata di metodo. La sintassi dei
metodi può essere combinata con la sintassi di query in diversi modi. Per altre informazioni, vedere sintassi
di query e sintassi di metodi in LINQ.
Le espressioni di query possono essere compilate in alberi delle espressioni o in delegati, a seconda del tipo
al quale viene applicata la query. Le query IEnumerable<T> vengono compilate in delegati. Le query
IQueryable e IQueryable<T> vengono compilate in alberi delle espressioni. Per altre informazioni, vedere
Alberi delle espressioni.

Passaggi successivi
Per altre informazioni dettagliate su LINQ, iniziare ad acquisire dimestichezza con alcuni concetti di base nella
sezione introduttiva Nozioni fondamentali sulle espressioni di query e quindi leggere la documentazione per la
tecnologia LINQ a cui si è interessati:
Documenti XML: LINQ to XML
ADO.NET Entity Framework: LINQ to Entities
Raccolte, file e stringhe .NET: LINQ to Objects
Per approfondire LINQ in generale, vedere LINQ in C#.
Per iniziare a utilizzare LINQ in C#, vedere l'esercitazione Uso di LINQ.
Introduzione alle query LINQ (C#)
02/11/2020 • 11 minutes to read • Edit Online

Una query è un'espressione che recupera dati da un'origine dati. Le query sono in genere espresse in un
linguaggio di query specializzato. Nel tempo sono stati sviluppati diversi linguaggi per i vari tipi di origini dati,
ad esempio SQL per database relazionali e XQuery per XML. Gli sviluppatori hanno dovuto pertanto imparare
un nuovo linguaggio di query per ogni tipo di origine dati o formato dati supportato. LINQ semplifica questa
situazione offrendo un modello coerente per l'utilizzo dei dati in diversi tipi di origini dati e formati. In una query
LINQ si utilizzano sempre oggetti. Si utilizzano gli stessi modelli di codifica di base per eseguire query e
trasformare i dati in documenti XML, database SQL, set di dati ADO.NET, raccolte .NET e qualsiasi altro formato
per il quale è disponibile un provider LINQ.

Tre parti di un'operazione di query


Tutte le operazioni di query LINQ sono costituite da tre azioni distinte:
1. Ottenere l'origine dati.
2. Creare la query.
3. Esecuzione della query.
Nell'esempio seguente viene illustrato come le tre parti di un'operazione di query vengono espresse nel codice
sorgente. Nell'esempio viene usata una matrice di valori interi come origine dati per motivi di praticità. Gli stessi
concetti si applicano però anche ad altre origini dati. In questo argomento si fa riferimento sempre a tale
esempio.

class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;

// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}

Nella figura seguente viene illustrata l'operazione di query completa. In LINQ l'esecuzione della query è diversa
dalla query stessa. In altre parole, non è stato recuperato alcun dato semplicemente creando una variabile di
query.
Origine dati
Poiché nell'esempio precedente è stata usata una matrice come origine dati, viene supportata implicitamente
l'interfaccia generica IEnumerable<T>. Questo significa che è possibile eseguire query con LINQ. Viene eseguita
una query in un'istruzione foreach e foreach richiede IEnumerable o IEnumerable<T>. I tipi che supportano
IEnumerable<T> o un'interfaccia derivata, ad esempio l'interfaccia generica IQueryable<T> sono denominati
tipi queryable.
Un tipo queryable non richiede alcuna modifica o trattamento speciale per fungere da origine dati LINQ. Se i
dati di origine non sono già in memoria come tipi queryable, il provider LINQ deve rappresentarlo come tale. Ad
esempio, LINQ to XML carica un documento XML in un tipo XElement queryable:

// Create a data source from an XML document.


// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Con LINQ to SQL , è necessario innanzitutto creare un mapping relazionale a oggetti in fase di progettazione
manualmente o utilizzando gli strumenti di LINQ to SQL in Visual Studio. È possibile scrivere le query sugli
oggetti e in fase di esecuzione LINQ to SQL gestisce la comunicazione con il database. Nell'esempio seguente
Customers rappresenta una tabella specifica nel database e il tipo del risultato della query, IQueryable<T>,
deriva da IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.


IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;

Per ulteriori informazioni sulla creazione di tipi specifici di origini dati, vedere la documentazione relativa ai vari
provider LINQ. Tuttavia, la regola di base è molto semplice: un'origine dati LINQ è un oggetto che supporta l'
IEnumerable<T> interfaccia generica o un'interfaccia che eredita da essa.

NOTE
I tipi, ad esempio ArrayList che supportano l'interfaccia non generica IEnumerable , possono essere utilizzati anche come
origine dati LINQ. Per ulteriori informazioni, vedere come eseguire una query su un ArrayList con LINQ (C#).
Query
La query specifica le informazioni da recuperare dall'origine o dalle origini dati. Una query può anche
specificare il modo in cui ordinare, raggruppare e definire le informazioni prima che vengano restituite. Una
query viene archiviata in una variabile di query e inizializzata con un'espressione di query. Per semplificare la
scrittura delle query, in C# è stata introdotta una nuova sintassi della query.
La query nell'esempio precedente restituisce tutti i numeri pari dalla matrice di valori interi. L'espressione di
query contiene tre clausole: from , where e select . Se si ha familiarità con SQL, si sarà notato che l'ordine
delle clausole è invertito rispetto all'ordine in SQL. La clausola from specifica l'origine dati, la where clausola
applica il filtro e la select clausola specifica il tipo degli elementi restituiti. Queste e altre clausole di query
sono descritte in dettaglio nella sezione LINQ (Language Integrated Query) . Per il momento, il punto
importante è che in LINQ la variabile di query stessa non esegue alcuna azione e non restituisce alcun dato.
Archivia solo le informazioni richieste per generare i risultati quando successivamente viene eseguita la query.
Per altre informazioni sul modo in cui le query vengono costruite automaticamente, vedere Cenni preliminari
sugli operatori di query standard (C#).

NOTE
Le query possono anche essere espresse usando la sintassi del metodo. Per altre informazioni, vedere Sintassi di query e
sintassi di metodi in LINQ.

Esecuzione di query
Esecuzione posticipata
Come indicato in precedenza, la variabile di query archivia solo i comandi della query. L'esecuzione effettiva
della query è rinviata finché non si esegue l'iterazione della variabile di query in un'istruzione foreach . Questo
concetto è detto esecuzione posticipata e viene illustrato nell'esempio seguente:

// Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}

Dall'istruzione foreach vengono anche recuperati i risultati della query. Ad esempio, nella query precedente la
variabile di iterazione num contiene ogni valore (uno alla volta) della sequenza restituita.
Poiché la variabile di query stessa non contiene mai i risultati della query, è possibile eseguirla un numero
illimitato di volte. Ad esempio, è possibile avere un database che viene aggiornato continuamente mediante
un'applicazione separata. Nell'applicazione è possibile creare una query che recupera i dati più recenti ed
eseguirla ripetutamente a determinati intervalli per recuperare ogni volta risultati diversi.
Esecuzione immediata
Le query che eseguono funzioni di aggregazione su un intervallo di elementi di origine devono prima eseguire
l'iterazione in tali elementi. Esempi di tali query sono Count , Max , Average e First . Queste query vengono
eseguite senza un'istruzione foreach esplicita poiché la query stessa deve usare foreach per poter restituire
un risultato. Si noti anche che questi tipi di query restituiscono un solo valore, non una raccolta IEnumerable .
Nella query seguente viene restituito un conteggio dei numeri pari nella matrice di origine:
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;

int evenNumCount = evenNumQuery.Count();

Per forzare l'esecuzione immediata di una query e memorizzarne nella cache i risultati, è possibile chiamare i
metodi ToList o ToArray.

List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();

È anche possibile forzare l'esecuzione inserendo il ciclo foreach immediatamente dopo l'espressione di query.
Tuttavia, chiamando ToList o ToArray vengono memorizzati nella cache anche tutti i dati di un singolo oggetto
della raccolta.

Vedere anche
Nozioni di base su LINQ in C#
Procedura dettagliata: scrittura di query in C#
LINQ (Language-Integrated Query)
foreach, in
Parole chiave di query (LINQ)
LINQ e tipi generici (C#)
02/11/2020 • 3 minutes to read • Edit Online

Le query LINQ sono basate su tipi generici, introdotti nella versione 2,0 di .NET Framework. Non è necessaria
una conoscenza approfondita dei generics per poter iniziare a scrivere le query. È tuttavia importante
comprendere due concetti di base:
1. Quando si crea un'istanza di una classe di raccolte generiche, ad esempio List<T>, sostituire "T" con il tipo
di oggetti che saranno contenuti nell'elenco. Ad esempio, un elenco di stringhe viene espresso come
List<string> e un elenco di oggetti Customer viene espresso come List<Customer> . Un elenco generico
è fortemente tipizzato e offre molti vantaggi rispetto alle raccolte che archiviano gli elementi come
Object. Se si tenta di aggiungere un oggetto Customer a un oggetto List<string> , verrà generato un
errore in fase di compilazione. È semplice usare le raccolte generiche poiché non è necessario eseguire il
cast dei tipi in fase di esecuzione.
2. IEnumerable<T> è l'interfaccia che consente alle classi di raccolte generiche di essere enumerate usando
l'istruzione foreach . Le classi di raccolte generiche supportano IEnumerable<T> nello stesso modo in
cui le classi di raccolte non generiche, come ArrayList, supportano IEnumerable.
Per altre informazioni sui generics, vedere Generics.

Variabili IEnumerable<T> nelle query LINQ


Le variabili di query LINQ sono tipizzate come IEnumerable<T> o come tipi derivati, ad esempio IQueryable<T>
. Nel caso di una variabile di query tipizzata come IEnumerable<Customer> , significa semplicemente che la query,
quando eseguita, genererà una sequenza di zero o più oggetti Customer .

IEnumerable<Customer> customerQuery =
from cust in customers
where cust.City == "London"
select cust;

foreach (Customer customer in customerQuery)


{
Console.WriteLine(customer.LastName + ", " + customer.FirstName);
}

Per altre informazioni, vedere relazioni tra i tipi nelle operazioni di query LINQ.

Gestione delle dichiarazioni di tipo generico tramite il compilatore


Se si preferisce, è possibile evitare la sintassi generica usando la parola chiave var. La parola chiave var chiede
al compilatore di dedurre il tipo di una variabile di query esaminando l'origine dati specificata nella clausola
from . Nell'esempio seguente viene generato lo stesso codice compilato dell'esempio precedente:
var customerQuery2 =
from cust in customers
where cust.City == "London"
select cust;

foreach(var customer in customerQuery2)


{
Console.WriteLine(customer.LastName + ", " + customer.FirstName);
}

La parola chiave var è utile quando il tipo della variabile è ovvio o quando non è importante specificare in
modo esplicito i tipi generici annidati, ad esempio quelli generati dalle query di gruppo. In generale, è
consigliabile usare var per rendere più difficile la lettura del codice da parte di altri utenti. Per altre
informazioni, vedere Variabili locali tipizzate in modo implicito.

Vedere anche
Generics
Operazioni di query LINQ di base (C#)
02/11/2020 • 9 minutes to read • Edit Online

Questo argomento fornisce una breve introduzione alle espressioni di query LINQ e ad alcuni tipi tipici di
operazioni eseguite in una query. Informazioni più specifiche sono disponibili negli argomenti seguenti:
Espressioni di query LINQ
Cenni preliminari sugli operatori di query standard (C#)
Procedura dettagliata: scrittura di query in C#

NOTE
Se si ha già familiarità con un linguaggio di query, ad esempio SQL o XQuery, è possibile ignorare la maggior parte di
questo argomento. Per informazioni sull' from ordine delle clausole nelle espressioni di query LINQ, vedere la sezione
"clausola" nella sezione successiva.

Ottenere un'origine dei dati


In una query LINQ, il primo passaggio consiste nel specificare l'origine dati. In C#, come nella maggior parte dei
linguaggi di programmazione, una variabile deve essere dichiarata prima di essere usata. In una query LINQ la
from clausola viene prima di tutto per presentare l'origine dati ( customers ) e la variabile di intervallo ( cust
).

//queryAllCustomers is an IEnumerable<Customer>
var queryAllCustomers = from cust in customers
select cust;

La variabile di intervallo è come la variabile di iterazione in un ciclo foreach ad eccezione del fatto che non si
verifica alcuna iterazione in un'espressione di query. Quando viene eseguita la query, la variabile di intervallo
verrà usata come riferimento a ogni elemento successivo in customers . Poiché il compilatore può dedurre il tipo
di cust , non è necessario specificarlo in modo esplicito. Altre variabili di intervallo possono essere introdotte
da una clausola let . Per altre informazioni, vedere Clausola let.

NOTE
Per origini dati non generiche, ad esempio ArrayList, la variabile di intervallo deve essere tipizzata in modo esplicito. Per
ulteriori informazioni, vedere come eseguire una query su un ArrayList con LINQ (C#) e clausola from.

Filtro
Probabilmente l'operazione di query più comune consiste nell'applicazione di un filtro sotto forma di
espressione booleana. Il filtro fa in modo che la query restituisca solo gli elementi per i quali l'espressione è
vera. Il risultato viene generato utilizzando la clausola where . Il filtro in realtà specifica gli elementi da escludere
dalla sequenza di origine. Nell'esempio seguente vengono restituiti solo i customers che hanno un indirizzo in
Londra.
var queryLondonCustomers = from cust in customers
where cust.City == "London"
select cust;

È possibile usare gli operatori logici AND e OR di C# per applicare tutte le espressioni necessarie nella clausola
where . Ad esempio, per restituire solo i clienti in "Londra" AND che si chiamano "Devon", si scrive il codice
seguente:

where cust.City == "London" && cust.Name == "Devon"

Per restituire i clienti di Londra o Parigi, si scrive il codice seguente:

where cust.City == "London" || cust.City == "Paris"

Per ulteriori informazioni, vedere clausola WHERE.

Ordering
È spesso utile ordinare i dati restituiti. La clausola orderby ordinerà gli elementi della sequenza restituita in base
all'operatore di confronto per il tipo che si sta ordinando. Ad esempio, la query seguente può essere estesa per
ordinare i risultati basati sulla proprietà Name . Poiché Name è una stringa, l'operatore di confronto esegue un
ordinamento alfabetico da A a Z.

var queryLondonCustomers3 =
from cust in customers
where cust.City == "London"
orderby cust.Name ascending
select cust;

Per ordinare i risultati in ordine inverso, da Z ad A, usare la clausola orderby…descending .


Per altre informazioni, vedere Clausola orderby.

Raggruppamento
La clausola group consente di raggruppare i risultati in base a una chiave specificata. Ad esempio, è possibile
specificare che i risultati devono essere raggruppati in base a City in modo che tutti i clienti di Londra o Parigi
siano elencati in gruppi individuali. In questo caso, cust.City è la soluzione.

// queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>>


var queryCustomersByCity =
from cust in customers
group cust by cust.City;

// customerGroup is an IGrouping<string, Customer>


foreach (var customerGroup in queryCustomersByCity)
{
Console.WriteLine(customerGroup.Key);
foreach (Customer customer in customerGroup)
{
Console.WriteLine(" {0}", customer.Name);
}
}
Quando si termina una query con una clausola group , i risultati vengono rappresentati come un elenco di
elenchi. Ogni elemento nell'elenco è un oggetto che ha un membro Key e un elenco di elementi che sono
raggruppati sotto tale chiave. Quando si esegue l'iterazione di una query che produce una sequenza di gruppi, è
necessario usare un ciclo foreach annidato. Il ciclo esterno esegue l'iterazione di ogni gruppo e il ciclo interno
esegue l'iterazione dei membri di ogni gruppo.
Se è necessario fare riferimento ai risultati di un'operazione di gruppo, è possibile usare la parola chiave into
per creare un identificatore sul quale eseguire query addizionali. La query seguente restituisce solo i gruppi che
contengono più di due clienti:

// custQuery is an IEnumerable<IGrouping<string, Customer>>


var custQuery =
from cust in customers
group cust by cust.City into custGroup
where custGroup.Count() > 2
orderby custGroup.Key
select custGroup;

Per altre informazioni, vedere Clausola group.

Aggiunta
Le operazioni di join creano associazioni tra le sequenze che non sono modellate in modo esplicito nelle origini
dei dati. Ad esempio, è possibile eseguire un join per trovare tutti i clienti e i distributori che hanno la stessa
ubicazione. In LINQ la join clausola funziona sempre con le raccolte di oggetti anziché direttamente con le
tabelle di database.

var innerJoinQuery =
from cust in customers
join dist in distributors on cust.City equals dist.City
select new { CustomerName = cust.Name, DistributorName = dist.Name };

In LINQ non è necessario usare join il più spesso possibile in SQL, perché le chiavi esterne in LINQ sono
rappresentate nel modello a oggetti come proprietà che contengono una raccolta di elementi. Ad esempio, un
oggetto Customer contiene una raccolta di oggetti Order . Anziché eseguire un join, si accede agli ordini usando
la notazione "Dot":

from order in Customer.Orders...

Per ulteriori informazioni, vedere clausola join.

Selezione (proiezioni)
La clausola select produce i risultati della query e specifica la "forma" o un tipo di ogni elemento restituito. Ad
esempio, è possibile specificare se i risultati saranno costituiti di oggetti Customer completi, di un solo membro,
di un subset di membri, oppure un tipo di risultato completamente diverso basato su un calcolo o su una
creazione nuova di oggetti. Quando la clausola select produce un valore diverso da una copia dell'elemento
d'origine, l'operazione viene chiamata proiezione. L'utilizzo di proiezioni per trasformare i dati è una potente
funzionalità di espressioni di query LINQ. Per altre informazioni, vedere Trasformazioni dati con LINQ (C#) e
Clausola select.

Vedere anche
Espressioni di query LINQ
Procedura dettagliata: scrittura di query in C#
Parole chiave di query (LINQ)
Tipi anonimi
Trasformazioni dati con LINQ (C#)
02/11/2020 • 9 minutes to read • Edit Online

LINQ (Language-Integrated Query) non riguarda solo il recupero dei dati. È anche un potente strumento per la
trasformazione dei dati. Utilizzando una query LINQ, è possibile utilizzare una sequenza di origine come input e
modificarla in molti modi per creare una nuova sequenza di output. È possibile modificare la sequenza senza
modificare gli elementi con operazioni di ordinamento e raggruppamento. Ma probabilmente la funzionalità più
potente delle query LINQ è la possibilità di creare nuovi tipi. Questa operazione viene eseguita nella clausola
select. Ad esempio, è possibile effettuare le attività seguenti:
Unire più sequenze di input in un'unica sequenza di output con un nuovo tipo.
Creare sequenze di output i cui elementi sono costituiti da una o più proprietà di ogni elemento nella
sequenza di origine.
Creare sequenze di output i cui elementi sono costituiti dai risultati delle operazioni eseguite sui dati di
origine.
Creare sequenze di output in un formato diverso. Ad esempio, è possibile trasformare i dati da righe SQL
o file di testo in XML.
Questi sono solo alcuni esempi. Naturalmente, queste trasformazioni possono essere combinate in modi diversi
nella stessa query. Inoltre, la sequenza di output di una query può essere usata come sequenza di input per una
nuova query.

Unione di più input in un'unica sequenza di output


È possibile usare una query LINQ per creare una sequenza di output che contiene elementi da più di una
sequenza di input. Nell'esempio seguente viene illustrato come combinare due strutture di dati in memoria, ma
è possibile applicare gli stessi principi per combinare dati da origini XML, SQL o DataSet. Si supponga di avere
questi tipi di classi:

class Student
{
public string First { get; set; }
public string Last {get; set;}
public int ID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public List<int> Scores;
}

class Teacher
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public string City { get; set; }
}

Nell'esempio riportato di seguito è visualizzata la query:


class DataTransformations
{
static void Main()
{
// Create the first data source.
List<Student> students = new List<Student>()
{
new Student { First="Svetlana",
Last="Omelchenko",
ID=111,
Street="123 Main Street",
City="Seattle",
Scores= new List<int> { 97, 92, 81, 60 } },
new Student { First="Claire",
Last="O’Donnell",
ID=112,
Street="124 Main Street",
City="Redmond",
Scores= new List<int> { 75, 84, 91, 39 } },
new Student { First="Sven",
Last="Mortensen",
ID=113,
Street="125 Main Street",
City="Lake City",
Scores= new List<int> { 88, 94, 65, 91 } },
};

// Create the second data source.


List<Teacher> teachers = new List<Teacher>()
{
new Teacher { First="Ann", Last="Beebe", ID=945, City="Seattle" },
new Teacher { First="Alex", Last="Robinson", ID=956, City="Redmond" },
new Teacher { First="Michiyo", Last="Sato", ID=972, City="Tacoma" }
};

// Create the query.


var peopleInSeattle = (from student in students
where student.City == "Seattle"
select student.Last)
.Concat(from teacher in teachers
where teacher.City == "Seattle"
select teacher.Last);

Console.WriteLine("The following students and teachers live in Seattle:");


// Execute the query.
foreach (var person in peopleInSeattle)
{
Console.WriteLine(person);
}

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}
/* Output:
The following students and teachers live in Seattle:
Omelchenko
Beebe
*/

Per altre informazioni, vedere Clausola join e Clausola select.

Selezione di un sottoinsieme di ogni elemento di origine


Esistono due modi principali per selezionare un sottoinsieme di ogni elemento nella sequenza di origine:
1. Per selezionare solo un membro dell'elemento di origine, usare l'operazione con punto. Nell'esempio
seguente si supponga che un oggetto Customer contenga diverse proprietà pubbliche tra cui una stringa
denominata City . Quando viene eseguita, questa query produce una sequenza di output di stringhe.

var query = from cust in Customers


select cust.City;

2. Per creare gli elementi che contengono più di una proprietà dall'elemento di origine, è possibile usare un
inizializzatore di oggetto con un oggetto denominato o un tipo anonimo. Nell'esempio seguente viene
illustrato l'uso di un tipo anonimo per incapsulare due proprietà da ogni elemento Customer :

var query = from cust in Customer


select new {Name = cust.Name, City = cust.City};

Per altre informazioni, vedere Inizializzatori di oggetto e di insieme e Tipi anonimi.

Trasformazione di oggetti in memoria in XML


Le query LINQ semplificano la trasformazione dei dati tra le strutture di dati in memoria, i database SQL, i set di
dati ADO.NET e i documenti o i flussi XML. Nell'esempio seguente gli oggetti di una struttura di dati in memoria
vengono trasformati in elementi XML.

class XMLTransform
{
static void Main()
{
// Create the data source by using a collection initializer.
// The Student class was defined previously in this topic.
List<Student> students = new List<Student>()
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 81,
60}},
new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List<int>{75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List<int>{88, 94, 65, 91}},
};

// Create the query.


var studentsToXML = new XElement("Root",
from student in students
let scores = string.Join(",", student.Scores)
select new XElement("student",
new XElement("First", student.First),
new XElement("Last", student.Last),
new XElement("Scores", scores)
) // end "student"
); // end "Root"

// Execute the query.


Console.WriteLine(studentsToXML);

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

Il codice genera il seguente output XML:


<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,92,81,60</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>75,84,91,39</Scores>
</student>
<student>
<First>Sven</First>
<Last>Mortensen</Last>
<Scores>88,94,65,91</Scores>
</student>
</Root>

Per altre informazioni, vedere Creazione di alberi XML in C# (LINQ to XML).

Esecuzione di operazioni su elementi di origine


Una sequenza di output potrebbe non contenere elementi o proprietà degli elementi della sequenza di origine.
L'output potrebbe invece essere una sequenza di valori che viene calcolata usando gli elementi di origine come
argomenti di input.
Con la query seguente viene eseguita una sequenza di numeri che rappresentano raggi di cerchi, viene calcolata
l'area per ogni raggio e viene restituita una sequenza di output contenente stringhe formattate con l'area
calcolata.
Ogni stringa per la sequenza di output verrà formattata utilizzando l' interpolazione di stringhe. Una stringa
interpolata avrà un oggetto $ davanti alle virgolette di apertura della stringa e le operazioni possono essere
eseguite all'interno di parentesi graffe posizionate all'interno della stringa interpolata. Una volta eseguite queste
operazioni, i risultati saranno concatenati.

NOTE
La chiamata ai metodi nelle espressioni di query non è supportata se la query verrà traslata in un altro dominio. Ad
esempio, non è possibile chiamare un normale metodo C# in LINQ to SQL perché SQL Server non offre alcun contesto.
Tuttavia, è possibile eseguire il mapping delle stored procedure ai metodi e chiamarli. Per altre informazioni, vedere Stored
procedure.
class FormatQuery
{
static void Main()
{
// Data source.
double[] radii = { 1, 2, 3 };

// LINQ query using method syntax.


IEnumerable<string> output =
radii.Select(r => $"Area for a circle with a radius of '{r}' = {r * r * Math.PI:F2}");

/*
// LINQ query using query syntax.
IEnumerable<string> output =
from rad in radii
select $"Area for a circle with a radius of '{rad}' = {rad * rad * Math.PI:F2}";
*/

foreach (string s in output)


{
Console.WriteLine(s);
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Area for a circle with a radius of '1' = 3.14
Area for a circle with a radius of '2' = 12.57
Area for a circle with a radius of '3' = 28.27
*/

Vedere anche
LINQ (Language-Integrated Query) (C#)
LINQ to SQL
LINQ to DataSet
LINQ to XML (C#)
Espressioni di query LINQ
clausola SELECT
Relazioni tra i tipi nelle operazioni di query LINQ
(C#)
02/11/2020 • 5 minutes to read • Edit Online

Per scrivere le query in modo efficace, è necessario comprendere in che modo i tipi di variabili in un'operazione
di query completa interagiscono tra loro. Se si conoscono queste relazioni, è più facile comprendere gli esempi
di LINQ e gli esempi di codice nella documentazione. In aggiunta, è possibile comprendere che cosa accade
dietro le quinte quando le variabili vengono tipizzate in modo implicito tramite var .
Le operazioni di query LINQ sono fortemente tipizzate nell'origine dati, nella query stessa e nell'esecuzione
della query. Il tipo delle variabili nella query deve essere compatibile con il tipo degli elementi nell'origine dati e
con il tipo della variabile di iterazione nell'istruzione foreach . Questa forte tipizzazione garantisce che gli errori
di tipo vengono rilevati in fase di compilazione quando possono essere corretti prima di essere riscontrati dagli
utenti.
Per illustrare tali relazioni del tipo, la maggior parte degli esempi che seguono usa la tipizzazione esplicita per
tutte le variabili. Nell'ultimo esempio viene illustrato come si applichino gli stessi principi anche quando si usa la
tipizzazione implicita tramite var.

Query che non trasformano i dati di origine


Nella figura seguente viene illustrata un'operazione di query LINQ to Objects che non esegue alcuna
trasformazione sui dati. L'origine contiene una sequenza di stringhe e anche l'output della query è una sequenza
di stringhe.

1. L'argomento del tipo dell'origine dati determina il tipo della variabile di intervallo.
2. Il tipo di oggetto selezionato determina il tipo della variabile di query. Qui name è una stringa. La
variabile di query è pertanto una IEnumerable<string> .
3. La variabile di query viene iterata nell'istruzione foreach . Poiché la variabile di query è una sequenza di
stringhe, anche la variabile di iterazione è una stringa.

Query che trasformano i dati di origine


La figura seguente mostra un'operazione di query LINQ to SQL che esegue una trasformazione sui dati di lieve
entità. La query usa una sequenza di oggetti Customer come input e seleziona solo la proprietà Name nel
risultato. Poiché Name è una stringa, la query genera una sequenza di stringhe come output.
1. L'argomento del tipo dell'origine dati determina il tipo della variabile di intervallo.
2. L'istruzione select restituisce la proprietà Name invece dell'oggetto Customer completo. Poiché Name è
una stringa, l'argomento del tipo custNameQuery è string e non Customer .
3. Poiché custNameQuery è una sequenza di stringhe, anche la variabile di iterazione del ciclo foreach del
ciclo deve essere una string .
La figura seguente mostra una trasformazione leggermente più complessa. L'istruzione select restituisce un
tipo anonimo che acquisisce solo due membri dell'oggetto di origine Customer .

1. L'argomento del tipo dell'origine dati è sempre il tipo della variabile di intervallo nella query.
2. Poiché l'istruzione select produce un tipo anonimo, la variabile di query deve essere tipizzata in modo
implicito tramite var .
3. Poiché il tipo della variabile di query è implicito, anche la variabile di iterazione nel ciclo foreach deve
essere implicita.

Deduzione delle informazioni sul tipo tramite il compilatore


Sebbene sia necessario comprendere le relazioni di tipo in un'operazione di query, è possibile scegliere di far
eseguire al compilatore tutto il lavoro al posto dell'utente. La parola chiave var può essere usata per qualsiasi
variabile locale in un'operazione di query. La figura seguente è simile all'esempio 2 illustrato in precedenza. Il
compilatore fornisce, tuttavia, il tipo forte per ogni variabile nell'operazione di query.
Per altre informazioni su var , vedere Variabili locali tipizzate in modo implicito.
Sintassi di query e sintassi di metodi in LINQ (C#)
02/11/2020 • 8 minutes to read • Edit Online

La maggior parte delle query nella documentazione di LINQ (Language Integrated Query) introduttiva viene
scritta usando la sintassi di query dichiarativa LINQ. Tuttavia, la sintassi di query deve essere convertita in
chiamate al metodo per Common Language Runtime (CLR) di .NET quando il codice viene compilato. Queste
chiamate al metodo richiamano gli operatori query standard, che hanno nomi come Where , Select , GroupBy ,
Join , Max e Average . È possibile chiamarli direttamente usando la sintassi di metodo anziché la sintassi di
query.
La sintassi di query e la sintassi di metodo sono semanticamente identiche, ma molti utenti ritengono che la
sintassi di query sia più semplice e più facile da leggere. Alcune query devono essere espresse come chiamate al
metodo. Ad esempio, è necessario usare una chiamata al metodo per esprimere una query che recupera il
numero di elementi che soddisfano una determinata condizione. È necessario usare una chiamata al metodo
anche per una query che recupera l'elemento con il valore massimo in una sequenza di origine. Nella
documentazione di riferimento per gli operatori query standard nello spazio dei nomi System.Linq viene usata
in genere la sintassi di metodo. Pertanto, anche quando si inizia a scrivere query LINQ, è utile acquisire
familiarità con l'uso della sintassi del metodo nelle query e nelle espressioni di query.

Metodi di estensione degli operatori query standard


Nell'esempio seguente viene illustrata un'espressione di query semplice e la query semanticamente equivalente
scritta come query basata su metodo.
class QueryVMethodSyntax
{
static void Main()
{
int[] numbers = { 5, 10, 8, 3, 6, 12};

//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)


{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}

// Keep the console open in debug mode.


Console.WriteLine(System.Environment.NewLine);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/*
Output:
6 8 10 12
6 8 10 12
*/

L'output dei due esempi è identico. Si noterà che il tipo della variabile di query è lo stesso in entrambi i formati:
IEnumerable<T>.
Per capire meglio la query basata su metodo, esaminiamola più da vicino. Sul lato destro dell'espressione, si può
notare che la clausola where viene ora espressa come metodo di istanza per l'oggetto numbers che, come si
ricorderà, ha un tipo di IEnumerable<int> . Chi ha familiarità con l'interfaccia generica IEnumerable<T> sa che
non ha un metodo Where . Tuttavia, se si richiama l'elenco di completamento IntelliSense nell'IDE di Visual
Studio, si vedrà non solo un metodo Where ma molti altri metodi, ad esempio Select , SelectMany , Join e
Orderby . Sono tutti operatori di query standard.

Sebbene possa sembrare che IEnumerable<T> sia stata ridefinita in modo da includere questi metodi aggiuntivi,
di fatto non è così. Gli operatori di query standard vengono implementati come un nuovo tipo di metodo
denominato metodo di estensione. I metodi di estensione "estendono" un tipo esistente. Possono essere
chiamati come se fossero metodi di istanza per il tipo. Gli operatori di query standard estendono
IEnumerable<T> e questo è il motivo per cui è possibile scrivere numbers.Where(...) .
Per iniziare a usare LINQ, è sufficiente conoscere i metodi di estensione per riportarli nell'ambito
dell'applicazione usando le using direttive corrette. Dal punto di vista dell'applicazione, un metodo di
estensione e un metodo di istanza normale sono la stessa cosa.
Per altre informazioni sui metodi di estensione, vedere Metodi di estensione. Per altre informazioni sugli
operatori di query standard, vedere Panoramica degli operatori di query standard (C#). Alcuni provider LINQ, ad
esempio LINQ to SQL e LINQ to XML , implementano i propri operatori di query standard e i metodi di
estensione aggiuntivi per altri tipi oltre a IEnumerable<T> .

Espressioni lambda
Nell'esempio precedente, si può notare che l'espressione condizionale ( num % 2 == 0 ) viene passata come
argomento inline al metodo Where : Where(num => num % 2 == 0). Questa espressione inline è definita
espressione lambda. È un modo pratico per scrivere codice che altrimenti dovrebbe essere scritto in un formato
più complesso come metodo anonimo, delegato generico o albero delle espressioni. In C# => è l'operatore
lambda, che viene letto come "goes to". L'elemento num a sinistra dell'operatore è la variabile di input che
corrisponde a num nell'espressione di query. Il compilatore è in grado di dedurre il tipo di num poiché sa che
numbers è un tipo IEnumerable<T> generico. Il corpo dell'espressione lambda è identico all'espressione nella
sintassi di query o in qualsiasi altra espressione o istruzione di C# e può includere chiamate al metodo e altra
logica complessa. Il valore restituito è semplicemente il risultato dell'espressione.
Per iniziare a usare LINQ, non è necessario utilizzare le espressioni lambda in modo esteso. Tuttavia, alcune
query possono essere espresse solo nella sintassi di metodo e alcune di esse richiedono le espressioni lambda.
Una volta acquisita familiarità con le espressioni lambda, si noterà che si tratta di uno strumento potente e
flessibile nella casella degli strumenti di LINQ. Per altre informazioni, vedere espressioni lambda.

Componibilità delle query


Nell'esempio di codice precedente il metodo OrderBy viene richiamato usando l'operatore punto nella chiamata
a Where . Where genera una sequenza filtrata e quindi Orderby agisce sulla sequenza ordinandola. Poiché le
query restituiscono un oggetto IEnumerable , è necessario comporle nella sintassi di metodo concatenando le
chiamate al metodo. Questa è l'operazione che il compilatore esegue in background quando si scrivono le query
usando la sintassi di query. E poiché una variabile di query non archivia i risultati della query, è possibile
modificarla o usarla come base per una nuova query in qualsiasi momento, anche dopo che è stata eseguita.
Funzionalità di C# che supportano LINQ
02/11/2020 • 7 minutes to read • Edit Online

Nella sezione seguente vengono illustrati i nuovi costrutti di linguaggio introdotti in C# 3.0. Anche se queste
nuove funzionalità sono tutte usate per un grado di query LINQ, non sono limitate a LINQ e possono essere
usate in qualsiasi contesto in cui si trovano utili.

Espressioni di query
Le espressioni di query usano una sintassi dichiarativa simile a SQL o XQuery per eseguire una query sulle
raccolte IEnumerable. In fase di compilazione la sintassi di query viene convertita in chiamate al metodo
nell'implementazione di un provider LINQ dei metodi di estensione degli operatori di query standard. Le
applicazioni controllano gli operatori di query standard inclusi nell'ambito specificando lo spazio dei nomi
adatto con una direttiva using . Nell'espressione di query seguente viene usata una matrice di stringhe,
raggruppate in base al primo carattere della stringa, e vengono ordinati i gruppi.

var query = from str in stringArray


group str by str[0] into stringGroup
orderby stringGroup.Key
select stringGroup;

Per altre informazioni, vedere Espressioni di query LINQ.

Variabili tipizzate in modo implicito (var)


Anziché specificare in modo esplicito un tipo quando si dichiara e inizializza una variabile, è possibile usare il
modificatore var per indicare al compilatore di dedurre e assegnare il tipo, come illustrato di seguito:

var number = 5;
var name = "Virginia";
var query = from str in stringArray
where str[0] == 'm'
select str;

Le variabili dichiarate come var sono fortemente tipizzate come variabili il cui tipo è specificato in modo
esplicito. L'uso di var consente di creare tipi anonimi, ma può essere usato solo per variabili locali. Le matrici
possono essere dichiarate anche con la tipizzazione implicita.
Per altre informazioni, vedere Variabili locali tipizzate in modo implicito.

Inizializzatori di oggetto e di raccolta


Gli inizializzatori di oggetti e Collection consentono di inizializzare gli oggetti senza chiamare in modo esplicito
un costruttore per l'oggetto. Gli inizializzatori vengono usati in genere nelle espressioni di query quando
proiettano i dati di origine in un nuovo tipo di dati. Supponendo di usare una classe denominata Customer con
le proprietà pubbliche Name e Phone , l'inizializzatore di oggetto può essere usato come nel codice seguente:

var cust = new Customer { Name = "Mike", Phone = "555-1212" };

Continuando con la classe Customer , si supponga un'origine dati denominata IncomingOrders e che per ogni
ordine con OrderSize grande si debba creare un nuovo oggetto Customer basato sull'ordine. È possibile
eseguire una query LINQ su questa origine dati e usare l'inizializzazione di oggetti per completare una raccolta:

var newLargeOrderCustomers = from o in IncomingOrders


where o.OrderSize > 5
select new Customer { Name = o.Name, Phone = o.Phone };

L'origine dati può avere più proprietà in background rispetto alla classe Customer , ad esempio OrderSize , ma
con l'inizializzazione di oggetti i dati restituiti dalla query vengono modellati nel tipo di dati desiderato. Vengono
quindi scelti i dati pertinenti per la classe. Di conseguenza, è ora disponibile un oggetto IEnumerable che
contiene i nuovi oggetti Customer desiderati. Il codice precedente può anche essere scritto nella sintassi del
metodo di LINQ:

var newLargeOrderCustomers = IncomingOrders.Where(x => x.OrderSize > 5).Select(y => new Customer { Name =
y.Name, Phone = y.Phone });

Per altre informazioni, vedere:


Inizializzatori di oggetto e di raccolta
Sintassi di espressione della query per operatori di query standard

Tipi anonimi
Un tipo anonimo viene costruito dal compilatore e il nome del tipo è disponibile solo al compilatore. I tipi
anonimi costituiscono una valida soluzione per raggruppare temporaneamente un set di proprietà nel risultato
di una query senza dovere definire un tipo denominato separato. I tipi anonimi vengono inizializzati con una
nuova espressione e un inizializzatore di oggetto, come illustrato di seguito:

select new {name = cust.Name, phone = cust.Phone};

Per ulteriori informazioni, vedere tipi anonimi.

Metodi di estensione
Un metodo di estensione è un metodo statico che può essere associato a un tipo, in modo da poter essere
chiamato come se fosse un metodo di istanza sul tipo. Questa funzionalità consente in pratica di "aggiungere"
nuovi metodi ai tipi esistenti senza doverli effettivamente modificare. Gli operatori di query standard sono un
set di metodi di estensione che forniscono funzionalità di query LINQ per qualsiasi tipo che implementa
IEnumerable<T> .
Per altre informazioni, vedere Metodi di estensione.

Espressioni lambda
Un'espressione lambda è una funzione inline che usa l'operatore => per separare i parametri di input dal corpo
della funzione e, in fase di compilazione, può essere convertita in un delegato o in un albero delle espressioni.
Nella programmazione LINQ si verificheranno espressioni lambda quando si eseguono chiamate a metodi
diretti agli operatori di query standard.
Per altre informazioni, vedere:
Funzioni anonime
Espressioni lambda
Alberi delle espressioni (C#)

Vedere anche
LINQ (Language-Integrated Query) (C#)
Procedura dettagliata: Scrittura di query in C#
(LINQ)
02/11/2020 • 17 minutes to read • Edit Online

Questa procedura dettagliata illustra le funzionalità del linguaggio C# utilizzate per scrivere espressioni di query
LINQ.

Creare un progetto C#
NOTE
Le istruzioni seguenti riguardano Visual Studio. Se si usa un ambiente di sviluppo diverso, creare un progetto console con
un riferimento a System.Core.dll e una direttiva using per lo spazio dei nomi System.Linq.

Per creare un progetto in Visual Studio


1. Avviare Visual Studio.
2. Nella barra dei menu scegliere File , Nuovo , Progetto .
Verrà visualizzata la finestra di dialogo Nuovo progetto .
3. Espandere Installati , Modelli e Visual C# e scegliere Applicazione console .
4. Nella casella di testo Nome immettere un nome diverso o accettare il nome predefinito e quindi
scegliere il pulsante OK .
Il nuovo progetto verrà visualizzato in Esplora soluzioni .
5. Si noti che il progetto contiene un riferimento a System.Core.dll e una direttiva using per lo spazio dei
nomi System.Linq.

Creare un'origine dati in memoria


L'origine dati per le query è un semplice elenco di oggetti Student . Ogni record Student ha un nome, un
cognome e una matrice di interi che rappresenta i punteggi dei test nella classe. Copiare questo codice nel
progetto. Tenere presente le seguenti caratteristiche:
La classe Student consiste di proprietà implementate automaticamente.
Ogni studente nell'elenco viene inizializzato con un inizializzatore di oggetto.
L'elenco stesso viene inizializzato con un inizializzatore di raccolta.
Questa intera struttura di dati sarà inizializzata e istanziata senza chiamate esplicite ad alcun costruttore o
accesso a membri espliciti. Per altre informazioni su queste nuove funzionalità, vedere Proprietà implementate
automaticamente e Inizializzatori di oggetto e di raccolta.
Per aggiungere l'origine dati
Aggiungere la classe Student e l'elenco di studenti inizializzato alla classe Program nel progetto.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}

// Create a data source by using a collection initializer.


static List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 92, 81,
60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {88, 94, 65, 91}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {97, 89, 85, 82}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {35, 72, 91, 70}},
new Student {First="Fadi", Last="Fakhouri", ID=116, Scores= new List<int> {99, 86, 90, 94}},
new Student {First="Hanying", Last="Feng", ID=117, Scores= new List<int> {93, 92, 80, 87}},
new Student {First="Hugo", Last="Garcia", ID=118, Scores= new List<int> {92, 90, 83, 78}},
new Student {First="Lance", Last="Tucker", ID=119, Scores= new List<int> {68, 79, 88, 92}},
new Student {First="Terry", Last="Adams", ID=120, Scores= new List<int> {99, 82, 81, 79}},
new Student {First="Eugene", Last="Zabokritski", ID=121, Scores= new List<int> {96, 85, 91, 60}},
new Student {First="Michael", Last="Tucker", ID=122, Scores= new List<int> {94, 92, 91, 91}}
};

Per aggiungere un nuovo studente all'elenco degli studenti


1. Aggiungere un nuovo Student all'elenco Students e usare un nome e punteggi di test di propria scelta.
Provare a digitare tutte le nuove informazioni sugli studenti per imparare meglio la sintassi per
l'inizializzatore di oggetto.

Creare la query
Per creare una query semplice
Nel metodo Main dell'applicazione creare una query semplice che, quando viene eseguita, genera un
elenco di tutti gli studenti il cui punteggio del primo test è maggiore di 90. Si noti che, poiché è
selezionato l'intero oggetto Student , il tipo di query è IEnumerable<Student> . Il codice potrebbe usare
anche la tipizzazione implicita mediante la parola chiave var, ma si usa la tipizzazione esplicita per
illustrare dettagliatamente i risultati. Per ulteriori informazioni su var , vedere variabili locali tipizzate in
modo implicito.
Si noti anche che la variabile di intervallo della query, student , funge da riferimento a ogni Student
nell'origine, fornendo l'accesso di membro per ogni oggetto.

// Create the query.


// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
from student in students
where student.Scores[0] > 90
select student;

Eseguire la query
Per eseguire la query
1. Scrivere ora il ciclo foreach che avvia l'esecuzione della query. Tenere presente quanto segue in merito
al codice:
A ogni elemento della sequenza restituita si accede tramite la variabile di iterazione nel ciclo
foreach .
Il tipo di questa variabile è Student e il tipo della variabile di query è compatibile,
IEnumerable<Student> .

2. Dopo aver aggiunto questo codice, compilare ed eseguire l'applicazione per vedere i risultati nella
finestra Console .

// Execute the query.


// var could be used here also.
foreach (Student student in studentQuery)
{
Console.WriteLine("{0}, {1}", student.Last, student.First);
}

// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

Per aggiungere un'altra condizione di filtro


1. È possibile combinare più condizioni booleane nel clausola where per perfezionare ulteriormente la
query. Il codice seguente aggiunge una condizione in modo che la query restituisca gli studenti il cui
primo punteggio era maggiore di 90 e il cui ultimo punteggio era minore di 80. La clausola where
dovrebbe essere simile al codice seguente.

where student.Scores[0] > 90 && student.Scores[3] < 80

Per ulteriori informazioni, vedere clausola WHERE.

Modificare la query
Per ordinare i risultati
1. Sarà più semplice analizzare i risultati se si trovano nello stesso tipo di ordine. È possibile ordinare la
sequenza restituita in base a qualsiasi campo accessibile negli elementi di origine. Ad esempio, la clausola
orderby seguente dispone i risultati in ordine alfabetico dalla A alla Z in base al cognome dello studente.
Aggiungere la seguente clausola orderby alla query, subito dopo l'istruzione where e prima
dell'istruzione select :

orderby student.Last ascending

2. Modificare ora la clausola orderby in modo che disponga i risultati in ordine decrescente in base al
punteggio del primo test, dal punteggio più alto al più basso.

orderby student.Scores[0] descending

3. Modificare la stringa di formato WriteLine in modo che sia possibile vedere i punteggi:

Console.WriteLine("{0}, {1} {2}", student.Last, student.First, student.Scores[0]);


Per altre informazioni, vedere Clausola orderby.
Per raggruppare i risultati
1. Il raggruppamento è una potente funzionalità delle espressioni di query. Una query con una clausola
group genera una sequenza di gruppi e ogni gruppo contiene un Key e una sequenza costituita da tutti i
membri di quel gruppo. La nuova query riportata di seguito raggruppa gli studenti usando la prima
lettera del cognome come chiave.

// studentQuery2 is an IEnumerable<IGrouping<char, Student>>


var studentQuery2 =
from student in students
group student by student.Last[0];

2. Si noti che il tipo della query è stato modificato. Ora genera una sequenza di gruppi che hanno il tipo
char come chiave e una sequenza di oggetti Student . Siccome il tipo della query è cambiato, il codice
seguente cambia anche il ciclo di esecuzione foreach :

// studentGroup is a IGrouping<char, Student>


foreach (var studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
foreach (Student student in studentGroup)
{
Console.WriteLine(" {0}, {1}",
student.Last, student.First);
}
}

// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene

3. Eseguire l'applicazione e visualizzare i risultati nella finestra Console .


Per altre informazioni, vedere Clausola group.
Per ottenere che le variabili siano tipizzate in modo implicito
1. Codificare in modo esplicito IEnumerables di IGroupings può risultare noioso. È possibile scrivere la
stessa query e ciclo foreach in modo molto più semplice usando var . La parola chiave var non
cambia il tipo degli oggetti ma si limita a istruire il compilatore a dedurre i tipi. Cambiare il tipo di
studentQuery e la variabile di iterazione group in var ed eseguire di nuovo la query. Si noti che nel
ciclo foreach interno, la variabile di iterazione è ancora tipizzata come Student e la query funziona
esattamente come prima. Cambiare la variabile di iterazione s in var ed eseguire di nuovo la query. Si
noti che si ottengono esattamente gli stessi risultati.

var studentQuery3 =
from student in students
group student by student.Last[0];

foreach (var groupOfStudents in studentQuery3)


{
Console.WriteLine(groupOfStudents.Key);
foreach (var student in groupOfStudents)
{
Console.WriteLine(" {0}, {1}",
student.Last, student.First);
}
}

// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene

Per altre informazioni su var, vedere Variabili locali tipizzate in modo implicito.
Per ordinare i gruppi in base al valore della chiave
1. Quando si esegue la query precedente, si nota che i gruppi non sono in ordine alfabetico. Per modificare
questa impostazione è necessario fornire una clausola orderby dopo la clausola group . Ma per usare
una clausola orderby , è necessario un identificatore che funge da riferimento ai gruppi creati per la
clausola group . L'identificatore viene fornito usando la parola chiave into , come segue:
var studentQuery4 =
from student in students
group student by student.Last[0] into studentGroup
orderby studentGroup.Key
select studentGroup;

foreach (var groupOfStudents in studentQuery4)


{
Console.WriteLine(groupOfStudents.Key);
foreach (var student in groupOfStudents)
{
Console.WriteLine(" {0}, {1}",
student.Last, student.First);
}
}

// Output:
//A
// Adams, Terry
//F
// Fakhouri, Fadi
// Feng, Hanying
//G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
//M
// Mortensen, Sven
//O
// Omelchenko, Svetlana
// O'Donnell, Claire
//T
// Tucker, Lance
// Tucker, Michael
//Z
// Zabokritski, Eugene

Quando si esegue questa query si può notare che i gruppi vengono disposti in ordine alfabetico.
Per introdurre un identificatore usando let
1. È possibile usare la parola chiave let per introdurre un identificatore per qualsiasi risultato di
espressione nell'espressione di query. Questo identificatore può essere comodo, come nell'esempio
seguente, oppure può migliorare le prestazioni archiviando i risultati di un'espressione in modo che non
debba essere calcolata più volte.
// studentQuery5 is an IEnumerable<string>
// This query returns those students whose
// first test score was higher than their
// average score.
var studentQuery5 =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where totalScore / 4 < student.Scores[0]
select student.Last + " " + student.First;

foreach (string s in studentQuery5)


{
Console.WriteLine(s);
}

// Output:
// Omelchenko Svetlana
// O'Donnell Claire
// Mortensen Sven
// Garcia Cesar
// Fakhouri Fadi
// Feng Hanying
// Garcia Hugo
// Adams Terry
// Zabokritski Eugene
// Tucker Michael

Per altre informazioni, vedere Clausola let.


Per usare la sintassi del metodo in un'espressione di query
1. Come descritto in Sintassi di query e sintassi di metodi in LINQ, alcune operazioni di query possono
essere espresse solo usando la sintassi dei metodi. Il codice seguente calcola il punteggio totale per ogni
Student nella sequenza di origine e quindi chiama il metodo Average() sui risultati della query per
calcolare il punteggio medio della classe.

var studentQuery6 =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
select totalScore;

double averageScore = studentQuery6.Average();


Console.WriteLine("Class average score = {0}", averageScore);

// Output:
// Class average score = 334.166666666667

Per eseguire trasformazioni o proiezioni nella clausola select


1. Un compito molto comune per una query è generare una sequenza i cui elementi differiscono da quelli
delle sequenze di origine. Eliminare o impostare come commento il ciclo di query ed esecuzione
precedente e sostituirlo con il codice seguente. Si noti che la query restituisce una sequenza di stringhe
(non Students ) e questo si riflette nel ciclo foreach .
IEnumerable<string> studentQuery7 =
from student in students
where student.Last == "Garcia"
select student.First;

Console.WriteLine("The Garcias in the class are:");


foreach (string s in studentQuery7)
{
Console.WriteLine(s);
}

// Output:
// The Garcias in the class are:
// Cesar
// Debra
// Hugo

2. Il codice precedente di questa procedura dettagliata indica che il punteggio medio della classe è pari a
circa 334. Per produrre una sequenza di Students il cui punteggio totale sia maggiore della media della
classe, insieme al loro Student ID , è possibile usare un tipo anonimo nell'istruzione select :

var studentQuery8 =
from student in students
let x = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where x > averageScore
select new { id = student.ID, score = x };

foreach (var item in studentQuery8)


{
Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score);
}

// Output:
// Student ID: 113, Score: 338
// Student ID: 114, Score: 353
// Student ID: 116, Score: 369
// Student ID: 117, Score: 352
// Student ID: 118, Score: 343
// Student ID: 120, Score: 341
// Student ID: 122, Score: 368

Passaggi successivi
Dopo aver acquisito familiarità con i fondamenti dell'uso delle query in C#, è possibile leggere la
documentazione e vedere gli esempi per il tipo specifico di provider LINQ a cui si è interessati:
LINQ to SQL
LINQ to DataSet
LINQ to XML (C#)
LINQ to Objects (C#)

Vedere anche
LINQ (Language-Integrated Query) (C#)
Espressioni di query LINQ
Cenni preliminari sugli operatori di query standard
(C#)
02/11/2020 • 6 minutes to read • Edit Online

Gli operatori di query standard sono metodi che costituiscono il modello LINQ. La maggior parte di questi
metodi agisce sulle sequenze. Una sequenza è un oggetto il cui tipo implementa l'interfaccia IEnumerable<T> o
l'interfaccia IQueryable<T>. Gli operatori di query standard forniscono le funzionalità di query che includono
filtro, proiezione, aggregazione, ordinamento e altro ancora.
Sono disponibili due set di operatori di query standard LINQ: uno che opera su oggetti di tipo IEnumerable<T> ,
un altro che opera su oggetti di tipo IQueryable<T> . I metodi che costituiscono ogni set sono membri statici
delle classi Enumerable e Queryable, rispettivamente. Vengono definiti come metodi di estensione del tipo su
cui agiscono. I metodi di estensione possono essere chiamati usando la sintassi del metodo statico o la sintassi
del metodo di istanza.
Inoltre, molti metodi degli operatori query standard agiscono su tipi diversi da quelli basati su IEnumerable<T>
o IQueryable<T>. Il tipo Enumerable definisce questi due diversi metodi che agiscono entrambi su oggetti di
tipo IEnumerable. Questi metodi, Cast<TResult>(IEnumerable) e OfType<TResult>(IEnumerable), consentono
l'esecuzione di query su una Collection senza parametri o non generica nel modello LINQ A tale scopo, crea una
raccolta fortemente tipizzata di oggetti. La classe Queryable definisce due metodi simili, Cast<TResult>
(IQueryable) e OfType<TResult>(IQueryable), che agiscono su oggetti di tipo Queryable.
Gli operatori di query standard presentano una durata di esecuzione diversa, a seconda che restituiscano un
valore singleton o una sequenza di valori. Questi metodi che restituiscono un valore singleton, ad esempio
Average e Sum, vengono eseguiti immediatamente. I metodi che restituiscono una sequenza rinviano
l'esecuzione della query e restituiscono un oggetto enumerabile.
Per i metodi che operano su raccolte in memoria, ovvero i metodi che estendono IEnumerable<T> , l'oggetto
enumerabile restituito acquisisce gli argomenti passati al metodo. Quando l'oggetto viene enumerato, viene
utilizzata la logica dell'operatore query e vengono restituiti i risultati della query.
Al contrario, i metodi che estendono IQueryable<T> non implementano il comportamento delle query.
Compilano un albero delle espressioni che rappresenta la query da eseguire. L'elaborazione delle query viene
gestita dall'oggetto IQueryable<T> di origine.
Le chiamate ai metodi della query possono essere concatenate in una query, consentendo alle query di
diventare arbitrariamente complesse.
Nell'esempio di codice seguente viene illustrato come usare gli operatori di query standard per ottenere
informazioni su una sequenza.
string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');

// Using query expression syntax.


var query = from word in words
group word.ToUpper() by word.Length into gr
orderby gr.Key
select new { Length = gr.Key, Words = gr };

// Using method-based query syntax.


var query2 = words.
GroupBy(w => w.Length, w => w.ToUpper()).
Select(g => new { Length = g.Key, Words = g }).
OrderBy(o => o.Length);

foreach (var obj in query)


{
Console.WriteLine("Words of length {0}:", obj.Length);
foreach (string word in obj.Words)
Console.WriteLine(word);
}

// This code example produces the following output:


//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS

Sintassi delle espressioni di query


Alcuni degli operatori di query standard usati più di frequente dispongono di sintassi dedicata delle parole
chiave per i linguaggi C# e Visual Basic che consente di chiamare gli operatori come parte di un'*espressione di
* query. Per altre informazioni sugli operatori di query standard con parole chiave dedicate e le relative sintassi,
vedere Sintassi di espressione della query per operatori di query standard (C#).

Estensione degli operatori di query standard


È possibile estendere il set di operatori di query standard creando metodi specifici del dominio appropriati per
la tecnologia o il dominio di destinazione. È possibile anche sostituire gli operatori di query standard con
implementazioni personalizzate che forniscono servizi aggiuntivi, ad esempio la valutazione remota, la
conversione delle query e l'ottimizzazione. Per un esempio, vedere AsEnumerable.

Sezioni correlate
I collegamenti seguenti contengono articoli che forniscono informazioni aggiuntive sui diversi operatori di
query standard in base alle funzionalità di.
Ordinamento dei dati (C#)
Operazioni sui set (C#)
Filtro di dati (C#)
Operazioni del quantificatore (C#)
Operazioni di proiezione (C#)
Partizionamento dei dati (C#)
Operazioni di join (C#)
Raggruppamento dei dati (C#)
Operazioni di generazione (C#)
Operazioni di uguaglianza (C#)
Operazioni sugli elementi (C#)
Conversione del tipo di dati (C#)
Operazioni di concatenazione (C#)
Operazioni di aggregazione (C#)

Vedere anche
Enumerable
Queryable
Introduzione alle query LINQ (C#)
Sintassi di espressione di query per operatori di query standard (C#)
Classificazione degli operatori di query standard in base alla modalità di esecuzione
Metodi di estensione
Sintassi di espressione di query per operatori di
query standard (C#)
02/11/2020 • 2 minutes to read • Edit Online

Alcuni degli operatori di query standard usati più di frequente dispongono di una sintassi dedicata delle parole
chiave per i linguaggi C# che consente di chiamare gli operatori come parte di un'espressione di query.
Un'espressione di query rappresenta un modo diverso e più leggibile per esprimere una query rispetto alla
sintassi equivalente basata su metodo. Le clausole di espressione di query vengono convertite in chiamate ai
metodi di query in fase di compilazione.

Tabella della sintassi di espressione di query


La tabella seguente elenca gli operatori di query standard che hanno clausole di espressione di query
equivalenti.

M ETO DO SIN TA SSI DI ESP RESSIO N E DEL L A Q UERY C #

Cast Usare una variabile di intervallo tipizzata in modo esplicito,


ad esempio:

from int i in numbers

Per ulteriori informazioni, vedere clausola from.

GroupBy group … by

-oppure-

group … by … into …

Per ulteriori informazioni, vedere clausola Group.

GroupJoin<TOuter,TInner,TKey,TResult> join … in … on … equals … into …


(IEnumerable<TOuter>, IEnumerable<TInner>,
Func<TOuter,TKey>, Func<TInner,TKey>, Per altre informazioni, vedere Clausola join.
Func<TOuter,IEnumerable<TInner>,TResult>)

Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, join … in … on … equals …


IEnumerable<TInner>, Func<TOuter,TKey>,
Func<TInner,TKey>, Func<TOuter,TInner,TResult>) Per altre informazioni, vedere Clausola join.

OrderBy<TSource,TKey>(IEnumerable<TSource>, orderby
Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.

OrderByDescending<TSource,TKey> orderby … descending


(IEnumerable<TSource>, Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.

Select select

Per altre informazioni, vedere Clausola select.


M ETO DO SIN TA SSI DI ESP RESSIO N E DEL L A Q UERY C #

SelectMany Più clausole from .

Per ulteriori informazioni, vedere clausola from.

ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.

ThenByDescending<TSource,TKey> orderby …, … descending


(IOrderedEnumerable<TSource>, Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.

Where where

Per altre informazioni, vedere Clausola where.

Vedere anche
Enumerable
Queryable
Cenni preliminari sugli operatori di query standard (C#)
Classificazione degli operatori di query standard in base alla modalità di esecuzione
Classificazione degli operatori di query standard in
base alla modalità di esecuzione (C#)
02/11/2020 • 6 minutes to read • Edit Online

Le implementazioni LINQ to Objects dei metodi degli operatori di query standard vengono eseguite in due modi
principali: implementazione immediata o posticipata. Gli operatori di query che usano l'esecuzione posticipata
possono essere anche suddivisi in due categorie: di flusso e non di flusso. Se si conosce la modalità di
esecuzione dei vari operatori di query, sarà più facile capire i risultati che si ottengono da una determinata
query. Ciò è particolarmente vero se l'origine dei dati viene modificata oppure se si sta creando una query su
un'altra query. Questo argomento classifica gli operatori di query standard in base alla relativa modalità di
esecuzione.

Modalità di esecuzione
Immediato
L'esecuzione immediata indica che l'origine dei dati è in lettura e l'operazione viene eseguita in corrispondenza
del punto del codice in cui viene dichiarata la query. Tutti gli operatori di query standard che restituiscono un
risultato singolo non enumerabile vengono eseguiti immediatamente.
Rinviata
L'esecuzione posticipata indica che l'operazione non viene eseguita in corrispondenza del punto del codice in cui
viene dichiarata la query. L'operazione viene eseguita solo quando la variabile di query viene enumerata, ad
esempio tramite un'istruzione foreach . Ciò significa che i risultati dell'esecuzione della query dipendono dal
contenuto dell'origine dei dati nel momento in cui viene eseguita la query e non quando viene definita. Se la
variabile di query viene enumerata più volte, i risultati potrebbero essere diversi ogni volta. Quasi tutti gli
operatori query standard con tipo restituito IEnumerable<T> o IOrderedEnumerable<TElement> vengono
eseguiti in modo posticipato.
Gli operatori di query che usano l'esecuzione posticipata possono essere anche suddivisi in due categorie: di
flusso e non di flusso.
Streaming
Gli operatori di flusso non devono leggere tutti i dati di origine prima di restituire elementi. In fase di
esecuzione, un operatore di flusso esegue l'operazione su ogni elemento di origine che legge e restituisce
l'elemento, se appropriato. Un operatore di flusso continua a leggere gli elementi fino a quando non può essere
prodotto un elemento di risultato. Ciò significa che potrebbe essere letto più di un elemento di origine per
produrre un elemento di risultato.
Non di flusso
Gli operatori non di flusso devono leggere tutti i dati di origine prima di produrre un elemento di risultato. In
questa categoria rientrano operazioni quali ordinamento o raggruppamento. In fase di esecuzione, gli operatori
di query non di flusso leggono tutti i dati di origine, li inseriscono in una struttura di dati, eseguono l'operazione
e producono gli elementi risultanti.

Tabella di classificazione
La tabella seguente classifica ogni metodo di operatore di query standard in base al metodo di esecuzione.
NOTE
Se un operatore è contrassegnato in due colonne, vengono coinvolte due sequenze di input nell'operazione e ogni
sequenza viene restituita in modo diverso. In questi casi, viene restituita sempre la prima sequenza nell'elenco dei
parametri in modo posticipato e di flusso.

ESEC UZ IO N E ESEC UZ IO N E
O P ERATO RE DI ESEC UZ IO N E P O ST IC IPATA DI P O ST IC IPATA DI N O N
Q UERY STA N DA RD T IP O REST IT UITO IM M EDIATA F L USSO F L USSO

Aggregate TSource X

All Boolean X

Any Boolean X

AsEnumerable IEnumerable<T> X

Average Valore numerico X


singolo

Cast IEnumerable<T> X

Concat IEnumerable<T> X

Contains Boolean X

Count Int32 X

DefaultIfEmpty IEnumerable<T> X

Distinct IEnumerable<T> X

ElementAt TSource X

ElementAtOrDefault TSource X

Empty IEnumerable<T> X

Except IEnumerable<T> X X

First TSource X

FirstOrDefault TSource X

GroupBy IEnumerable<T> X

GroupJoin IEnumerable<T> X X

Intersect IEnumerable<T> X X

Join IEnumerable<T> X X
ESEC UZ IO N E ESEC UZ IO N E
O P ERATO RE DI ESEC UZ IO N E P O ST IC IPATA DI P O ST IC IPATA DI N O N
Q UERY STA N DA RD T IP O REST IT UITO IM M EDIATA F L USSO F L USSO

Last TSource X

LastOrDefault TSource X

LongCount Int64 X

Max Valore numerico X


singolo, TSource o
TResult

Min Valore numerico X


singolo, TSource o
TResult

OfType IEnumerable<T> X

OrderBy IOrderedEnumerable X
<TElement>

OrderByDescending IOrderedEnumerable X
<TElement>

Range IEnumerable<T> X

Repeat IEnumerable<T> X

Reverse IEnumerable<T> X

Select IEnumerable<T> X

SelectMany IEnumerable<T> X

SequenceEqual Boolean X

Single TSource X

SingleOrDefault TSource X

Skip IEnumerable<T> X

SkipWhile IEnumerable<T> X

Sum Valore numerico X


singolo

Take IEnumerable<T> X

TakeWhile IEnumerable<T> X
ESEC UZ IO N E ESEC UZ IO N E
O P ERATO RE DI ESEC UZ IO N E P O ST IC IPATA DI P O ST IC IPATA DI N O N
Q UERY STA N DA RD T IP O REST IT UITO IM M EDIATA F L USSO F L USSO

ThenBy IOrderedEnumerable X
<TElement>

ThenByDescending IOrderedEnumerable X
<TElement>

ToArray Matrice TSource X

ToDictionary Dictionary<TKey,TVal X
ue>

ToList IList<T> X

ToLookup ILookup<TKey,TElem X
ent>

Union IEnumerable<T> X

Where IEnumerable<T> X

Vedere anche
Enumerable
Cenni preliminari sugli operatori di query standard (C#)
Sintassi di espressione di query per operatori di query standard (C#)
LINQ to Objects (C#)
Ordinamento dei dati (C#)
02/11/2020 • 4 minutes to read • Edit Online

Un'operazione di ordinamento consente di ordinare gli elementi di una sequenza in base a uno o più attributi. Il
primo criterio di ordinamento consente di applicare un ordinamento principale agli elementi. Specificando un
secondo criterio di ordinamento, è possibile ordinare gli elementi all'interno di ogni gruppo di ordinamento
principale.
La figura seguente illustra i risultati di un'operazione di ordinamento alfabetico in una sequenza di caratteri:

La sezione seguente elenca i metodi dell'operatore query standard che ordina i dati.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

OrderBy Ordina i valori in ordine orderby Enumerable.OrderBy


crescente.
Queryable.OrderBy

OrderByDescending Ordina i valori in ordine orderby … descending Enumerable.OrderByDescen


decrescente. ding

Queryable.OrderByDescendi
ng

ThenBy Esegue un ordinamento orderby …, … Enumerable.ThenBy


secondario crescente.
Queryable.ThenBy

ThenByDescending Esegue un ordinamento orderby …, … Enumerable.ThenByDescend


secondario decrescente. descending ing

Queryable.ThenByDescendi
ng

Reverse Inverte l'ordine degli Non applicabile. Enumerable.Reverse


elementi in una Collection.
Queryable.Reverse

Esempi di sintassi delle espressioni di query


Esempi di ordinamento primario
Ordinamento primario crescente
Nell'esempio seguente viene illustrato come usare la clausola orderby in una query LINQ per ordinare le
stringhe in una matrice in base alla lunghezza di stringa, in ordine crescente.
string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words


orderby word.Length
select word;

foreach (string str in query)


Console.WriteLine(str);

/* This code produces the following output:

the
fox
quick
brown
jumps
*/

Ordinamento primario decrescente


Nell'esempio seguente viene illustrato come usare la clausola orderby descending in una query LINQ per
ordinare le stringhe in base alla prima lettera, in ordine decrescente.

string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words


orderby word.Substring(0, 1) descending
select word;

foreach (string str in query)


Console.WriteLine(str);

/* This code produces the following output:

the
quick
jumps
fox
brown
*/

Esempi di ordinamento secondario


Ordinamento secondario crescente
Nell'esempio seguente viene illustrato come usare la clausola orderby in una query LINQ per eseguire un
ordinamento primario e secondario delle stringhe in una matrice. Le stringhe vengono ordinate prima in base
alla lunghezza e poi in base alla prima lettera della stringa, in ordine crescente.
string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words


orderby word.Length, word.Substring(0, 1)
select word;

foreach (string str in query)


Console.WriteLine(str);

/* This code produces the following output:

fox
the
brown
jumps
quick
*/

Ordinamento secondario in ordine decrescente


L'esempio seguente illustra come usare la clausola orderby descending in una query LINQ per eseguire un
ordinamento primario, in ordine crescente, e un ordinamento secondario, in ordine decrescente. Le stringhe
vengono ordinate principalmente in base alla lunghezza e poi in base alla prima lettera della stringa.

string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words


orderby word.Length, word.Substring(0, 1) descending
select word;

foreach (string str in query)


Console.WriteLine(str);

/* This code produces the following output:

the
fox
quick
jumps
brown
*/

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Clausola orderby
Ordinare i risultati di una clausola join
Come ordinare o filtrare i dati di testo in base a qualsiasi parola o campo (LINQ) (C#)
Operazioni sui set (C#)
02/11/2020 • 3 minutes to read • Edit Online

Le operazioni sui set in LINQ si riferiscono alle operazioni di query che generano un set di risultati basato sulla
presenza o sull'assenza di elementi equivalenti all'interno delle stesse Collection oppure di Collection (o set)
distinte.
La sezione seguente elenca i metodi dell'operatore query standard che eseguono operazioni sui set.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Distinct Rimuove i valori duplicati da Non applicabile. Enumerable.Distinct


una Collection.
Queryable.Distinct

Except Restituisce la differenza dei Non applicabile. Enumerable.Except


set, ovvero gli elementi di
una Collection che non Queryable.Except
sono presenti in una
seconda Collection.

Intersect Restituisce l'intersezione di Non applicabile. Enumerable.Intersect


set, ovvero gli elementi
presenti in ognuna delle Queryable.Intersect
due Collection.

Union Restituisce l'unione di set, Non applicabile. Enumerable.Union


ovvero gli elementi univoci
presenti in una delle due Queryable.Union
Collection.

Confronto tra le operazioni sui set


Distinct
Nell'esempio seguente viene illustrato il comportamento del Enumerable.Distinct metodo su una sequenza di
caratteri. La sequenza restituita contiene gli elementi univoci dalla sequenza di input.
string[] planets = { "Mercury", "Venus", "Venus", "Earth", "Mars", "Earth" };

IEnumerable<string> query = from planet in planets.Distinct()


select planet;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* Mercury
* Venus
* Earth
* Mars
*/

Except
Nell'esempio seguente viene illustrato il comportamento di Enumerable.Except . La sequenza restituita contiene
solo gli elementi dalla prima sequenza di input che non sono presenti nella seconda sequenza di input.

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };


string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Except(planets2)


select planet;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* Venus
*/

Intersect
Nell'esempio seguente viene illustrato il comportamento di Enumerable.Intersect . La sequenza restituita
contiene gli elementi comuni a entrambe le sequenze di input.
string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Intersect(planets2)


select planet;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* Mercury
* Earth
* Jupiter
*/

Union
Nell'esempio seguente viene illustrata un'operazione di Unione su due sequenze di caratteri. La sequenza
restituita contiene gli elementi univoci da entrambe le sequenze di input.

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };


string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Union(planets2)


select planet;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* Mercury
* Venus
* Earth
* Jupiter
* Mars
*/

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Come combinare e confrontare raccolte di stringhe (LINQ) (C#)
Come trovare la differenza dei set tra due elenchi (LINQ) (C#)
Filtro di dati (C#)
02/11/2020 • 2 minutes to read • Edit Online

Il filtro si riferisce all'operazione in base alla quale il set di risultati viene limitato in modo da contenere solo gli
elementi che corrispondono a una condizione specificata. È anche noto come selezione.
Nella figura seguente vengono illustrati i risultati del filtro di una sequenza di caratteri. Il predicato per
l'operazione di filtro specifica che il carattere deve essere 'A'.

La sezione seguente elenca i metodi dell'operatore query standard che esegue la selezione.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

OfType Seleziona i valori, a seconda Non applicabile. Enumerable.OfType


della loro capacità di
eseguire il cast a un tipo Queryable.OfType
specificato.

Where Seleziona i valori che si where Enumerable.Where


basano su una funzione di
predicato. Queryable.Where

Esempio di sintassi delle espressioni di query


Nell'esempio seguente viene usata la clausola where per filtrare da una matrice le stringhe con una lunghezza
specifica.

string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words


where word.Length == 3
select word;

foreach (string str in query)


Console.WriteLine(str);

/* This code produces the following output:

the
fox
*/

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
clausola WHERE
Specificare dinamicamente i filtri dei predicati in fase di esecuzione
Come eseguire una query sui metadati di un assembly tramite reflection (LINQ) (C#)
Come eseguire una query per i file con un attributo o un nome specifico (C#)
Come ordinare o filtrare i dati di testo in base a qualsiasi parola o campo (LINQ) (C#)
Operazioni del quantificatore (C#)
02/11/2020 • 4 minutes to read • Edit Online

Le operazioni del quantificatore restituiscono un valore Boolean, che indica se alcuni o tutti gli elementi di una
sequenza soddisfano una condizione.
La figura seguente illustra due diverse operazioni del quantificatore in due diverse sequenze di origine. La prima
operazione chiede se uno o più elementi sono il carattere "A" e il risultato è true . La seconda operazione chiede
se tutti gli elementi sono il carattere "A" e il risultato è true .

La sezione seguente elenca i metodi dell'operatore query standard che eseguono operazioni del quantificatore.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Tutti Determina se tutti gli Non applicabile. Enumerable.All


elementi di una sequenza
soddisfano una condizione. Queryable.All

Qualsiasi Determina se alcuni Non applicabile. Enumerable.Any


elementi di una sequenza
soddisfano una condizione. Queryable.Any

Contiene Determina se una sequenza Non applicabile. Enumerable.Contains


contiene un elemento
specifico. Queryable.Contains

Esempi di sintassi delle espressioni di query


Tutti
Nell'esempio seguente viene utilizzato All per verificare che tutte le stringhe abbiano una lunghezza specifica.
class Market
{
public string Name { get; set; }
public string[] Items { get; set; }
}

public static void Example()


{
List<Market> markets = new List<Market>
{
new Market { Name = "Emily's", Items = new string[] { "kiwi", "cheery", "banana" } },
new Market { Name = "Kim's", Items = new string[] { "melon", "mango", "olive" } },
new Market { Name = "Adam's", Items = new string[] { "kiwi", "apple", "orange" } },
};

// Determine which market have all fruit names length equal to 5


IEnumerable<string> names = from market in markets
where market.Items.All(item => item.Length == 5)
select market.Name;

foreach (string name in names)


{
Console.WriteLine($"{name} market");
}

// This code produces the following output:


//
// Kim's market
}

Qualsiasi
Nell'esempio seguente viene utilizzato Any per verificare che le stringhe inizino con "o".

class Market
{
public string Name { get; set; }
public string[] Items { get; set; }
}

public static void Example()


{
List<Market> markets = new List<Market>
{
new Market { Name = "Emily's", Items = new string[] { "kiwi", "cheery", "banana" } },
new Market { Name = "Kim's", Items = new string[] { "melon", "mango", "olive" } },
new Market { Name = "Adam's", Items = new string[] { "kiwi", "apple", "orange" } },
};

// Determine which market have any fruit names start with 'o'
IEnumerable<string> names = from market in markets
where market.Items.Any(item => item.StartsWith("o"))
select market.Name;

foreach (string name in names)


{
Console.WriteLine($"{name} market");
}

// This code produces the following output:


//
// Kim's market
// Adam's market
}
Contiene
Nell'esempio seguente viene utilizzato Contains per verificare che una matrice disponga di un elemento
specifico.

class Market
{
public string Name { get; set; }
public string[] Items { get; set; }
}

public static void Example()


{
List<Market> markets = new List<Market>
{
new Market { Name = "Emily's", Items = new string[] { "kiwi", "cheery", "banana" } },
new Market { Name = "Kim's", Items = new string[] { "melon", "mango", "olive" } },
new Market { Name = "Adam's", Items = new string[] { "kiwi", "apple", "orange" } },
};

// Determine which market contains fruit names equal 'kiwi'


IEnumerable<string> names = from market in markets
where market.Items.Contains("kiwi")
select market.Name;

foreach (string name in names)


{
Console.WriteLine($"{name} market");
}

// This code produces the following output:


//
// Emily's market
// Adam's market
}

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Specificare dinamicamente i filtri dei predicati in fase di esecuzione
Come eseguire una query per trovare frasi che contengono un set specificato di parole (LINQ) (C#)
Operazioni di proiezione (C#)
02/11/2020 • 5 minutes to read • Edit Online

La proiezione si riferisce all'operazione di trasformazione di un oggetto in un nuovo form costituito spesso solo
dalle proprietà che verranno usate successivamente. Utilizzando la proiezione, è possibile costruire un nuovo
tipo compilato in base a ogni oggetto. È possibile proiettare una proprietà ed eseguirvi una funzione
matematica. È anche possibile proiettare l'oggetto originale senza modificarlo.
Nella sezione seguente sono elencati i metodi dell'operatore query standard che eseguono la proiezione.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Select Proietta i valori che si select Enumerable.Select


basano su una funzione di
trasformazione. Queryable.Select

SelectMany Proietta le sequenze di Usare più clausole from Enumerable.SelectMany


valori che si basano su una
funzione di trasformazione Queryable.SelectMany
semplificandoli in un'unica
sequenza.

Esempi di sintassi delle espressioni di query


Select
L'esempio seguente usa la clausola select per proiettare la prima lettera di ogni stringa di un elenco di
stringhe.

List<string> words = new List<string>() { "an", "apple", "a", "day" };

var query = from word in words


select word.Substring(0, 1);

foreach (string s in query)


Console.WriteLine(s);

/* This code produces the following output:

a
a
a
d
*/

SelectMany
L'esempio seguente usa più clausole from per proiettare tutte le parole di ogni stringa di un elenco di stringhe.
List<string> phrases = new List<string>() { "an apple a day", "the quick brown fox" };

var query = from phrase in phrases


from word in phrase.Split(' ')
select word;

foreach (string s in query)


Console.WriteLine(s);

/* This code produces the following output:

an
apple
a
day
the
quick
brown
fox
*/

Confronto tra Select e SelectMany


La funzione di Select() e SelectMany() è produrre uno o più valori risultato dai valori di origine. Select()
produce un valore risultato per ogni valore di origine. Il risultato complessivo è pertanto una raccolta
contenente lo stesso numero di elementi della raccolta di origine. Al contrario, SelectMany() produce un singolo
risultato complessivo che contiene sottoraccolte concatenate di ogni valore di origine. La funzione di
trasformazione passata come argomento a SelectMany() deve restituire una sequenza enumerabile di valori
per ogni valore di origine. Queste sequenze enumerabili vengono quindi concatenate da SelectMany() per
creare un'unica grande sequenza.
Le due figure seguenti illustrano la differenza concettuale tra le azioni di questi due metodi. In ogni caso, si
supponga che la funzione del selettore (trasformazione) selezioni la matrice di fiori di ogni valore di origine.
La figura mostra che Select() restituisce una raccolta contenente lo stesso numero di elementi della raccolta di
origine.

La figura mostra che SelectMany() concatena la sequenza intermedia di matrici in un unico valore risultato
finale contenente tutti i valori di ogni matrice intermedia.
Esempio di codice
L'esempio seguente confronta il comportamento di Select() e SelectMany() . Il codice crea un "bouquet" di
fiori prendendo i primi due elementi di ogni elenco di nomi di fiori nella raccolta di origine. In questo esempio, il
"valore singolo" usato dalla funzione di trasformazione Select<TSource,TResult>(IEnumerable<TSource>,
Func<TSource,TResult>) è anch'esso una raccolta di valori. Questo richiede il ciclo foreach aggiuntivo in modo
da enumerare tutte le stringhe di ogni sottosequenza.

class Bouquet
{
public List<string> Flowers { get; set; }
}

static void SelectVsSelectMany()


{
List<Bouquet> bouquets = new List<Bouquet>() {
new Bouquet { Flowers = new List<string> { "sunflower", "daisy", "daffodil", "larkspur" }},
new Bouquet{ Flowers = new List<string> { "tulip", "rose", "orchid" }},
new Bouquet{ Flowers = new List<string> { "gladiolis", "lily", "snapdragon", "aster", "protea" }},
new Bouquet{ Flowers = new List<string> { "larkspur", "lilac", "iris", "dahlia" }}
};

// *********** Select ***********


IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);

// ********* SelectMany *********


IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);

Console.WriteLine("Results by using Select():");


// Note the extra foreach loop here.
foreach (IEnumerable<String> collection in query1)
foreach (string item in collection)
Console.WriteLine(item);

Console.WriteLine("\nResults by using SelectMany():");


foreach (string item in query2)
Console.WriteLine(item);

/* This code produces the following output:

Results by using Select():


sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
snapdragon
aster
protea
larkspur
lilac
iris
dahlia

Results by using SelectMany():


sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
*/

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
clausola SELECT
Come popolare le raccolte di oggetti da più origini (LINQ) (C#)
Come suddividere un file in molti file usando i gruppi (LINQ) (C#)
Partizionamento dei dati (C#)
02/11/2020 • 2 minutes to read • Edit Online

Il partizionamento in LINQ è l'operazione di divisione di una sequenza di input in due sezioni senza
ridisposizione degli elementi e la successiva restituzione di una delle sezioni.
La figura seguente illustra i risultati di tre diverse operazioni di partizionamento in una sequenza di caratteri. La
prima operazione restituisce i primi tre elementi nella sequenza. La seconda operazione ignora i primi tre
elementi e restituisce gli elementi rimanenti. La terza operazione ignora i primi due elementi nella sequenza e
restituisce i tre elementi successivi.

Nella sezione seguente sono elencati i metodi degli operatori di query standard che eseguono la partizione delle
sequenze.

Operatori
SIN TA SSI DI ESP RESSIO N E
N O M E O P ERATO RE DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Ignora Ignora gli elementi fino a Non applicabile. Enumerable.Skip


una posizione specificata in
una sequenza. Queryable.Skip

SkipWhile Ignora gli elementi in base a Non applicabile. Enumerable.SkipWhile


una funzione di predicato
fino a quando un elemento Queryable.SkipWhile
non soddisfa la condizione.

Take Accetta gli elementi fino a Non applicabile. Enumerable.Take


una posizione specificata in
una sequenza. Queryable.Take

TakeWhile Accetta gli elementi in base Non applicabile. Enumerable.TakeWhile


a una funzione di predicato
fino a quando un elemento Queryable.TakeWhile
non soddisfa la condizione.

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
JoinOperazioni (C#)
28/01/2021 • 6 minutes to read • Edit Online

Un join di due origini dati è un'associazione di oggetti in un'origine dati con oggetti che condividono un
attributo comune in un'altra origine dati.
JoinING è un'operazione importante nelle query che hanno come destinazione origini dati le cui relazioni
reciproche non possono essere seguite direttamente. Nella programmazione orientata a oggetti ciò potrebbe
corrispondere a una correlazione non modellata tra oggetti, ad esempio la direzione inversa di una relazione
unidirezionale. Un esempio di relazione unidirezionale è costituito da una classe Customer che include una
proprietà di tipo City, ma la classe City non include una proprietà che sia una raccolta di oggetti Customer. Se si
ha un elenco di oggetti City e si vogliono trovare tutti i clienti in ogni città, è possibile usare un'operazione join
per individuarli.
I metodi di join disponibili nel framework LINQ sono Join e GroupJoin. Questi metodi eseguono equijoin, ovvero
join che associano due origini dati in base all'uguaglianza delle rispettive chiavi. Per il confronto, Transact-SQL
supporta operatori join diversi da' Equals ', ad esempio l'operatore ' minore di '. In termini di database
relazionale, Join implementa un inner join, un tipo di join in cui vengono restituiti solo gli oggetti che hanno una
corrispondenza nell'altro set di dati. Il metodo GroupJoin non ha equivalenti diretti in termini di database
relazionale, ma implementa un superset di inner join e left outer join. Un left outer join è un join che restituisce
ogni elemento della prima origine dati (a sinistra), anche se non ha elementi correlati nell'altra origine dati.
L'illustrazione seguente mostra una visualizzazione concettuale dei due set e degli elementi dei set che sono
inclusi in un inner join o in un left outer join.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Join Joins due sequenze basate join … in … on … Enumerable.Join


sulle funzioni del selettore equals …
principale ed estrae coppie Queryable.Join
di valori.

GroupJoin Joins due sequenze basate join … in … on … Enumerable.GroupJoin


sulle funzioni del selettore equals … into …
di chiave e raggruppa le Queryable.GroupJoin
corrispondenze risultanti
per ogni elemento.

Esempi di sintassi delle espressioni di query


Join
Nell'esempio seguente viene usata la join … in … on … equals … clausola per unire in join due sequenze in base
a un valore specifico:

class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}

class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
}

public static void Example()


{
List<Product> products = new List<Product>
{
new Product { Name = "Cola", CategoryId = 0 },
new Product { Name = "Tea", CategoryId = 0 },
new Product { Name = "Apple", CategoryId = 1 },
new Product { Name = "Kiwi", CategoryId = 1 },
new Product { Name = "Carrot", CategoryId = 2 },
};

List<Category> categories = new List<Category>


{
new Category { Id = 0, CategoryName = "Beverage" },
new Category { Id = 1, CategoryName = "Fruit" },
new Category { Id = 2, CategoryName = "Vegetable" }
};

// Join products and categories based on CategoryId


var query = from product in products
join category in categories on product.CategoryId equals category.Id
select new { product.Name, category.CategoryName };

foreach (var item in query)


{
Console.WriteLine($"{item.Name} - {item.CategoryName}");
}

// This code produces the following output:


//
// Cola - Beverage
// Tea - Beverage
// Apple - Fruit
// Kiwi - Fruit
// Carrot - Vegetable
}

GroupJoin
Nell'esempio seguente viene usata la join … in … on … equals … into … clausola per unire in join due sequenze
in base a un valore specifico e vengono raggruppate le corrispondenze risultanti per ogni elemento:
class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}

class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
}

public static void Example()


{
List<Product> products = new List<Product>
{
new Product { Name = "Cola", CategoryId = 0 },
new Product { Name = "Tea", CategoryId = 0 },
new Product { Name = "Apple", CategoryId = 1 },
new Product { Name = "Kiwi", CategoryId = 1 },
new Product { Name = "Carrot", CategoryId = 2 },
};

List<Category> categories = new List<Category>


{
new Category { Id = 0, CategoryName = "Beverage" },
new Category { Id = 1, CategoryName = "Fruit" },
new Category { Id = 2, CategoryName = "Vegetable" }
};

// Join categories and product based on CategoryId and grouping result


var productGroups = from category in categories
join product in products on category.Id equals product.CategoryId into productGroup
select productGroup;

foreach (IEnumerable<Product> productGroup in productGroups)


{
Console.WriteLine("Group");
foreach (Product product in productGroup)
{
Console.WriteLine($"{product.Name,8}");
}
}

// This code produces the following output:


//
// Group
// Cola
// Tea
// Group
// Apple
// Kiwi
// Group
// Carrot
}

Vedere anche
System.Linq
Standard Query Operators Overview (C#) (Panoramica degli operatori di query standard)
Tipi anonimi
Formulare Join le query e tra i prodotti
Clausola join
Joinusando chiavi composite
Come unire il contenuto da file non analoghi (LINQ) (C#)
Ordinare i risultati di una clausola join
Eseguire operazioni di join personalizzate
Eseguire join raggruppati
Eseguire inner join
Eseguire left outer join
Come popolare le raccolte di oggetti da più origini (LINQ) (C#)
Raggruppamento dei dati (C#)
02/11/2020 • 2 minutes to read • Edit Online

Il raggruppamento consiste nell'inserire i dati in gruppi in modo che gli elementi in ogni gruppo condividano un
attributo comune.
Nella figura seguente vengono illustrati i risultati del raggruppamento di una sequenza di caratteri. La chiave di
ogni gruppo è il carattere.

Nella sezione seguente vengono elencati i metodi degli operatori query standard che eseguono il
raggruppamento degli elementi di dati.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

GroupBy Raggruppa gli elementi che group … by Enumerable.GroupBy


condividono un attributo
comune. Ogni gruppo è -oppure- Queryable.GroupBy
rappresentato da un
oggetto group … by … into …
IGrouping<TKey,TElement>.

ToLookup Inserisce gli elementi in un Non applicabile. Enumerable.ToLookup


oggetto
Lookup<TKey,TElement>,
un dizionario uno-a-molti,
sulla base di una funzione
del selettore di chiavi.

Esempio di sintassi delle espressioni di query


Nell'esempio di codice seguente viene illustrato come usare la clausola group by per raggruppare i numeri
interi in un elenco a seconda che siano numeri pari o numeri dispari.
List<int> numbers = new List<int>() { 35, 44, 200, 84, 3987, 4, 199, 329, 446, 208 };

IEnumerable<IGrouping<int, int>> query = from number in numbers


group number by number % 2;

foreach (var group in query)


{
Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
foreach (int i in group)
Console.WriteLine(i);
}

/* This code produces the following output:

Odd numbers:
35
3987
199
329

Even numbers:
44
200
84
4
446
208
*/

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Clausola group
Creare un gruppo annidato
Come raggruppare i file per estensione (LINQ) (C#)
Raggruppare i risultati di una query
Eseguire una sottoquery su un'operazione di raggruppamento
Come suddividere un file in molti file usando i gruppi (LINQ) (C#)
Operazioni di generazione (C#)
02/11/2020 • 2 minutes to read • Edit Online

La generazione si riferisce alla creazione di una nuova sequenza di valori.


La sezione seguente elenca i metodi dell'operatore query standard che esegue la generazione.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

DefaultIfEmpty Sostituisce una raccolta Non applicabile. Enumerable.DefaultIfEmpty


vuota con una raccolta
singleton dal valore Queryable.DefaultIfEmpty
predefinito.

Empty Restituisce una raccolta Non applicabile. Enumerable.Empty


vuota.

Range Genera una raccolta che Non applicabile. Enumerable.Range


contiene una sequenza di
numeri.

Repeat Genera una raccolta che Non applicabile. Enumerable.Repeat


contiene un valore ripetuto.

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Operazioni di uguaglianza (C#)
02/11/2020 • 2 minutes to read • Edit Online

Due sequenze i cui elementi corrispondenti sono uguali e che hanno lo stesso numero di elementi sono
considerate uguali.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

SequenceEqual Determina se due sequenze Non applicabile. Enumerable.SequenceEqual


sono uguali confrontando
gli elementi con Queryable.SequenceEqual
metodologia pair-wise.

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Come confrontare il contenuto di due cartelle (LINQ) (C#)
Operazioni sugli elementi (C#)
02/11/2020 • 2 minutes to read • Edit Online

Le operazioni sugli elementi restituiscono un singolo elemento specifico di una sequenza.


La sezione seguente elenca i metodi dell'operatore query standard che eseguono operazioni sugli elementi.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

ElementAt Restituisce l'elemento in Non applicabile. Enumerable.ElementAt


corrispondenza dell'indice
specificato in una Collection. Queryable.ElementAt

ElementAtOrDefault Restituisce l'elemento in Non applicabile. Enumerable.ElementAtOrDe


corrispondenza di un indice fault
specificato in una Collection
o un valore predefinito se Queryable.ElementAtOrDef
l'indice è esterno ault
all'intervallo.

Primo Restituisce il primo Non applicabile. Enumerable.First


elemento di una Collection
o il primo elemento che Queryable.First
soddisfa una condizione.

FirstOrDefault Restituisce il primo Non applicabile. Enumerable.FirstOrDefault


elemento di una Collection
o il primo elemento che Queryable.FirstOrDefault
soddisfa una condizione.
Restituisce un valore Queryable.FirstOrDefault<T
predefinito se questo tipo di Source>
elemento non esiste. (IQueryable<TSource>)

Last (Ultimo) Restituisce l'ultimo Non applicabile. Enumerable.Last


elemento di una Collection
o l'ultimo elemento che Queryable.Last
soddisfa una condizione.

LastOrDefault Restituisce l'ultimo Non applicabile. Enumerable.LastOrDefault


elemento di una Collection
o l'ultimo elemento che Queryable.LastOrDefault
soddisfa una condizione.
Restituisce un valore
predefinito se questo tipo di
elemento non esiste.
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Single Restituisce l'unico elemento Non applicabile. Enumerable.Single


di una raccolta o l'unico
elemento che soddisfa una Queryable.Single
condizione. Genera
un'eccezione
InvalidOperationException
se non è presente alcun
elemento o sono presenti
più elementi da restituire.

SingleOrDefault Restituisce l'unico elemento Non applicabile. Enumerable.SingleOrDefault


di una raccolta o l'unico
elemento che soddisfa una Queryable.SingleOrDefault
condizione. Restituisce un
valore predefinito se non
sono presenti elementi da
restituire. Genera
un'eccezione
InvalidOperationException
se sono presenti più
elementi da restituire.

Vedere anche
System.Linq
Standard Query Operators Overview (C#) (Panoramica degli operatori di query standard)
Come eseguire una query per il file o i file più grandi in un albero di directory (LINQ) (C#)
Conversione di tipi di dati (C#)
02/11/2020 • 4 minutes to read • Edit Online

I metodi di conversione modificano il tipo degli oggetti di input.


Le operazioni di conversione nelle query LINQ sono utili in un'ampia gamma di applicazioni. Ecco alcuni esempi:
Il metodo Enumerable.AsEnumerable può essere usato per nascondere l'implementazione personalizzata
di un tipo di un operatore query standard.
Il metodo Enumerable.OfType può essere usato per abilitare le raccolte senza parametri per l'esecuzione
di query LINQ.
I metodi Enumerable.ToArray, Enumerable.ToDictionary, Enumerable.ToList e Enumerable.ToLookup
possono essere usati per forzare l'esecuzione di una query immediata invece che rinviarla fino a quando
la query non viene enumerata.

Metodi
Nella tabella seguente sono elencati i metodi di operatore query standard che eseguono conversioni di tipi di
dati.
I metodi di conversione nella tabella i cui nomi iniziano con "As" modificano il tipo statico della raccolta di
origine ma non lo enumerano. I metodi i cui nomi iniziano con "To" enumerano la raccolta di origine e
inseriscono gli elementi nel tipo di raccolta corrispondente.

SIN TA SSI DI ESP RESSIO N E


N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

AsEnumerable Restituisce l'input tipizzato Non applicabile. Enumerable.AsEnumerable


come oggetto
IEnumerable<T>.

AsQueryable Converte un oggetto Non applicabile. Queryable.AsQueryable


IEnumerable (generico) in
un oggetto IQueryable
(generico).

Cast Esegue il cast degli elementi Usare una variabile di Enumerable.Cast


di una raccolta a un tipo intervallo tipizzata in modo
specificato. esplicito. Ad esempio: Queryable.Cast

from string str in


words

OfType Filtra i valori, a seconda Non applicabile. Enumerable.OfType


della loro capacità di
eseguire il cast a un tipo Queryable.OfType
specificato.

ToArray Converte una raccolta in Non applicabile. Enumerable.ToArray


una matrice. Questo
metodo forza l'esecuzione
di query.
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

ToDictionary Inserisce gli elementi in un Non applicabile. Enumerable.ToDictionary


oggetto
Dictionary<TKey,TValue>
sulla base di una funzione
del selettore di chiavi.
Questo metodo forza
l'esecuzione di query.

ToList Converte una raccolta in un Non applicabile. Enumerable.ToList


oggetto List<T>. Questo
metodo forza l'esecuzione
di query.

ToLookup Inserisce gli elementi in un Non applicabile. Enumerable.ToLookup


oggetto
Lookup<TKey,TElement>,
un dizionario uno-a-molti,
sulla base di una funzione
del selettore di chiavi.
Questo metodo forza
l'esecuzione di query.

Esempio di sintassi delle espressioni di query


Nell'esempio di codice seguente viene utilizzata una variabile di intervallo tipizzata in modo esplicito per
eseguire il cast di un tipo a un sottotipo prima di accedere a un membro disponibile solo sul sottotipo.
class Plant
{
public string Name { get; set; }
}

class CarnivorousPlant : Plant


{
public string TrapType { get; set; }
}

static void Cast()


{
Plant[] plants = new Plant[] {
new CarnivorousPlant { Name = "Venus Fly Trap", TrapType = "Snap Trap" },
new CarnivorousPlant { Name = "Pitcher Plant", TrapType = "Pitfall Trap" },
new CarnivorousPlant { Name = "Sundew", TrapType = "Flypaper Trap" },
new CarnivorousPlant { Name = "Waterwheel Plant", TrapType = "Snap Trap" }
};

var query = from CarnivorousPlant cPlant in plants


where cPlant.TrapType == "Snap Trap"
select cPlant;

foreach (Plant plant in query)


Console.WriteLine(plant.Name);

/* This code produces the following output:

Venus Fly Trap


Waterwheel Plant
*/
}

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
clausola from
Espressioni di query LINQ
Come eseguire una query su un ArrayList con LINQ (C#)
Operazioni di concatenazione (C#)
02/11/2020 • 2 minutes to read • Edit Online

Il termine concatenazione descrive l'operazione che consiste nell'accodare una sequenza a un'altra sequenza.
La figura seguente illustra un'operazione di concatenazione tra due sequenze di caratteri.

La sezione seguente elenca i metodi dell'operatore query standard che esegue la concatenazione.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Concat Concatena due sequenze Non applicabile. Enumerable.Concat


formando un'unica
sequenza. Queryable.Concat

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Come combinare e confrontare raccolte di stringhe (LINQ) (C#)
Operazioni di aggregazione (C#)
02/11/2020 • 2 minutes to read • Edit Online

Un'operazione di aggregazione calcola un singolo valore da una raccolta di valori. Un esempio di operazione di
aggregazione è rappresentato dal calcolo della temperatura media giornaliera dai valori della temperatura
giornaliera di un mese.
La figura seguente illustra i risultati di due operazioni di aggregazione diverse in una sequenza di numeri. La
prima operazione somma i numeri. La seconda operazione restituisce il valore massimo nella sequenza.

La sezione seguente elenca i metodi degli operatori di query standard che eseguono operazioni di
aggregazione.

Metodi
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Aggregate Esegue un'operazione di Non applicabile. Enumerable.Aggregate


aggregazione personalizzata
sui valori di una raccolta. Queryable.Aggregate

Media Calcola il valore medio di Non applicabile. Enumerable.Average


una raccolta di valori.
Queryable.Average

Conteggio Conta gli elementi in una Non applicabile. Enumerable.Count


raccolta e, facoltativamente,
solo gli elementi che Queryable.Count
soddisfano una funzione di
predicato.

LongCount Conta gli elementi in una Non applicabile. Enumerable.LongCount


raccolta di grandi
dimensioni e, Queryable.LongCount
facoltativamente, solo gli
elementi che soddisfano
una funzione di predicato.

Max Determina il valore Non applicabile. Enumerable.Max


massimo in una raccolta.
Queryable.Max
SIN TA SSI DI ESP RESSIO N E
N O M E M ETO DO DESC RIZ IO N E DEL L A Q UERY C # A LT RE IN F O RM A Z IO N I

Min Determina il valore minimo Non applicabile. Enumerable.Min


in una raccolta.
Queryable.Min

SUM Calcola la somma dei valori Non applicabile. Enumerable.Sum


in una raccolta.
Queryable.Sum

Vedere anche
System.Linq
Cenni preliminari sugli operatori di query standard (C#)
Come calcolare i valori di colonna in un file di testo CSV (LINQ) (C#)
Come eseguire una query per il file o i file più grandi in un albero di directory (LINQ) (C#)
Come eseguire una query per il numero totale di byte in un set di cartelle (LINQ) (C#)
LINQ to Objects (C#)
02/11/2020 • 3 minutes to read • Edit Online

Il termine "LINQ to Objects" si riferisce all'utilizzo diretto di query LINQ con qualsiasi raccolta IEnumerable o
IEnumerable<T>, senza l'utilizzo di un'API o un provider LINQ intermedio, come per LINQ to SQL o LINQ to
XML. È possibile usare LINQ per eseguire una query su qualsiasi raccolta enumerabile, ad esempio List<T>,
Array o Dictionary<TKey,TValue>. La raccolta può essere definita dall'utente o può essere restituita da un'API
.NET.
Come concetto di base, LINQ to Objects rappresenta un nuovo approccio alle raccolte. In passato, era necessario
scrivere cicli foreach complessi che specificavano come recuperare i dati da una raccolta. Con l'approccio LINQ,
è possibile scrivere il codice dichiarativo che descrive i dati da recuperare.
Le query LINQ offrono anche tre vantaggi principali rispetto ai cicli foreach tradizionali:
Sono più brevi e leggibili, soprattutto quando si filtrano più condizioni.
Forniscono funzioni potenti di filtro, ordinamento e raggruppamento con un codice dell'applicazione
minimo.
Possono essere trasferiti in altre origini dati con modifiche minime o nulle.
In generale, più è complessa l'operazione che si vuole eseguire sui dati, maggiore sarà il vantaggio che si
realizzerà usando LINQ anziché le tecniche di iterazione tradizionali.
Lo scopo di questa sezione è illustrare l'approccio LINQ con alcuni esempi specificamente selezionati. Non è
destinato a essere esaustivo.

Contenuto della sezione


LINQ e stringhe (C#)
Viene illustrato come usare LINQ per eseguire query e trasformare stringhe e raccolte di stringhe. Include anche
collegamenti ad articoli che illustrano questi principi.
LINQ e reflection (C#)
Collegamenti a un esempio che illustra l'uso di reflection in LINQ.
Directory di file e LINQ (C#)
Viene illustrato come usare LINQ per interagire con i file system. Sono inoltre inclusi collegamenti ad articoli in
cui vengono illustrati questi concetti.
Come eseguire una query su un ArrayList con LINQ (C#)
Viene illustrato come eseguire una query su un oggetto ArrayList in C#.
Come aggiungere metodi personalizzati per le query LINQ (C#)
Spiega come estendere il set di metodi utilizzabili per le query LINQ aggiungendo metodi di estensione
all'interfaccia IEnumerable<T>.
LINQ (Language-Integrated Query) (C#)
Vengono forniti collegamenti ad articoli che illustrano LINQ e forniscono esempi di codice che eseguono query.
LINQ e stringhe (C#)
02/11/2020 • 5 minutes to read • Edit Online

È possibile usare LINQ per eseguire query e trasformare stringhe e raccolte di stringhe. LINQ può essere
particolarmente utile con i dati semistrutturati nei file di testo. Le query LINQ possono essere usate in
associazione a funzioni per valori stringa tradizionali ed espressioni regolari. Ad esempio, è possibile usare il
metodo String.Split o Regex.Split per creare una matrice di stringhe in cui sarà possibile eseguire query o
apportare modifiche usando LINQ. È possibile usare il metodo Regex.IsMatch nella clausola where di una query
LINQ. È anche possibile usare LINQ per eseguire query o modificare i risultati MatchCollection restituiti da
un'espressione regolare.
È anche possibile usare le tecniche descritte in questa sezione per trasformare dati di testo semistrutturati in
XML. Per ulteriori informazioni, vedere come generare XML da file CSV.
Gli esempi di questa sezione sono suddivisi in due categorie:

Esecuzione di query in un blocco di testo


È possibile eseguire query, analizzare e modificare blocchi di testo suddividendoli in una matrice di stringhe più
piccole sottoponibile a query usando il metodo String.Split o Regex.Split. È possibile suddividere il testo di
origine in parole, frasi, paragrafi, pagine o altri criteri e quindi eseguire altre suddivisioni se richieste nella query.
Come contare le occorrenze di una parola in una stringa (LINQ) (C#)
Descrive come usare LINQ per eseguire query semplici nel testo.
Come eseguire una query per trovare frasi che contengono un set specificato di parole (LINQ) (C#)
Descrive come suddividere file di testo su limiti arbitrari e come eseguire query in ogni parte.
Come eseguire una query per i caratteri in una stringa (LINQ) (C#)
Dimostra che una stringa è un tipo sottoponibile a query.
Come combinare query LINQ con espressioni regolari (C#)
Descrive come usare le espressioni regolari nelle query LINQ per un modello complesso corrispondente
ai risultati della query filtrati.

Esecuzione di query di dati semistrutturati in formato testo


Numerosi tipi di file di testo sono costituiti da una serie di righe, spesso con formattazione simile, ad esempio
file delimitati da tabulazione o virgola o righe a lunghezza fissa. Dopo la lettura del file di testo in memoria, è
possibile usare LINQ per eseguire query e/o modificare le righe. Le query LINQ semplificano anche la
combinazione di dati di più origini.
Come trovare la differenza dei set tra due elenchi (LINQ) (C#)
Descrive come trovare tutte le stringhe presenti in un elenco ma non in un altro elenco.
Come ordinare o filtrare i dati di testo in base a qualsiasi parola o campo (LINQ) (C#)
Descrive come ordinare le righe di testo in base a una parola o un campo.
Come riordinare i campi di un file delimitato (LINQ) (C#)
Descrive come riordinare i campi in una riga in un file con estensione csv.
Come combinare e confrontare raccolte di stringhe (LINQ) (C#)
Descrive come combinare elenchi di stringhe in diversi modi.
Come popolare le raccolte di oggetti da più origini (LINQ) (C#)
Descrive come creare raccolte di oggetti usando più file di testo come origini dati.
Come unire il contenuto da file non analoghi (LINQ) (C#)
Descrive come combinare le stringhe in due elenchi in un'unica stringa usando una chiave
corrispondente.
Come suddividere un file in molti file usando i gruppi (LINQ) (C#)
Descrive come creare file nuovi usando un singolo file come origine dati.
Come calcolare i valori di colonna in un file di testo CSV (LINQ) (C#)
Descrive come eseguire calcoli matematici in dati di testo in file con estensione csv.

Vedere anche
LINQ (Language-Integrated Query) (C#)
Come generare XML da file CSV
Come contare le occorrenze di una parola in una
stringa (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio illustra come usare una query LINQ per contare le occorrenze di una parola specifica all'interno
di una stringa. Si noti che per eseguire il conteggio viene prima chiamato il metodo Split per creare una matrice
di parole. Il metodo Split influisce negativamente sulle prestazioni. Se l'unica operazione da eseguire sulla
stringa è il conteggio delle parole, è consigliabile usare il metodo Matches o IndexOf. Se tuttavia le prestazioni
non rappresentano un problema critico o se la frase è già stata suddivisa per sottoporla ad altri tipi di query,
anche LINQ costituisce una scelta appropriata per contare le parole o le frasi.

Esempio
class CountWords
{
static void Main()
{
string text = @"Historically, the world of data and the world of objects" +
@" have not been well integrated. Programmers work in C# or Visual Basic" +
@" and also in SQL or XQuery. On the one side are concepts such as classes," +
@" objects, fields, inheritance, and .NET APIs. On the other side" +
@" are tables, columns, rows, nodes, and separate languages for dealing with" +
@" them. Data types often require translation between the two worlds; there are" +
@" different standard functions. Because the object world has no notion of query, a" +
@" query can only be represented as a string without compile-time type checking or" +
@" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" +
@" objects in memory is often tedious and error-prone.";

string searchTerm = "data";

//Convert the string into an array of words


string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' },
StringSplitOptions.RemoveEmptyEntries);

// Create the query. Use ToLowerInvariant to match "data" and "Data"


var matchQuery = from word in source
where word.ToLowerInvariant() == searchTerm.ToLowerInvariant()
select word;

// Count the matches, which executes the query.


int wordCount = matchQuery.Count();
Console.WriteLine("{0} occurrences(s) of the search term \"{1}\" were found.", wordCount,
searchTerm);

// Keep console window open in debug mode


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/* Output:
3 occurrences(s) of the search term "data" were found.
*/

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Come eseguire una query per trovare frasi che
contengono un set specificato di parole (LINQ) (C#)
02/11/2020 • 3 minutes to read • Edit Online

Questo esempio illustra come trovare frasi in un file di testo che contengono corrispondenze per ogni set di
parole specificato. Sebbene la matrice dei termini di ricerca sia hardcoded in questo esempio, può essere anche
popolata in modo dinamico durante il runtime. In questo esempio la query restituisce le frasi che contengono le
parole "Historically", "data" e "integrated".

Esempio
class FindSentences
{
static void Main()
{
string text = @"Historically, the world of data and the world of objects " +
@"have not been well integrated. Programmers work in C# or Visual Basic " +
@"and also in SQL or XQuery. On the one side are concepts such as classes, " +
@"objects, fields, inheritance, and .NET APIs. On the other side " +
@"are tables, columns, rows, nodes, and separate languages for dealing with " +
@"them. Data types often require translation between the two worlds; there are " +
@"different standard functions. Because the object world has no notion of query, a " +
@"query can only be represented as a string without compile-time type checking or " +
@"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
@"objects in memory is often tedious and error-prone.";

// Split the text block into an array of sentences.


string[] sentences = text.Split(new char[] { '.', '?', '!' });

// Define the search terms. This list could also be dynamically populated at runtime.
string[] wordsToMatch = { "Historically", "data", "integrated" };

// Find sentences that contain all the terms in the wordsToMatch array.
// Note that the number of terms to match is not specified at compile time.
var sentenceQuery = from sentence in sentences
let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' },
StringSplitOptions.RemoveEmptyEntries)
where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
select sentence;

// Execute the query. Note that you can explicitly type


// the iteration variable here even though sentenceQuery
// was implicitly typed.
foreach (string str in sentenceQuery)
{
Console.WriteLine(str);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/* Output:
Historically, the world of data and the world of objects have not been well integrated
*/

La query funziona suddividendo prima il testo in frasi e quindi suddividendo le frasi in una matrice di stringhe
contenenti ogni parola. Per ognuna di queste matrici, il metodo Distinct rimuove tutte le parole duplicate e
quindi la query esegue un'operazione Intersect sulla matrice di parole e sulla matrice wordsToMatch . Se il
conteggio dell'intersezione corrisponde al conteggio della matrice wordsToMatch , significa che sono state trovate
tutte le parole e viene restituita la frase originale.
Nella chiamata a Split vengono usati i segni di punteggiatura come separatori in modo da rimuoverli dalla
stringa. Se questa operazione non è stata eseguita, è possibile ad esempio avere una stringa "Historically" che
non corrisponde a "Historically" nella matrice wordsToMatch . È possibile che sia necessario usare altri separatori,
a seconda dei tipi di punteggiatura individuati nel testo di origine.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Come eseguire una query per i caratteri in una
stringa (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

Poiché la classe String implementa l'interfaccia generica IEnumerable<T>, è possibile eseguire una query su
qualsiasi stringa come una sequenza di caratteri. Tuttavia, questo uso di LINQ non è comune. Per le operazioni
con criteri di ricerca complessi, usare la classe Regex.

Esempio
Nell'esempio seguente viene eseguita una query su una stringa per determinare il numero di cifre che contiene.
Si noti che, dopo la prima esecuzione, la query viene "riutilizzata". Ciò è possibile perché la query stessa non
archivia i risultati effettivi.

class QueryAString
{
static void Main()
{
string aString = "ABCDE99F-J74-12-89A";

// Select only those characters that are numbers


IEnumerable<char> stringQuery =
from ch in aString
where Char.IsDigit(ch)
select ch;

// Execute the query


foreach (char c in stringQuery)
Console.Write(c + " ");

// Call the Count method on the existing query.


int count = stringQuery.Count();
Console.WriteLine("Count = {0}", count);

// Select all characters before the first '-'


IEnumerable<char> stringQuery2 = aString.TakeWhile(c => c != '-');

// Execute the second query


foreach (char c in stringQuery2)
Console.Write(c);

Console.WriteLine(System.Environment.NewLine + "Press any key to exit");


Console.ReadKey();
}
}
/* Output:
Output: 9 9 7 4 1 2 8 9
Count = 8
ABCDE99F
*/

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.
Vedere anche
LINQ e stringhe (C#)
Come combinare query LINQ con espressioni regolari (C#)
Come combinare query LINQ con espressioni
regolari (C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo esempio viene illustrato come usare la classe Regex per creare un'espressione regolare per una
corrispondenza più complessa nelle stringhe di testo. La query LINQ consente di filtrare esattamente i file che si
vuole cercare tramite l'espressione regolare e di dare forma ai risultati.

Esempio
class QueryWithRegEx
{
public static void Main()
{
// Modify this path as necessary so that it accesses your version of Visual Studio.
string startFolder = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\";
// One of the following paths may be more appropriate on your computer.
//string startFolder = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\";

// Take a snapshot of the file system.


IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

// Create the regular expression to find all things "Visual".


System.Text.RegularExpressions.Regex searchTerm =
new System.Text.RegularExpressions.Regex(@"Visual (Basic|C#|C\+\+|Studio)");

// Search the contents of each .htm file.


// Remove the where clause to find even more matchedValues!
// This query produces a list of files where a match
// was found, and a list of the matchedValues in that file.
// Note: Explicit typing of "Match" in select clause.
// This is required because MatchCollection is not a
// generic IEnumerable collection.
var queryMatchingFiles =
from file in fileList
where file.Extension == ".htm"
let fileText = System.IO.File.ReadAllText(file.FullName)
let matches = searchTerm.Matches(fileText)
where matches.Count > 0
select new
{
name = file.FullName,
matchedValues = from System.Text.RegularExpressions.Match match in matches
select match.Value
};

// Execute the query.


Console.WriteLine("The term \"{0}\" was found in:", searchTerm.ToString());

foreach (var v in queryMatchingFiles)


{
// Trim the path a bit, then write
// the file name in which a match was found.
string s = v.name.Substring(startFolder.Length - 1);
Console.WriteLine(s);

// For this file, write out all the matching strings


foreach (var v2 in v.matchedValues)
{
Console.WriteLine(" " + v2);
Console.WriteLine(" " + v2);
}
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

// This method assumes that the application has discovery


// permissions for all folders under the specified path.
static IEnumerable<System.IO.FileInfo> GetFiles(string path)
{
if (!System.IO.Directory.Exists(path))
throw new System.IO.DirectoryNotFoundException();

string[] fileNames = null;


List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);


foreach (string name in fileNames)
{
files.Add(new System.IO.FileInfo(name));
}
return files;
}
}

Si noti che è anche possibile eseguire una query sull'oggetto MatchCollection restituito da una ricerca RegEx . In
questo esempio viene generato nei risultati solo il valore di ogni corrispondenza. Tuttavia, è anche possibile
usare LINQ per eseguire tutti i tipi di filtro, ordinamento e raggruppamento sulla raccolta. Poiché
MatchCollection è una raccolta non generica IEnumerable, è necessario dichiarare esplicitamente il tipo della
variabile di intervallo nella query.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come trovare la differenza dei set tra due elenchi
(LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

In questo esempio viene illustrato come usare LINQ per confrontare due elenchi di stringhe e restituire le righe
presenti in names1.txt ma non in names2.txt.
Per creare i file di dati
1. Copiare names1.txt e names2.txt nella cartella della soluzione, come illustrato in come combinare e
confrontare raccolte di stringhe (LINQ) (C#).

Esempio
class CompareLists
{
static void Main()
{
// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");

// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =
names1.Except(names2);

// Execute the query.


Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
Console.WriteLine(s);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/* Output:
The following lines are in names1.txt but not names2.txt
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
*/

Alcuni tipi di operazioni di query in C#, ad esempio Except, Distinct, Union e Concat, possono essere espressi
solo nella sintassi basata su metodo.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Come ordinare o filtrare i dati di testo in base a
qualsiasi parola o campo (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

L'esempio seguente illustra come ordinare righe di testo strutturato, ad esempio valori delimitati da virgole, in
base a un qualsiasi campo. Il campo può essere specificato in modo dinamico in runtime. Si supponga che i
campi in scores.csv rappresentino il numero ID di uno studente, seguito da una serie di quattro punteggi di test.
Per creare un file che contenga i dati
1. Copiare i dati scores.csv dall'argomento come unire il contenuto da file non analoghi (LINQ) (C#) e salvarlo
nella cartella della soluzione.

Esempio
public class SortLines
{
static void Main()
{
// Create an IEnumerable data source
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

// Change this to any value from 0 to 4.


int sortField = 1;

Console.WriteLine("Sorted highest to lowest by field [{0}]:", sortField);

// Demonstrates how to return query from a method.


// The query is executed here.
foreach (string str in RunQuery(scores, sortField))
{
Console.WriteLine(str);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

// Returns the query variable, not query results!


static IEnumerable<string> RunQuery(IEnumerable<string> source, int num)
{
// Split the string and sort on field[num]
var scoreQuery = from line in source
let fields = line.Split(',')
orderby fields[num] descending
select line;

return scoreQuery;
}
}
/* Output (if sortField == 1):
Sorted highest to lowest by field [1]:
116, 99, 86, 90, 94
120, 99, 82, 81, 79
111, 97, 92, 81, 60
114, 97, 89, 85, 82
121, 96, 85, 91, 60
122, 94, 92, 91, 91
117, 93, 92, 80, 87
118, 92, 90, 83, 78
113, 88, 94, 65, 91
112, 75, 84, 91, 39
119, 68, 79, 88, 92
115, 35, 72, 91, 70
*/

Questo esempio illustra anche come restituire una variabile di query da un metodo.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Come riordinare i campi di un file delimitato (LINQ)
(C#)
02/11/2020 • 2 minutes to read • Edit Online

Un file con valori delimitati da virgole (CSV) è un file di testo che spesso viene usato per archiviare dati di un
foglio di calcolo o altri dati tabulari rappresentati da righe e colonne. Usando il metodo Split per separare i
campi, è molto semplice eseguire una query e modificare i file CSV tramite LINQ. In effetti la stessa tecnica può
essere usata per riordinare le parti di qualsiasi riga di testo strutturata, non solo i file CSV.
Nell'esempio seguente vengono usate tre colonne per rappresentare "cognome", "nome" e "ID" di alcuni
studenti. I campi sono in ordine alfabetico in base ai cognomi degli studenti. La query genera una nuova
sequenza in cui la colonna ID viene visualizzata per prima, seguita da una seconda colonna che combina il nome
e il cognome dello studente. Le righe vengono riordinate in base al campo ID. I risultati vengono salvati in un
nuovo file e i dati originali non vengono modificati.
Per creare il file di dati
1. Copiare le righe seguenti in un file di testo normale denominato spreadsheet1.csv. Salvare il file nella
cartella del progetto.

Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121

Esempio
class CSVFiles
{
static void Main(string[] args)
{
// Create the IEnumerable data source
string[] lines = System.IO.File.ReadAllLines(@"../../../spreadsheet1.csv");

// Create the query. Put field 2 first, then


// reverse and combine fields 0 and 1 from the old field
IEnumerable<string> query =
from line in lines
let x = line.Split(',')
orderby x[2]
select x[2] + ", " + (x[1] + " " + x[0]);

// Execute the query and write out the new file. Note that WriteAllLines
// takes a string[], so ToArray is called on the query.
System.IO.File.WriteAllLines(@"../../../spreadsheet2.csv", query.ToArray());

Console.WriteLine("Spreadsheet2.csv written to disk. Press any key to exit");


Console.ReadKey();
}
}
/* Output to spreadsheet2.csv:
111, Svetlana Omelchenko
112, Claire O'Donnell
113, Sven Mortensen
114, Cesar Garcia
115, Debra Garcia
116, Fadi Fakhouri
117, Hanying Feng
118, Hugo Garcia
119, Lance Tucker
120, Terry Adams
121, Eugene Zabokritski
122, Michael Tucker
*/

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come generare XML da file CSV (C#)
Come combinare e confrontare raccolte di stringhe
(LINQ) (C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo esempio viene illustrato come unire i file che contengono righe di testo e quindi ordinare i risultati. In
particolare viene illustrato come eseguire una concatenazione semplice, un'unione e un'intersezione su due set
di righe di testo.
Per impostare il progetto e i file di testo
1. Copiare i nomi seguenti in un file di testo denominato names1.txt e salvarlo nella cartella del progetto:

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra

2. Copiare i nomi seguenti in un file di testo denominato names2.txt e salvarlo nella cartella del progetto. Si
noti che i due file hanno alcuni nomi in comune.

Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

Esempio
class MergeStrings
{
static void Main(string[] args)
{
//Put text files in your solution folder
string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

//Simple concatenation and sort. Duplicates are preserved.


IEnumerable<string> concatQuery =
fileA.Concat(fileB).OrderBy(s => s);

// Pass the query variable to another function for execution.


OutputQueryResults(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");

// Concatenate and remove duplicate names based on


// default string comparer.
// default string comparer.
IEnumerable<string> uniqueNamesQuery =
fileA.Union(fileB).OrderBy(s => s);
OutputQueryResults(uniqueNamesQuery, "Union removes duplicate names:");

// Find the names that occur in both files (based on


// default string comparer).
IEnumerable<string> commonNamesQuery =
fileA.Intersect(fileB);
OutputQueryResults(commonNamesQuery, "Merge based on intersect:");

// Find the matching fields in each list. Merge the two


// results by using Concat, and then
// sort using the default string comparer.
string nameMatch = "Garcia";

IEnumerable<String> tempQuery1 =
from name in fileA
let n = name.Split(',')
where n[0] == nameMatch
select name;

IEnumerable<string> tempQuery2 =
from name2 in fileB
let n2 = name2.Split(',')
where n2[0] == nameMatch
select name2;

IEnumerable<string> nameMatchQuery =
tempQuery1.Concat(tempQuery2).OrderBy(s => s);
OutputQueryResults(nameMatchQuery, $"Concat based on partial name match \"{nameMatch}\":");

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

static void OutputQueryResults(IEnumerable<string> query, string message)


{
Console.WriteLine(System.Environment.NewLine + message);
foreach (string item in query)
{
Console.WriteLine(item);
}
Console.WriteLine("{0} total names in list", query.Count());
}
}
/* Output:
Simple concatenate and sort. Duplicates are preserved:
Aw, Kam Foo
Bankov, Peter
Bankov, Peter
Beebe, Ann
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Guy, Wey Yuan
Holm, Michael
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
20 total names in list
20 total names in list

Union removes duplicate names:


Aw, Kam Foo
Bankov, Peter
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Guy, Wey Yuan
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
16 total names in list

Merge based on intersect:


Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
4 total names in list

Concat based on partial name match "Garcia":


Garcia, Debra
Garcia, Hugo
Garcia, Hugo
3 total names in list
*/

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come popolare le raccolte di oggetti da più origini
(LINQ) (C#)
02/11/2020 • 5 minutes to read • Edit Online

In questo esempio viene illustrato come unire dati da origini diverse in una sequenza di tipi nuovi.

NOTE
Non provare a creare un join di dati in memoria o nel file system con dati che sono ancora in un database. Questi join tra
domini possono generare risultati non definiti a causa dei diversi modi in cui vengono definite le operazioni di join per le
query di database e per altri tipi di origini. È anche possibile che tale operazione possa generare un'eccezione di memoria
insufficiente se la quantità di dati nel database è piuttosto grande. Per creare un join di dati di un database con i dati in
memoria, chiamare prima ToList o ToArray nella query di database e quindi creare il join nella raccolta restituita.

Per creare il file di dati


Copiare i file names.csv e scores.csv nella cartella del progetto, come descritto in come unire il contenuto da file
non analoghi (LINQ) (C#).

Esempio
Nell'esempio seguente viene illustrato come usare un tipo denominato Student per archiviare i dati uniti da
due raccolte di stringhe in memoria che simulano i dati del foglio di calcolo in formato CSV. La prima raccolta di
stringhe rappresenta i nomi e gli ID degli studenti e la seconda raccolta rappresenta gli ID degli studenti, nella
prima colonna, e i punteggi di quattro esami. L'ID viene usato come chiave esterna.

using System;
using System.Collections.Generic;
using System.Linq;

class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}

class PopulateCollection
{
static void Main()
{
// These data files are defined in How to join content from
// dissimilar files (LINQ).

// Each line of names.csv consists of a last name, a first name, and an


// ID number, separated by commas. For example, Omelchenko,Svetlana,111
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");

// Each line of scores.csv consists of an ID number and four test


// scores, separated by commas. For example, 111, 97, 92, 81, 60
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

// Merge the data sources using a named type.


// var could be used instead of an explicit type. Note the dynamic
// var could be used instead of an explicit type. Note the dynamic
// creation of a list of ints for the ExamScores member. The first item
// is skipped in the split string because it is the student ID,
// not an exam score.
IEnumerable<Student> queryNamesScores =
from nameLine in names
let splitName = nameLine.Split(',')
from scoreLine in scores
let splitScoreLine = scoreLine.Split(',')
where Convert.ToInt32(splitName[2]) == Convert.ToInt32(splitScoreLine[0])
select new Student()
{
FirstName = splitName[0],
LastName = splitName[1],
ID = Convert.ToInt32(splitName[2]),
ExamScores = (from scoreAsText in splitScoreLine.Skip(1)
select Convert.ToInt32(scoreAsText)).
ToList()
};

// Optional. Store the newly created student objects in memory


// for faster access in future queries. This could be useful with
// very large data files.
List<Student> students = queryNamesScores.ToList();

// Display each student's name and exam score average.


foreach (var student in students)
{
Console.WriteLine("The average score of {0} {1} is {2}.",
student.FirstName, student.LastName,
student.ExamScores.Average());
}

//Keep console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
The average score of Omelchenko Svetlana is 82.5.
The average score of O'Donnell Claire is 72.25.
The average score of Mortensen Sven is 84.5.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Hugo is 85.75.
The average score of Tucker Lance is 81.75.
The average score of Adams Terry is 85.25.
The average score of Zabokritski Eugene is 83.
The average score of Tucker Michael is 92.
*/

Nella clausola select viene usato un inizializzatore di oggetto per creare un'istanza di ogni nuovo oggetto
Student con i dati delle due origini.

Se non è necessario archiviare i risultati della query, può essere più utile usare i tipi anonimi rispetto ai tipi
denominati. I tipi denominati sono necessari se si passano i risultati della query al di fuori del metodo in cui
viene eseguita la query. Nell'esempio seguente viene eseguita la stessa attività dell'esempio precedente, ma
vengono usati i tipi anonimi al posto dei tipi denominati:
// Merge the data sources by using an anonymous type.
// Note the dynamic creation of a list of ints for the
// ExamScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
var queryNamesScores2 =
from nameLine in names
let splitName = nameLine.Split(',')
from scoreLine in scores
let splitScoreLine = scoreLine.Split(',')
where Convert.ToInt32(splitName[2]) == Convert.ToInt32(splitScoreLine[0])
select new
{
First = splitName[0],
Last = splitName[1],
ExamScores = (from scoreAsText in splitScoreLine.Skip(1)
select Convert.ToInt32(scoreAsText))
.ToList()
};

// Display each student's name and exam score average.


foreach (var student in queryNamesScores2)
{
Console.WriteLine("The average score of {0} {1} is {2}.",
student.First, student.Last, student.ExamScores.Average());
}

Vedere anche
LINQ e stringhe (C#)
Inizializzatori di oggetto e di raccolta
Tipi anonimi
Come suddividere un file in molti file usando i
gruppi (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio illustra un modo per unire il contenuto di due file e creare quindi un set di nuovi file in cui i dati
sono organizzati in modo diverso.
Per creare i file di dati
1. Copiare i nomi seguenti in un file di testo denominato names1.txt e salvarlo nella cartella del progetto:

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra

2. Copiare i nomi seguenti in un file di testo denominato names2.txt e salvarlo nella cartella del progetto. Si
noti che i due file hanno alcuni nomi in comune.

Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

Esempio
class SplitWithGroups
{
static void Main()
{
string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

// Concatenate and remove duplicate names based on


// default string comparer
var mergeQuery = fileA.Union(fileB);

// Group the names by the first letter in the last name.


var groupQuery = from name in mergeQuery
let n = name.Split(',')
group name by n[0][0] into g
orderby g.Key
select g;
// Create a new file for each group that was created
// Note that nested foreach loops are required to access
// individual items with each group.
foreach (var g in groupQuery)
{
// Create the new file name.
string fileName = @"../../../testFile_" + g.Key + ".txt";

// Output to display.
Console.WriteLine(g.Key);

// Write file.
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
{
foreach (var item in g)
{
sw.WriteLine(item);
// Output to console for example purposes.
Console.WriteLine(" {0}", item);
}
}
}
// Keep console window open in debug mode.
Console.WriteLine("Files have been written. Press any key to exit");
Console.ReadKey();
}
}
/* Output:
A
Aw, Kam Foo
B
Bankov, Peter
Beebe, Ann
E
El Yassir, Mehdi
G
Garcia, Hugo
Guy, Wey Yuan
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
H
Holm, Michael
L
Liu, Jinghao
M
Myrcha, Jacek
McLin, Nkenge
N
Noriega, Fabricio
P
Potra, Cristina
T
Toyoshima, Tim
*/

Il programma scrive un file separato per ogni gruppo nella stessa cartella dei file di dati.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come unire il contenuto da file non analoghi (LINQ)
(C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo esempio viene illustrato come eseguire un join di dati da due file con valori delimitati da virgole che
condividono un valore comune usato come una chiave corrispondente. Questa tecnica può essere utile se è
necessario combinare dati provenienti da due fogli di calcolo, o da un foglio di calcolo e da un file con un altro
formato, in un nuovo file. È possibile modificare l'esempio in modo che funzioni con qualsiasi tipo di testo
strutturato.

Per creare i file di dati


1. Copiare le righe seguenti in un file denominato scores.csv e salvarlo nella cartella del progetto. Il file
rappresenta i dati del foglio di calcolo. La colonna 1 è l'ID studente e le colonne da 2 a 5 sono i punteggi
dei test.

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

2. Copiare le righe seguenti in un file denominato names.csv e salvarlo nella cartella del progetto. Il file
rappresenta un foglio di calcolo che contiene il cognome, il nome e l'ID degli studenti.

Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122

Esempio
using System;
using System.Collections.Generic;
using System.Linq;

class JoinStrings
{
static void Main()
{
// Join content from dissimilar files that contain
// related information. File names.csv contains the student
// name plus an ID number. File scores.csv contains the ID
// and a set of four test scores. The following query joins
// the scores to the student names by using ID as a
// matching key.

string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");


string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

// Name: Last[0], First[1], ID[2]


// Omelchenko, Svetlana, 11
// Score: StudentID[0], Exam1[1] Exam2[2], Exam3[3], Exam4[4]
// 111, 97, 92, 81, 60

// This query joins two dissimilar spreadsheets based on common ID value.


// Multiple from clauses are used instead of a join clause
// in order to store results of id.Split.
IEnumerable<string> scoreQuery1 =
from name in names
let nameFields = name.Split(',')
from id in scores
let scoreFields = id.Split(',')
where Convert.ToInt32(nameFields[2]) == Convert.ToInt32(scoreFields[0])
select nameFields[0] + "," + scoreFields[1] + "," + scoreFields[2]
+ "," + scoreFields[3] + "," + scoreFields[4];

// Pass a query variable to a method and execute it


// in the method. The query itself is unchanged.
OutputQueryResults(scoreQuery1, "Merge two spreadsheets:");

// Keep console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

static void OutputQueryResults(IEnumerable<string> query, string message)


{
Console.WriteLine(System.Environment.NewLine + message);
foreach (string item in query)
{
Console.WriteLine(item);
}
Console.WriteLine("{0} total names in list", query.Count());
}
}
/* Output:
Merge two spreadsheets:
Omelchenko, 97, 92, 81, 60
O'Donnell, 75, 84, 91, 39
Mortensen, 88, 94, 65, 91
Garcia, 97, 89, 85, 82
Garcia, 35, 72, 91, 70
Fakhouri, 99, 86, 90, 94
Feng, 93, 92, 80, 87
Garcia, 92, 90, 83, 78
Tucker, 68, 79, 88, 92
Adams, 99, 82, 81, 79
Zabokritski, 96, 85, 91, 60
Tucker, 94, 92, 91, 91
12 total names in list
*/

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come calcolare i valori di colonna in un file di testo
CSV (LINQ) (C#)
02/11/2020 • 4 minutes to read • Edit Online

In questo esempio viene illustrato come eseguire i calcoli di aggregazione quali Sum, Average, Min e Max nelle
colonne di un file con estensione csv. I principi di esempio riportati di seguito possono essere applicati ad altri
tipi di testo strutturati.

Per creare il file di origine


1. Copiare le righe seguenti in un file denominato scores.csv e salvarlo nella cartella del progetto. Si
supponga che la prima colonna rappresenti degli ID studente e che le colonne successive rappresentino i
punteggi di quattro esami.

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

Esempio
class SumColumns
{
static void Main(string[] args)
{
string[] lines = System.IO.File.ReadAllLines(@"../../../scores.csv");

// Specifies the column to compute.


int exam = 3;

// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60

// Add one to exam to skip over the first column,


// which holds the student ID.
SingleColumn(lines, exam + 1);
Console.WriteLine();
MultiColumns(lines);

Console.WriteLine("Press any key to exit");


Console.ReadKey();
}

static void SingleColumn(IEnumerable<string> strs, int examNum)


{
Console.WriteLine("Single Column Query:");
// Parameter examNum specifies the column to
// run the calculations on. This value could be
// passed in dynamically at runtime.

// Variable columnQuery is an IEnumerable<int>.


// The following query performs two steps:
// 1) use Split to break each row (a string) into an array
// of strings,
// 2) convert the element at position examNum to an int
// and select it.
var columnQuery =
from line in strs
let elements = line.Split(',')
select Convert.ToInt32(elements[examNum]);

// Execute the query and cache the results to improve


// performance. This is helpful only with very large files.
var results = columnQuery.ToList();

// Perform aggregate calculations Average, Max, and


// Min on the column specified by examNum.
double average = results.Average();
int max = results.Max();
int min = results.Min();

Console.WriteLine("Exam #{0}: Average:{1:##.##} High Score:{2} Low Score:{3}",


examNum, average, max, min);
}

static void MultiColumns(IEnumerable<string> strs)


{
Console.WriteLine("Multi Column Query:");

// Create a query, multiColQuery. Explicit typing is used


// to make clear that, when executed, multiColQuery produces
// nested sequences. However, you get the same results by
// using 'var'.

// The multiColQuery query performs the following steps:


// 1) use Split to break each row (a string) into an array
// of strings,
// 2) use Skip to skip the "Student ID" column, and store the
// rest of the row in scores.
// 3) convert each score in the current row from a string to
// an int, and select that entire sequence as one row
// in the results.
IEnumerable<IEnumerable<int>> multiColQuery =
from line in strs
let elements = line.Split(',')
let scores = elements.Skip(1)
select (from str in scores
select Convert.ToInt32(str));

// Execute the query and cache the results to improve


// performance.
// ToArray could be used instead of ToList.
var results = multiColQuery.ToList();

// Find out how many columns you have in results.


int columnCount = results[0].Count();

// Perform aggregate calculations Average, Max, and


// Min on each column.
// Perform one iteration of the loop for each column
// of scores.
// You can use a for loop instead of a foreach loop
// because you already executed the multiColQuery
// query by calling ToList.
for (int column = 0; column < columnCount; column++)
for (int column = 0; column < columnCount; column++)
{
var results2 = from row in results
select row.ElementAt(column);
double average = results2.Average();
int max = results2.Max();
int min = results2.Min();

// Add one to column because the first exam is Exam #1,


// not Exam #0.
Console.WriteLine("Exam #{0} Average: {1:##.##} High Score: {2} Low Score: {3}",
column + 1, average, max, min);
}
}
}
/* Output:
Single Column Query:
Exam #4: Average:76.92 High Score:94 Low Score:39

Multi Column Query:


Exam #1 Average: 86.08 High Score: 99 Low Score: 35
Exam #2 Average: 86.42 High Score: 94 Low Score: 72
Exam #3 Average: 84.75 High Score: 91 Low Score: 65
Exam #4 Average: 76.92 High Score: 94 Low Score: 39
*/

La query funziona usando il metodo Split per convertire ogni riga di testo in una matrice. Ogni elemento della
matrice rappresenta una colonna. Infine, il testo in ogni colonna viene convertito in una rappresentazione
numerica. Se il file è un file con valori delimitati da tabulazioni, aggiornare solo l'argomento nel metodo Split
in \t .

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ e stringhe (C#)
Directory di file e LINQ (C#)
Come eseguire una query sui metadati di un
assembly tramite reflection (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile utilizzare le API di Reflection .NET per esaminare i metadati in un assembly .NET e creare raccolte di
tipi, membri dei tipi, parametri e così via che si trovano in tale assembly. Poiché queste raccolte supportano
l'interfaccia generica IEnumerable<T>, è possibile eseguire query su tali raccolte tramite LINQ.
Nell'esempio seguente viene illustrato come LINQ può essere usato con il processo di reflection per recuperare
metadati specifici sui metodi che corrispondono a un criterio di ricerca specificato. In questo caso la query
individuerà i nomi di tutti i metodi dell'assembly che restituiscono tipi enumerabili, ad esempio matrici.

Esempio
using System;
using System.Linq;
using System.Reflection;

class ReflectionHowTO
{
static void Main()
{
Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=
b77a5c561934e089");
var pubTypesQuery = from type in assembly.GetTypes()
where type.IsPublic
from method in type.GetMethods()
where method.ReturnType.IsArray == true
|| ( method.ReturnType.GetInterface(
typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
&& method.ReturnType.FullName != "System.String" )
group method.ToString() by type.ToString();

foreach (var groupOfMethods in pubTypesQuery)


{
Console.WriteLine("Type: {0}", groupOfMethods.Key);
foreach (var method in groupOfMethods)
{
Console.WriteLine(" {0}", method);
}
}

Console.WriteLine("Press any key to exit... ");


Console.ReadKey();
}
}

L'esempio usa il metodo Assembly.GetTypes per restituire una matrice di tipi nell'assembly specificato. Il filtro
where viene applicato in modo che vengano restituiti solo i tipi pubblici. Per ogni tipo pubblico, viene generata
una sottoquery usando la matrice MethodInfo restituita dalla chiamata a Type.GetMethods. Questi risultati
vengono filtrati per restituire solo i metodi il cui tipo restituito è una matrice oppure un tipo che implementa
IEnumerable<T>. Infine, questi risultati vengono raggruppati usando il nome del tipo come chiave.

Vedere anche
LINQ to Objects (C#)
Come eseguire una query sui metadati di un
assembly tramite reflection (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile utilizzare le API di Reflection .NET per esaminare i metadati in un assembly .NET e creare raccolte di
tipi, membri dei tipi, parametri e così via che si trovano in tale assembly. Poiché queste raccolte supportano
l'interfaccia generica IEnumerable<T>, è possibile eseguire query su tali raccolte tramite LINQ.
Nell'esempio seguente viene illustrato come LINQ può essere usato con il processo di reflection per recuperare
metadati specifici sui metodi che corrispondono a un criterio di ricerca specificato. In questo caso la query
individuerà i nomi di tutti i metodi dell'assembly che restituiscono tipi enumerabili, ad esempio matrici.

Esempio
using System;
using System.Linq;
using System.Reflection;

class ReflectionHowTO
{
static void Main()
{
Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=
b77a5c561934e089");
var pubTypesQuery = from type in assembly.GetTypes()
where type.IsPublic
from method in type.GetMethods()
where method.ReturnType.IsArray == true
|| ( method.ReturnType.GetInterface(
typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
&& method.ReturnType.FullName != "System.String" )
group method.ToString() by type.ToString();

foreach (var groupOfMethods in pubTypesQuery)


{
Console.WriteLine("Type: {0}", groupOfMethods.Key);
foreach (var method in groupOfMethods)
{
Console.WriteLine(" {0}", method);
}
}

Console.WriteLine("Press any key to exit... ");


Console.ReadKey();
}
}

L'esempio usa il metodo Assembly.GetTypes per restituire una matrice di tipi nell'assembly specificato. Il filtro
where viene applicato in modo che vengano restituiti solo i tipi pubblici. Per ogni tipo pubblico, viene generata
una sottoquery usando la matrice MethodInfo restituita dalla chiamata a Type.GetMethods. Questi risultati
vengono filtrati per restituire solo i metodi il cui tipo restituito è una matrice oppure un tipo che implementa
IEnumerable<T>. Infine, questi risultati vengono raggruppati usando il nome del tipo come chiave.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
02/11/2020 • 5 minutes to read • Edit Online

Molte file system operazioni sono essenzialmente query e sono pertanto ideali per l'approccio LINQ.
Le query in questa sezione non sono distruttive. Non vengono usate per modificare il contenuto dei file o delle
cartelle originali. Ne consegue la regola per cui le query non dovrebbero causare effetti collaterali. In generale, il
codice (incluse le query che eseguono operatori create/update/delete) che modifica i dati di origine deve essere
mantenuto separato dal codice che esegue esclusivamente query sui dati.
Questa sezione contiene i seguenti argomenti:
Come eseguire una query per i file con un attributo o un nome specifico (C#)
La procedura illustra come cercare file esaminando una o più proprietà del relativo oggetto FileInfo.
Come raggruppare i file per estensione (LINQ) (C#)
La procedura illustra come restituire gruppi dell'oggetto FileInfo in base all'estensione del file.
Come eseguire una query per il numero totale di byte in un set di cartelle (LINQ) (C#)
La procedura illustra come restituire il numero totale di byte in tutti i file all'interno di un albero di directory
specificato.
Come confrontare il contenuto di due cartelle (LINQ) (C#)s
La procedura illustra come restituire tutti i file presenti in due cartelle specifiche e tutti i file presenti in una
cartella, ma non nell'altra.
Come eseguire una query per il file o i file più grandi in un albero di directory (LINQ) (C#)
La procedura illustra come ripristinare il file più grande, il file più piccolo o un numero specificato di file in un
albero di directory.
Come eseguire una query per i file duplicati in un albero di directory (LINQ) (C#)
La procedura illustra come raggruppare tutti i nomi file che si verificano in più di una posizione all'interno di un
albero di directory specificato. Viene anche illustrato come eseguire confronti più complessi basati su un
operatore di confronto personalizzato.
Come eseguire una query sul contenuto dei file in una cartella (LINQ) (C#)
La procedura illustra come eseguire l'iterazione nelle cartelle in un albero, aprire ogni file ed eseguire query sul
contenuto del file.

Commenti
La creazione di un'origine dati che rappresenta accuratamente il contenuto del file system e gestisce
correttamente le eccezioni comporta un certo livello di complessità. Negli esempi riportati in questa sezione
viene creata una raccolta di snapshot di oggetti FileInfo, che rappresenta tutti i file in una cartella radice
specificata e in tutte le relative sottocartelle. Lo stato effettivo di ogni FileInfo può cambiare nel tempo tra l'inizio
e la fine dell'esecuzione di una query. Ad esempio, è possibile creare un elenco di oggetti FileInfo da usare come
origine dati. Se si tenta di accedere alla proprietà Length in una query, l'oggetto FileInfo tenterà di accedere al
file system per aggiornare il valore di Length . Se il file non esiste più, nella query si otterrà un'eccezione
FileNotFoundException, anche se non si sta eseguendo una query direttamente nel file system. Alcune query in
questa sezione usano un metodo separato che impiega queste particolari eccezioni in determinati casi. Un'altra
opzione consiste nel mantenere l'origine dati aggiornata in modo dinamico tramite FileSystemWatcher.
Vedere anche
LINQ to Objects (C#)
Come eseguire una query per i file con un attributo
o un nome specifico (C#)
02/11/2020 • 2 minutes to read • Edit Online

In questo esempio viene illustrato come trovare tutti i file con un'estensione del nome specificata, come ad
esempio "txt", in un albero di directory specificato. Viene anche illustrato come restituire il file più recente o
meno recente nell'albero in base all'ora di creazione.

Esempio
class FindFileByExtension
{
// This query will produce the full path for all .txt files
// under the specified folder including subfolders.
// It orders the list according to the file name.
static void Main()
{
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// This method assumes that the application has discovery permissions


// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

//Create the query


IEnumerable<System.IO.FileInfo> fileQuery =
from file in fileList
where file.Extension == ".txt"
orderby file.Name
select file;

//Execute the query. This might write out a lot of files!


foreach (System.IO.FileInfo fi in fileQuery)
{
Console.WriteLine(fi.FullName);
}

// Create and execute a new query by using the previous


// query as a starting point. fileQuery is not
// executed again until the call to Last()
var newestFile =
(from file in fileQuery
orderby file.CreationTime
select new { file.FullName, file.CreationTime })
.Last();

Console.WriteLine("\r\nThe newest .txt file is {0}. Creation time: {1}",


newestFile.FullName, newestFile.CreationTime);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Compilazione del codice
Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come raggruppare i file per estensione (LINQ) (C#)
02/11/2020 • 4 minutes to read • Edit Online

Questo esempio illustra come usare LINQ per eseguire operazioni avanzate di raggruppamento e ordinamento
su elenchi di file o cartelle. Illustra anche come disporre l'output nella finestra della console usando i metodi
Skip e Take.

Esempio
La query seguente illustra come raggruppare il contenuto di un albero di directory specificato per l'estensione
dei nomi dei file.

class GroupByExtension
{
// This query will sort all the files under the specified folder
// and subfolder into groups keyed by the file extension.
private static void Main()
{
// Take a snapshot of the file system.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

// Used in WriteLine to trim output lines.


int trimLength = startFolder.Length;

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// This method assumes that the application has discovery permissions


// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

// Create the query.


var queryGroupByExt =
from file in fileList
group file by file.Extension.ToLower() into fileGroup
orderby fileGroup.Key
select fileGroup;

// Display one group at a time. If the number of


// entries is greater than the number of lines
// in the console window, then page the output.
PageOutput(trimLength, queryGroupByExt);
}

// This method specifically handles group queries of FileInfo objects with string keys.
// It can be modified to work for any long listings of data. Note that explicit typing
// must be used in method signatures. The groupbyExtList parameter is a query that produces
// groups of FileInfo objects with string keys.
private static void PageOutput(int rootLength,
IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>>
groupByExtList)
{
// Flag to break out of paging loop.
bool goAgain = true;

// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;

// Iterate through the outer collection of groups.


foreach (var filegroup in groupByExtList)
foreach (var filegroup in groupByExtList)
{
// Start a new extension at the top of a page.
int currentLine = 0;

// Output only as many lines of the current group as will fit in the window.
do
{
Console.Clear();
Console.WriteLine(filegroup.Key == String.Empty ? "[none]" : filegroup.Key);

// Get 'numLines' number of items starting at number 'currentLine'.


var resultPage = filegroup.Skip(currentLine).Take(numLines);

//Execute the resultPage query


foreach (var f in resultPage)
{
Console.WriteLine("\t{0}", f.FullName.Substring(rootLength));
}

// Increment the line counter.


currentLine += numLines;

// Give the user a chance to escape.


Console.WriteLine("Press any key to continue or the 'End' key to break...");
ConsoleKey key = Console.ReadKey().Key;
if (key == ConsoleKey.End)
{
goAgain = false;
break;
}
} while (currentLine < filegroup.Count());

if (goAgain == false)
break;
}
}
}

L'output di questo programma può essere lungo, a seconda dei dettagli del file system locale e
dell'impostazione di startFolder . Per abilitare la visualizzazione di tutti i risultati, in questo esempio viene
illustrato come scorrere i risultati. È possibile applicare le stesse tecniche ad applicazioni Windows e Web. Si noti
che, poiché il codice dispone gli elementi in un gruppo, è necessario un ciclo foreach annidato. È disponibile
anche una logica aggiuntiva per calcolare la posizione corrente nell'elenco e per consentire all'utente di
interrompere lo scorrimento e uscire dal programma. In questo caso particolare la query di scorrimento viene
eseguita sui risultati memorizzati nella cache della query originale. In altri contesti, come ad esempio LINQ to
SQL, la memorizzazione nella cache non è necessaria.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come eseguire una query per il numero totale di
byte in un set di cartelle (LINQ) (C#)
02/11/2020 • 3 minutes to read • Edit Online

Questo esempio illustra come recuperare il numero totale di byte usati da tutti i file in una cartella specificata e
in tutte le relative sottocartelle.

Esempio
Il metodo Sum aggiunge i valori di tutti gli elementi selezionati nella clausola select . È possibile modificare
facilmente questa query per recuperare il file più grande o più piccolo nell'albero di directory specificato
chiamando il metodo Min o Max invece di Sum.
class QuerySize
{
public static void Main()
{
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\VC#";

// Take a snapshot of the file system.


// This method assumes that the application has discovery permissions
// for all folders under the specified path.
IEnumerable<string> fileList = System.IO.Directory.GetFiles(startFolder, "*.*",
System.IO.SearchOption.AllDirectories);

var fileQuery = from file in fileList


select GetFileLength(file);

// Cache the results to avoid multiple trips to the file system.


long[] fileLengths = fileQuery.ToArray();

// Return the size of the largest file


long largestFile = fileLengths.Max();

// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();

Console.WriteLine("There are {0} bytes in {1} files under {2}",


totalBytes, fileList.Count(), startFolder);
Console.WriteLine("The largest files is {0} bytes.", largestFile);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

// This method is used to swallow the possible exception


// that can be raised when accessing the System.IO.FileInfo.Length property.
static long GetFileLength(string filename)
{
long retval;
try
{
System.IO.FileInfo fi = new System.IO.FileInfo(filename);
retval = fi.Length;
}
catch (System.IO.FileNotFoundException)
{
// If a file is no longer present,
// just add zero bytes to the total.
retval = 0;
}
return retval;
}
}

Se è necessario contare solo il numero di byte in un albero di directory specificato, è possibile eseguire questa
operazione in modo più efficiente senza creare una query LINQ che comporta un sovraccarico dovuto alla
creazione della raccolta di elenchi come origine dati. I vantaggi dell'uso di LINQ aumentano per le query più
complesse oppure quando è necessario eseguire più query nella stessa origine dati.
La query effettua una chiamata a un metodo separato per ottenere la lunghezza del file. Questa operazione
viene eseguita per gestire la possibile eccezione che viene generata nel caso in cui il file sia stato eliminato in un
altro thread dopo la creazione dell'oggetto FileInfo nella chiamata a GetFiles . Anche se l'oggetto FileInfo è già
stato creato, è possibile che si verifichi un'eccezione perché un oggetto FileInfo tenterà di aggiornare la relativa
proprietà Length usando la lunghezza più recente quando viene eseguito per la prima volta l'accesso alla
proprietà. Inserendo questa operazione in un blocco try/catch all'esterno della query, si evita di eseguire
operazioni nelle query che possono causare effetti collaterali. In generale, è necessario prestare particolare
attenzione durante la gestione delle eccezioni per assicurarsi che un'applicazione non venga lasciata in uno stato
sconosciuto.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come confrontare il contenuto di due cartelle
(LINQ) (C#)
02/11/2020 • 3 minutes to read • Edit Online

In questo esempio vengono illustrati tre modi per confrontare due elenchi di file:
Eseguendo una query su un valore booleano che specifica se i due elenchi di file sono identici.
Eseguendo una query sull'intersezione per recuperare i file presenti in entrambe le cartelle.
Eseguendo una query sulla differenza tra set per recuperare i file che sono presenti in una cartella, ma
non nell'altra.

NOTE
Le tecniche illustrate di seguito possono essere adattate per confrontare le sequenze di oggetti di qualsiasi tipo.

La classe FileComparer descritta in questo argomento illustra come usare una classe di operatori di confronto
personalizzata insieme con gli operatori query standard. La classe non è destinata all'uso in scenari reali. Si
limita a usare il nome e la lunghezza in byte di ogni file per determinare se il contenuto di ogni cartella è
identico o meno. In uno scenario reale è necessario modificare questo operatore di confronto per eseguire un
controllo di uguaglianza più rigoroso.

Esempio
namespace QueryCompareTwoDirs
{
class CompareDirs
{

static void Main(string[] args)


{

// Create two identical or different temporary folders


// on a local drive and change these file paths.
string pathA = @"C:\TestDir";
string pathB = @"C:\TestDir2";

System.IO.DirectoryInfo dir1 = new System.IO.DirectoryInfo(pathA);


System.IO.DirectoryInfo dir2 = new System.IO.DirectoryInfo(pathB);

// Take a snapshot of the file system.


IEnumerable<System.IO.FileInfo> list1 = dir1.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);
IEnumerable<System.IO.FileInfo> list2 = dir2.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

//A custom file comparer defined below


FileCompare myFileCompare = new FileCompare();

// This query determines whether the two folders contain


// identical file lists, based on the custom file comparer
// that is defined in the FileCompare class.
// The query executes immediately because it returns a bool.
bool areIdentical = list1.SequenceEqual(list2, myFileCompare);
if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}

// Find the common files. It produces a sequence and doesn't


// execute until the foreach statement.
var queryCommonFiles = list1.Intersect(list2, myFileCompare);

if (queryCommonFiles.Any())
{
Console.WriteLine("The following files are in both folders:");
foreach (var v in queryCommonFiles)
{
Console.WriteLine(v.FullName); //shows which items end up in result list
}
}
else
{
Console.WriteLine("There are no common files in the two folders.");
}

// Find the set difference between the two folders.


// For this example we only check one way.
var queryList1Only = (from file in list1
select file).Except(list2, myFileCompare);

Console.WriteLine("The following files are in list1 but not list2:");


foreach (var v in queryList1Only)
{
Console.WriteLine(v.FullName);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

// This implementation defines a very simple comparison


// between two FileInfo objects. It only compares the name
// of the files being compared and their length in bytes.
class FileCompare : System.Collections.Generic.IEqualityComparer<System.IO.FileInfo>
{
public FileCompare() { }

public bool Equals(System.IO.FileInfo f1, System.IO.FileInfo f2)


{
return (f1.Name == f2.Name &&
f1.Length == f2.Length);
}

// Return a hash that reflects the comparison criteria. According to the


// rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
// also be equal. Because equality as defined here is a simple value equality, not
// reference identity, it is possible that two or more objects will produce the same
// hash code.
public int GetHashCode(System.IO.FileInfo fi)
{
string s = $"{fi.Name}{fi.Length}";
return s.GetHashCode();
}
}
}
Compilazione del codice
Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come eseguire una query per il file o i file più
grandi in un albero di directory (LINQ) (C#)
02/11/2020 • 5 minutes to read • Edit Online

Questo esempio illustra cinque query relative alla dimensione dei file in byte:
Come recuperare la dimensione in byte del file più grande.
Come recuperare la dimensione in byte del file più piccolo.
Come recuperare il file più grande o più piccolo dell'oggetto FileInfo da una o più cartelle in una cartella
radice specificata.
Come recuperare una sequenza, ad esempio i 10 file più grandi.
Come ordinare i file in gruppi in base alla dimensione del file in byte, ignorando i file di dimensione
inferiore a un valore specificato.

Esempio
L'esempio seguente contiene cinque query distinte che illustrano come eseguire una query e raggruppare i file
in base alle dimensioni in byte. È possibile modificare facilmente questi esempi per basare la query su un'altra
proprietà dell'oggetto FileInfo.

class QueryBySize
{
static void Main(string[] args)
{
QueryFilesBySize();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

private static void QueryFilesBySize()


{
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// This method assumes that the application has discovery permissions


// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

//Return the size of the largest file


long maxSize =
(from file in fileList
let len = GetFileLength(file)
select len)
.Max();

Console.WriteLine("The length of the largest file under {0} is {1}",


startFolder, maxSize);

// Return the FileInfo object for the largest file


// by sorting and selecting from beginning of list
System.IO.FileInfo longestFile =
(from file in fileList
(from file in fileList
let len = GetFileLength(file)
where len > 0
orderby len descending
select file)
.First();

Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes",
startFolder, longestFile.FullName, longestFile.Length);

//Return the FileInfo of the smallest file


System.IO.FileInfo smallestFile =
(from file in fileList
let len = GetFileLength(file)
where len > 0
orderby len ascending
select file).First();

Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes",
startFolder, smallestFile.FullName, smallestFile.Length);

//Return the FileInfos for the 10 largest files


// queryTenLargest is an IEnumerable<System.IO.FileInfo>
var queryTenLargest =
(from file in fileList
let len = GetFileLength(file)
orderby len descending
select file).Take(10);

Console.WriteLine("The 10 largest files under {0} are:", startFolder);

foreach (var v in queryTenLargest)


{
Console.WriteLine("{0}: {1} bytes", v.FullName, v.Length);
}

// Group the files according to their size, leaving out


// files that are less than 200000 bytes.
var querySizeGroups =
from file in fileList
let len = GetFileLength(file)
where len > 0
group file by (len / 100000) into fileGroup
where fileGroup.Key >= 2
orderby fileGroup.Key descending
select fileGroup;

foreach (var filegroup in querySizeGroups)


{
Console.WriteLine(filegroup.Key.ToString() + "00000");
foreach (var item in filegroup)
{
Console.WriteLine("\t{0}: {1}", item.Name, item.Length);
}
}
}

// This method is used to swallow the possible exception


// that can be raised when accessing the FileInfo.Length property.
// In this particular case, it is safe to swallow the exception.
static long GetFileLength(System.IO.FileInfo fi)
{
long retval;
try
{
retval = fi.Length;
}
catch (System.IO.FileNotFoundException)
{
// If a file is no longer present,
// just add zero bytes to the total.
retval = 0;
}
return retval;
}

Per restituire uno o più oggetti FileInfo completi, è necessario che la query esamini prima di tutto ogni oggetto
nell'origine dati e quindi ordini gli oggetti in base al valore della relativa proprietà Length. La query potrà quindi
restituire il singolo oggetto o la sequenza con le lunghezze maggiori. Usare First per restituire il primo elemento
di un elenco. Usare Take per restituire i primi n elementi. Specificare un ordinamento decrescente per inserire gli
elementi più piccoli all'inizio dell'elenco.
La query effettua una chiamata a un metodo separato per ottenere le dimensioni dei file in byte per gestire la
possibile eccezione che viene generata nel caso in cui un file sia stato eliminato in un altro thread nel periodo
trascorso dalla creazione dell'oggetto FileInfo nella chiamata a GetFiles . Anche se l'oggetto FileInfo è già stato
creato, è possibile che si verifichi un'eccezione perché un oggetto FileInfo tenterà di aggiornare la relativa
proprietà Length usando le dimensioni in byte più recenti quando viene eseguito per la prima volta l'accesso
alla proprietà. Inserendo questa operazione in un blocco try/catch all'esterno della query, si segue la regola di
evitare le operazioni nelle query che possono causare effetti collaterali. In generale, è necessario prestare
particolare attenzione durante la gestione delle eccezioni per assicurarsi che un'applicazione non venga lasciata
in uno stato sconosciuto.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come eseguire una query per i file duplicati in un
albero di directory (LINQ) (C#)
02/11/2020 • 4 minutes to read • Edit Online

Talvolta i file con lo stesso nome possono trovarsi in più di una cartella. Ad esempio, nella cartella di
installazione di Visual Studio diverse cartelle hanno un file readme.htm. In questo esempio viene illustrato come
eseguire una query per trovare tali nomi di file duplicati in una cartella radice specificata. Nel secondo esempio
viene illustrato come eseguire una query per i file la cui dimensione e i tempi di ultima scrittura corrispondono.

Esempio
class QueryDuplicateFileNames
{
static void Main(string[] args)
{
// Uncomment QueryDuplicates2 to run that query.
QueryDuplicates();
// QueryDuplicates2();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

static void QueryDuplicates()


{
// Change the root drive or folder if necessary
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// This method assumes that the application has discovery permissions


// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

// used in WriteLine to keep the lines shorter


int charsToSkip = startFolder.Length;

// var can be used for convenience with groups.


var queryDupNames =
from file in fileList
group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
where fileGroup.Count() > 1
select fileGroup;

// Pass the query to a method that will


// output one page at a time.
PageOutput<string, string>(queryDupNames);
}

// A Group key that can be passed to a separate method.


// Override Equals and GetHashCode to define equality for the key.
// Override ToString to provide a friendly name for Key.ToString()
class PortableKey
{
public string Name { get; set; }
public DateTime LastWriteTime { get; set; }
public long Length { get; set; }

public override bool Equals(object obj)


{
PortableKey other = (PortableKey)obj;
return other.LastWriteTime == this.LastWriteTime &&
other.Length == this.Length &&
other.Name == this.Name;
}

public override int GetHashCode()


{
string str = $"{this.LastWriteTime}{this.Length}{this.Name}";
return str.GetHashCode();
}
public override string ToString()
{
return $"{this.Name} {this.Length} {this.LastWriteTime}";
}
}
static void QueryDuplicates2()
{
// Change the root drive or folder if necessary.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

// Make the lines shorter for the console display


int charsToSkip = startFolder.Length;

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

// Note the use of a compound key. Files that match


// all three properties belong to the same group.
// A named type is used to enable the query to be
// passed to another method. Anonymous types can also be used
// for composite keys but cannot be passed across method boundaries
//
var queryDupFiles =
from file in fileList
group file.FullName.Substring(charsToSkip) by
new PortableKey { Name = file.Name, LastWriteTime = file.LastWriteTime, Length = file.Length
} into fileGroup
where fileGroup.Count() > 1
select fileGroup;

var list = queryDupFiles.ToList();

int i = queryDupFiles.Count();

PageOutput<PortableKey, string>(queryDupFiles);
}

// A generic method to page the output of the QueryDuplications methods


// Here the type of the group must be specified explicitly. "var" cannot
// be used in method signatures. This method does not display more than one
// group per page.
private static void PageOutput<K, V>(IEnumerable<System.Linq.IGrouping<K, V>> groupByExtList)
{
// Flag to break out of paging loop.
bool goAgain = true;

// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;

// Iterate through the outer collection of groups.


foreach (var filegroup in groupByExtList)
{
{
// Start a new extension at the top of a page.
int currentLine = 0;

// Output only as many lines of the current group as will fit in the window.
do
{
Console.Clear();
Console.WriteLine("Filename = {0}", filegroup.Key.ToString() == String.Empty ? "[none]" :
filegroup.Key.ToString());

// Get 'numLines' number of items starting at number 'currentLine'.


var resultPage = filegroup.Skip(currentLine).Take(numLines);

//Execute the resultPage query


foreach (var fileName in resultPage)
{
Console.WriteLine("\t{0}", fileName);
}

// Increment the line counter.


currentLine += numLines;

// Give the user a chance to escape.


Console.WriteLine("Press any key to continue or the 'End' key to break...");
ConsoleKey key = Console.ReadKey().Key;
if (key == ConsoleKey.End)
{
goAgain = false;
break;
}
} while (currentLine < filegroup.Count());

if (goAgain == false)
break;
}
}
}

La prima query usa una chiave semplice per determinare una corrispondenza. Individua i file che hanno lo
stesso nome ma contenuto diverso. La seconda query usa una chiave composta per individuare la
corrispondenza con tre proprietà dell'oggetto FileInfo. Questa query individuerà file che hanno lo stesso nome e
contenuto simile o identico.

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
LINQ to Objects (C#)
Directory di file e LINQ (C#)
Come eseguire una query sul contenuto dei file di
testo in una cartella (LINQ) (C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio illustra come eseguire una query su tutti i file in un albero di directory specificato, aprire ogni
file e controllarne il contenuto. Questo tipo di tecnica può essere usato per creare indici o indici inversi del
contenuto di un albero di directory. In questo esempio viene eseguita una semplice ricerca di una stringa.
Tuttavia, con un'espressione regolare è possibile eseguire tipi di criteri di ricerca più complessi. Per ulteriori
informazioni, vedere come combinare query LINQ con espressioni regolari (C#).

Esempio
class QueryContents
{
public static void Main()
{
// Modify this path as necessary.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

// Take a snapshot of the file system.


System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// This method assumes that the application has discovery permissions


// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);

string searchTerm = @"Visual Studio";

// Search the contents of each file.


// A regular expression created with the RegEx class
// could be used instead of the Contains method.
// queryMatchingFiles is an IEnumerable<string>.
var queryMatchingFiles =
from file in fileList
where file.Extension == ".htm"
let fileText = GetFileText(file.FullName)
where fileText.Contains(searchTerm)
select file.FullName;

// Execute the query.


Console.WriteLine("The term \"{0}\" was found in:", searchTerm);
foreach (string filename in queryMatchingFiles)
{
Console.WriteLine(filename);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}

// Read the contents of the file.


static string GetFileText(string name)
{
string fileContents = String.Empty;

// If the file has been deleted since we took


// the snapshot, ignore it and return the empty string.
if (System.IO.File.Exists(name))
{
fileContents = System.IO.File.ReadAllText(name);
}
return fileContents;
}
}

Compilazione del codice


Creare un progetto di applicazione console C# con direttive using per gli spazi dei nomi System.Linq e
System.IO.

Vedere anche
Directory di file e LINQ (C#)
LINQ to Objects (C#)
Come eseguire una query su un ArrayList con LINQ
(C#)
02/11/2020 • 2 minutes to read • Edit Online

Quando si usa LINQ per eseguire una query su raccolte IEnumerable non generiche, ad esempio ArrayList, è
necessario dichiarare in modo esplicito il tipo della variabile di intervallo in base al tipo specifico di oggetti nella
raccolta. Ad esempio, con un ArrayList di oggetti Student , la clausola from sarà simile alla seguente:

var query = from Student s in arrList


//...

Specificando il tipo della variabile di intervallo, si esegue il cast di ogni elemento di ArrayList in Student .
L'uso di una variabile di intervallo tipizzata in modo esplicito in un'espressione di query è equivalente alla
chiamata del metodo Cast. Cast genera un'eccezione se non è possibile eseguire il cast specificato. Cast e OfType
sono i due metodi dell'operatore query standard che operano sui tipi IEnumerable non generici. Per altre
informazioni, vedere relazioni tra i tipi nelle operazioni di query LINQ.

Esempio
L'esempio seguente mostra una query semplice su un oggetto ArrayList. Si noti che questo esempio usa gli
inizializzatori di oggetto quando il codice chiama il metodo Add, anche se non si tratta di un requisito.
using System;
using System.Collections;
using System.Linq;

namespace NonGenericLINQ
{
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int[] Scores { get; set; }
}

class Program
{
static void Main(string[] args)
{
ArrayList arrList = new ArrayList();
arrList.Add(
new Student
{
FirstName = "Svetlana", LastName = "Omelchenko", Scores = new int[] { 98, 92, 81, 60
}
});
arrList.Add(
new Student
{
FirstName = "Claire", LastName = "O’Donnell", Scores = new int[] { 75, 84, 91, 39 }
});
arrList.Add(
new Student
{
FirstName = "Sven", LastName = "Mortensen", Scores = new int[] { 88, 94, 65, 91 }
});
arrList.Add(
new Student
{
FirstName = "Cesar", LastName = "Garcia", Scores = new int[] { 97, 89, 85, 82 }
});

var query = from Student student in arrList


where student.Scores[0] > 95
select student;

foreach (Student s in query)


Console.WriteLine(s.LastName + ": " + s.Scores[0]);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
/* Output:
Omelchenko: 98
Garcia: 97
*/

Vedere anche
LINQ to Objects (C#)
Come aggiungere metodi personalizzati per le
query LINQ (C#)
02/11/2020 • 8 minutes to read • Edit Online

È possibile estendere il set di metodi usati per le query LINQ aggiungendo metodi di estensione all'
IEnumerable<T> interfaccia. Ad esempio, oltre alle operazioni standard medie o massime, è possibile creare un
metodo di aggregazione personalizzato per calcolare un singolo valore da una sequenza di valori. Si crea anche
un metodo che funziona come filtro personalizzato o una trasformazione di dati specifica per una sequenza di
valori e restituisce una nuova sequenza. Esempi di tali metodi sono Distinct, Skip e Reverse.
Quando si estende l'interfaccia IEnumerable<T>, è possibile applicare i metodi personalizzati a qualsiasi raccolta
enumerabile. Per altre informazioni, vedere Metodi di estensione.

Aggiunta di un metodo di aggregazione


Un metodo di aggregazione calcola un singolo valore da un set di valori. LINQ offre diversi metodi di
aggregazione, tra cui Average, Min e Max. È possibile creare il proprio metodo di aggregazione aggiungendo un
metodo di estensione all'interfaccia IEnumerable<T>.
L'esempio di codice seguente illustra come creare un metodo di estensione denominato Median per calcolare
un valore mediano per una sequenza di numeri di tipo double .

public static class LINQExtension


{
public static double Median(this IEnumerable<double>? source)
{
if (!(source?.Any() ?? false))
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}

var sortedList = (from number in source


orderby number
select number).ToList();

int itemIndex = sortedList.Count / 2;

if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}

Chiamare questo metodo di estensione per qualsiasi raccolta enumerabile nello stesso modo in cui si chiamano
altri metodi di aggregazione dall'interfaccia IEnumerable<T>.
L'esempio di codice seguente illustra come usare il metodo Median di una matrice di tipo double .
double[] numbers = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };

var query = numbers.Median();

Console.WriteLine("double: Median = " + query);


/*
This code produces the following output:

Double: Median = 4.85


*/

Overload di un metodo di aggregazione per accettare tipi diversi


È possibile eseguire l'overload del metodo di aggregazione in modo che accetti sequenze di tipi diversi.
L'approccio standard consiste nel creare un overload per ogni tipo. Un altro approccio consiste nel creare un
overload che accetti un tipo generico e lo converta in un tipo specifico tramite un delegato. È anche possibile
combinare entrambi gli approcci.
Per creare un overload per ogni tipo
È possibile creare un overload specifico per ogni tipo che si vuole supportare. Nell'esempio di codice seguente
viene illustrato l'overload del metodo Median per il tipo int .

//int overload
public static double Median(this IEnumerable<int> source) =>
(from num in source select (double)num).Median();

È ora possibile chiamare gli overload Median per entrambi i tipi integer e double , come illustrato nel codice
seguente:

double[] numbers1 = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };

var query1 = numbers1.Median();

Console.WriteLine("double: Median = " + query1);

int[] numbers2 = { 1, 2, 3, 4, 5 };

var query2 = numbers2.Median();

Console.WriteLine("int: Median = " + query2);


/*
This code produces the following output:

Double: Median = 4.85


Integer: Median = 3
*/

Per creare un overload generico


È anche possibile creare un overload che accetti una sequenza di oggetti generici. Questo overload accetta un
delegato come parametro e lo usa per convertire una sequenza di oggetti di un tipo generico in un tipo
specifico.
Il codice seguente mostra un overload del metodo Median che accetta il delegato Func<T,TResult> come
parametro. Questo delegato accetta un oggetto di tipo generico T e restituisce un oggetto di tipo double .
// Generic overload.
public static double Median<T>(this IEnumerable<T> numbers,
Func<T, double> selector) =>
(from num in numbers select selector(num)).Median();

È ora possibile chiamare il metodo Median per una sequenza di oggetti di qualsiasi tipo. Se il tipo non ha un
proprio overload del metodo, è necessario passare un parametro del delegato. In C# è possibile usare
un'espressione lambda a questo scopo. Solo in Visual Basic, se si usa la clausola Aggregate o Group By anziché
la chiamata al metodo, è possibile passare qualsiasi valore o espressione che si trovi nell'ambito della clausola.
L'esempio di codice seguente illustra come chiamare il metodo Median per una matrice di numeri interi e una
matrice di stringhe. Per le stringhe, viene calcolato il valore mediano della lunghezza delle stringhe nella matrice.
L'esempio mostra come passare il parametro del delegato Func<T,TResult> al metodo Median per ogni caso.

int[] numbers3 = { 1, 2, 3, 4, 5 };

/*
You can use the num=>num lambda expression as a parameter for the Median method
so that the compiler will implicitly convert its value to double.
If there is no implicit conversion, the compiler will display an error message.
*/
var query3 = numbers3.Median(num => num);

Console.WriteLine("int: Median = " + query3);

string[] numbers4 = { "one", "two", "three", "four", "five" };

// With the generic overload, you can also use numeric properties of objects.

var query4 = numbers4.Median(str => str.Length);

Console.WriteLine("String: Median = " + query4);

/*
This code produces the following output:

Integer: Median = 3
String: Median = 4
*/

Aggiunta di un metodo che restituisce una sequenza


È possibile estendere l'interfaccia IEnumerable<T> con un metodo di query personalizzato che restituisce una
sequenza di valori. In questo caso, il metodo deve restituire una raccolta di tipo IEnumerable<T>. Tali metodi
possono essere usati per applicare filtri o trasformazioni di dati in una sequenza di valori.
Nell'esempio seguente viene illustrato come creare un metodo di estensione denominato AlternateElements
che restituisce tutti gli altri elementi in una raccolta, a partire dal primo elemento.
// Extension method for the IEnumerable<T> interface.
// The method returns every other element of a sequence.
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T> source)
{
int i = 0;
foreach (var element in source)
{
if (i % 2 == 0)
{
yield return element;
}
i++;
}
}

È possibile chiamare questo metodo di estensione per qualsiasi raccolta enumerabile nello stesso modo in cui si
chiamano altri metodi dall'interfaccia IEnumerable<T>, come illustrato nel codice seguente:

string[] strings = { "a", "b", "c", "d", "e" };

var query5 = strings.AlternateElements();

foreach (var element in query5)


{
Console.WriteLine(element);
}
/*
This code produces the following output:

a
c
e
*/

Vedere anche
IEnumerable<T>
Metodi di estensione
LINQ to ADO.NET (pagina portale)
02/11/2020 • 3 minutes to read • Edit Online

LINQ to ADO.NET consente di eseguire una query su qualsiasi oggetto enumerabile in ADO.NET utilizzando il
modello di programmazione LINQ (Language-Integrated Query).

NOTE
La documentazione di LINQ to ADO.NET si trova nella sezione ADO.NET dell'SDK .NET Framework: LINQ e ADO.NET.

Sono disponibili tre tecnologie LINQ (Language-Integrated Query) separate: LINQ to DataSet, LINQ to SQL e
LINQ to Entities. LINQ to DataSet offre un supporto più completo e ottimizzato per l'esecuzione di query su
DataSet, LINQ to SQL consente di eseguire query direttamente sugli schemi di database di SQL Server e LINQ to
Entities consente di eseguire query su Entity Data Model.

LINQ to DataSet
DataSet è uno dei componenti maggiormente usati in ADO.NET ed è un elemento chiave del modello di
programmazione disconnessa su cui si basa ADO.NET. Nonostante l'importanza che lo contraddistingue,
tuttavia, DataSet ha solo funzionalità limitate di query.
LINQ to DataSet consente di compilare funzionalità di esecuzione di query più complesse nell'oggetto DataSet
usando la stessa funzionalità di query disponibile per molte altre origini dati.
Per altre informazioni, vedere LINQ to DataSet.

LINQ to SQL
LINQ to SQL fornisce un'infrastruttura di runtime per la gestione di dati relazionali come oggetti. In LINQ to SQL
viene eseguito il mapping del modello dati di un database relazionale a un modello a oggetti espresso nel
linguaggio di programmazione dello sviluppatore. Quando l'applicazione viene eseguita, LINQ to SQL converte
in SQL le query LINQ (Language Integrated Query) nel modello a oggetti e le invia al database per l'esecuzione.
Quando il database restituisce i risultati, LINQ to SQL li converte di nuovo in oggetti che è possibile modificare.
LINQ to SQL include il supporto di stored procedure e funzioni definite dall'utente nel database e
dell'ereditarietà nel modello a oggetti.
Per altre informazioni, vedere LINQ to SQL.

LINQ to Entities
Tramite Entity Data Model, i dati relazionali vengono esposti come oggetti nell'ambiente .NET. Questo rende il
livello di oggetto una destinazione ideale per il supporto LINQ, consentendo agli sviluppatori di formulare query
sul database dal linguaggio utilizzato per compilare la logica di business. Questa funzionalità è nota come LINQ
to Entities. Per altre informazioni, vedere LINQ to Entities.

Vedere anche
LINQ e ADO.NET
LINQ (Language-Integrated Query) (C#)
Abilitazione di un'origine dati per l'esecuzione di
query LINQ
28/01/2021 • 6 minutes to read • Edit Online

Sono disponibili diversi modi per estendere LINQ in modo da consentire la query su qualsiasi origine dati nel
modello LINQ. L'origine dati potrebbe, ad esempio, essere una struttura ad albero dei dati, un servizio Web, un
file system o un database. Il modello LINQ rende più semplice per i client eseguire una query su un'origine dati
per cui è abilitata l'esecuzione di query LINQ, perché la sintassi e il modello della query non cambiano. I modi in
cui LINQ può essere esteso a queste origini dati sono i seguenti:
Implementazione dell' IEnumerable<T> interfaccia in un tipo per abilitare LINQ to Objects l'esecuzione di
query su quel tipo.
Creazione di metodi di operatori di query standard, ad esempio Where e Select , che estendono un tipo,
per consentire l'esecuzione di query LINQ personalizzate su tale tipo.
Creando un provider per l'origine dati che implementi l'interfaccia IQueryable<T>. Un provider che
implementa questa interfaccia riceve le query LINQ sotto forma di alberi delle espressioni, che può essere
eseguito in modo personalizzato, ad esempio in modalità remota.
Creazione di un provider per l'origine dati che sfrutta una tecnologia LINQ esistente. Tale provider
consentirebbe non solo l'esecuzione di query, ma anche le operazioni di inserimento, aggiornamento ed
eliminazione e il mapping per i tipi definiti dall'utente.
In questo argomento vengono descritte queste opzioni.

Come attivare l'esecuzione di query LINQ sull'origine dati


Dati in memoria
Esistono due modi per abilitare l'esecuzione di query LINQ sui dati in memoria. Se i dati sono di un tipo che
implementa IEnumerable<T> , è possibile eseguire una query sui dati utilizzando LINQ to Objects. Se non ha
senso abilitare l'enumerazione del tipo implementando l' IEnumerable<T> interfaccia, è possibile definire i
metodi degli operatori di query standard LINQ in quel tipo o creare metodi dell'operatore di query standard
LINQ che estendono il tipo. Le implementazioni personalizzate degli operatori di query standard devono
utilizzare l'esecuzione posticipata per restituire i risultati.
Dati remoti
L'opzione migliore per abilitare l'esecuzione di query LINQ su un'origine dati remota consiste nell'implementare
l' IQueryable<T> interfaccia. È tuttavia diverso dall'estendere un provider come LINQ to SQL per un'origine dati.

Provider LINQ IQueryable


I provider LINQ che implementano IQueryable<T> possono variare notevolmente nella loro complessità. In
questa sezione vengono illustrati i diversi livelli di complessità.
Un provider IQueryable meno complesso potrebbe interfacciarsi con un singolo metodo di un servizio Web.
Questo tipo di provider è molto specifico poiché prevede informazioni specifiche nelle query che gestisce. Ha un
sistema del tipo chiuso, forse esponendo un solo tipo di risultato. La maggior parte dell'esecuzione della query
avviene localmente, utilizzando ad esempio le implementazioni Enumerable degli operatori di query standard.
Un provider meno complesso potrebbe esaminare solo un'espressione della chiamata al metodo nella struttura
ad albero dell'espressione che rappresenta la query facendo sì che la logica rimanente della query venga gestita
altrove.
Un provider IQueryable mediamente complesso potrebbe essere destinato a un'origine dati che ha un
linguaggio di query parzialmente espressivo. Se è destinato a un servizio Web, potrebbe interagire con più
metodi del servizio Web e selezionare il metodo da chiamare in base alla domanda posta dalla query. Un
provider mediamente complesso può avere un sistema di tipi più dettagliato rispetto a un provider semplice, ma
rimane sempre un sistema di tipi fisso. Ad esempio, il provider può esporre tipi che hanno relazioni uno-a-molti
che possono essere attraversate, ma non fornisce la tecnologia di mapping per i tipi definiti dall'utente.
Un IQueryable provider complesso, ad esempio il LINQ to SQL provider, potrebbe tradurre le query LINQ
complete in un linguaggio di query espressivo, ad esempio SQL. Un provider complesso è più generale di un
provider meno complesso, poiché può gestire un'ampia gamma di domande nella query. Ha anche un sistema
di tipi aperto e pertanto deve contenere un'infrastruttura completa per eseguire il mapping dei tipi definiti
dall'utente. Lo sviluppo di un provider complesso è molto impegnativo.

Vedere anche
IQueryable<T>
IEnumerable<T>
Enumerable
Cenni preliminari sugli operatori di query standard (C#)
LINQ to Objects (C#)
IDE di Visual Studio e supporto di strumenti per
LINQ (C#)
02/11/2020 • 2 minutes to read • Edit Online

L'ambiente di sviluppo integrato (IDE) di Visual Studio offre le seguenti funzionalità che supportano lo sviluppo
di applicazioni LINQ:

Object Relational Designer


Object Relational Designer è uno strumento di progettazione visiva che può essere usato nelle applicazioni LINQ
to SQL per generare le classi in C# che rappresentano i dati relazionali in un database sottostante. Per altre
informazioni, vedere Strumenti LINQ to SQL in Visual Studio.

Strumenti della riga di comando di SQLMetal


SQLMetal è uno strumento della riga di comando che può essere usato nei processi di compilazione per
generare classi dai database esistenti per l'uso nelle applicazioni LINQ to SQL. Per altre informazioni, vedere
SqlMetal.exe (strumento per la generazione del codice).

Editor di codice sensibile a LINQ


L'editor di codice C# supporta LINQ ampiamente con funzionalità di formattazione e IntelliSense.

Supporto del debugger di Visual Studio


Il debugger di Visual Studio supporta il debug delle espressioni di query. Per altre informazioni, vedere Debug
LINQ.

Vedere anche
LINQ (Language-Integrated Query) (C#)
Reflection (C#)
02/11/2020 • 2 minutes to read • Edit Online

La reflection fornisce oggetti (di tipo Type ) che descrivono assembly, moduli e tipi. È possibile usare la reflection
per creare in modo dinamico un'istanza di un tipo, associare il tipo a un oggetto esistente oppure ottenere il tipo
da un oggetto esistente e richiamarne i metodi o accedere ai relativi campi e proprietà. Se si usano attributi nel
codice, la reflection consente di accedervi. Per altre informazioni, vedere Attributi.
Ecco un semplice esempio di Reflection usando il GetType() metodo ereditato da tutti i tipi della classe di Object
base. per ottenere il tipo di una variabile:

NOTE
Assicurarsi using System; di aggiungere e using System.Reflection; all'inizio del file con estensione cs .

// Using GetType to obtain type information:


int i = 42;
Type type = i.GetType();
Console.WriteLine(type);

L'output è: System.Int32 .
L'esempio seguente usa la reflection per ottenere il nome completo dell'assembly caricato.

// Using Reflection to get information of an Assembly:


Assembly info = typeof(int).Assembly;
Console.WriteLine(info);

L'output è: System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e .

NOTE
Le parole chiave di C# protected e internal non hanno significato in IL e non sono usate nelle API di reflection. I
termini corrispondenti in IL sono Famiglia e Assembly. Per identificare un metodo internal tramite reflection, usare la
proprietà IsAssembly. Per identificare un metodo protected internal , usare IsFamilyOrAssembly.

Panoramica della reflection


La reflection è utile nelle situazioni seguenti:
Quando è necessario accedere agli attributi nei metadati del programma. Per altre informazioni, vedere
Recupero di informazioni memorizzate negli attributi.
Per esaminare e creare istanze di tipi in un assembly.
Per creare nuovi tipi in fase di esecuzione. Usare le classi in System.Reflection.Emit.
Per eseguire l'associazione tardiva, accedere ai metodi per i tipi creati in fase di esecuzione. Vedere
l'argomento relativo a caricamento e uso dinamico dei tipi.

Sezioni correlate
Per altre informazioni:
Reflection
Visualizzazione delle informazioni sul tipo
Reflection e tipi generici
System.Reflection.Emit
Recupero di informazioni memorizzate negli attributi

Vedere anche
Guida per programmatori C#
Assembly in .NET
Serializzazione (C#)
02/11/2020 • 9 minutes to read • Edit Online

Il termine serializzazione indica il processo di conversione di un oggetto in un flusso di byte, allo scopo di
archiviare tale oggetto o trasmetterlo alla memoria, a un database o a un file. Il fine principale della
serializzazione è salvare lo stato di un oggetto per consentirne la ricreazione in caso di necessità. Il processo
inverso è denominato deserializzazione.

Funzionamento della serializzazione


La figura seguente illustra il processo complessivo di serializzazione:

L'oggetto viene serializzato in un flusso che contiene i dati. Il flusso può inoltre contenere informazioni sul tipo
dell'oggetto, ad esempio la versione, le impostazioni cultura e il nome dell'assembly. Da tale flusso, l'oggetto
può essere archiviato in un database, un file o una memoria.
Usi della serializzazione
La serializzazione consente allo sviluppatore di salvare lo stato di un oggetto e di ricrearlo in base alle esigenze,
fornendo l'archiviazione di oggetti, nonché lo scambio di dati. Tramite la serializzazione, uno sviluppatore può
eseguire azioni come le seguenti:
Invio dell'oggetto a un'applicazione remota tramite un servizio Web
Passaggio di un oggetto da un dominio a un altro
Passaggio di un oggetto tramite un firewall come stringa JSON o XML
Gestione della sicurezza o delle informazioni specifiche dell'utente tra le applicazioni

Serializzazione JSON
Lo System.Text.Json spazio dei nomi contiene classi per la serializzazione e la deserializzazione di JavaScript
Object Notation (JSON). JSON è uno standard aperto comunemente usato per la condivisione di dati sul Web.
La serializzazione JSON serializza le proprietà pubbliche di un oggetto in una stringa, una matrice di byte o un
flusso conforme alla specifica JSON RFC 8259. Per controllare la modalità JsonSerializer di serializzazione o
deserializzazione di un'istanza della classe:
Usare un JsonSerializerOptions oggetto
Applicare gli attributi dallo System.Text.Json.Serialization spazio dei nomi alle classi o alle proprietà
Implementare convertitori personalizzati

Serializzazione in formato binario e XML


Lo System.Runtime.Serialization spazio dei nomi contiene classi per la serializzazione e la deserializzazione
binaria e XML.
La serializzazione binaria usa la codifica binaria per generare una serializzazione compatta per usi quali
l'archiviazione o i flussi di rete basati sui socket. Nella serializzazione binaria vengono serializzati tutti i membri,
anche quelli di sola lettura, e le prestazioni risultano migliorate.

WARNING
La serializzazione binaria può rappresentare un pericolo. Per ulteriori informazioni, vedere la Guida alla sicurezza di
BinaryFormatter.

La serializzazione XML serializza le proprietà e i campi pubblici di un oggetto, o i parametri e i valori restituiti dei
metodi, in un flusso XML conforme a uno specifico documento in linguaggio XSD (XML Schema Definition). La
serializzazione XML genera classi fortemente tipizzate con proprietà e campi pubblici convertiti in XML.
System.Xml.Serializationcontiene classi per la serializzazione e la deserializzazione di XML. È possibile applicare
attributi alle classi e ai membri delle classi per controllare il modo in cui XmlSerializer serializza o deserializza
un'istanza della classe.
Rendere un oggetto serializzabile
Per la serializzazione binaria o XML, è necessario:
Oggetto da serializzare.
Flusso che deve contenere l'oggetto serializzato
System.Runtime.Serialization.FormatterIstanza di
Applicare l' SerializableAttribute attributo a un tipo per indicare che le istanze del tipo possono essere
serializzate. Se si prova a serializzare ma il tipo non ha l'attributo SerializableAttribute viene generata
un'eccezione.
Per evitare che un campo venga serializzato, applicare l' NonSerializedAttribute attributo. Se un campo di un
tipo serializzabile contiene un puntatore, un handle o un'altra struttura di dati specifica di un particolare
ambiente e tale campo non può essere ricostituito in modo corretto in un ambiente diverso, è necessario
renderlo non serializzabile.
Se una classe serializzata contiene riferimenti a oggetti di altre classi contrassegnate con SerializableAttribute,
verranno serializzati anche tali oggetti.
Serializzazione di base e personalizzata
La serializzazione binaria e XML può essere eseguita in due modi: Basic e Custom.
La serializzazione di base USA .NET per serializzare automaticamente l'oggetto. L'unico requisito è che alla
classe sia SerializableAttribute applicato l'attributo. È possibile usare l'attributo NonSerializedAttribute per
evitare la serializzazione di campi specifici.
Quando si usa la serializzazione di base, il controllo delle versioni degli oggetti può creare problemi. Usare la
serializzazione personalizzata quando gli aspetti di controllo delle versioni sono importanti. La serializzazione di
base è il modo più semplice per eseguire la serializzazione, ma non assicura il controllo completo del processo.
Con la serializzazione personalizzata è possibile specificare esattamente quali oggetti verranno serializzati e il
modo in cui verrà eseguita la serializzazione. La classe deve essere contrassegnata con SerializableAttribute e
implementare l'interfaccia ISerializable. Se si desidera che l'oggetto venga deserializzato anche in modo
personalizzato, utilizzare un costruttore personalizzato.

Serializzazione della finestra di progettazione


La serializzazione della finestra di progettazione è una forma speciale di serializzazione che interessa il tipo di
persistenza dell'oggetto associato agli strumenti di sviluppo. La serializzazione della finestra di progettazione è il
processo di conversione di un oggetto grafico in un file di origine che può essere usato in seguito per
recuperare l'oggetto grafico stesso. Un file di origine può contenere codice, markup o anche informazioni su
tabelle SQL.

Argomenti correlati ed esempi


Panoramica diSystem.Text.Js Viene illustrato come ottenere la System.Text.Json libreria.
Come serializzare e deserializzare JSON in .NET. Viene illustrato come leggere e scrivere dati oggetto da e verso
JSON usando la JsonSerializer classe.
Procedura dettagliata: Persistenza di un oggetto in Visual Studio (C#)
Dimostra in che modo è possibile usare la serializzazione per rendere persistenti i dati di un oggetto tra le
istanze, consentendo di archiviare i valori e di recuperarli alla successiva creazione di un'istanza dell'oggetto.
Come leggere i dati di un oggetto da un file XML (C#)
Spiega come leggere i dati della classe precedentemente scritti in un file XML usando la classe XmlSerializer.
Come scrivere i dati di un oggetto in un file XML (C#)
Spiega come scrivere l'oggetto da una classe in un file XML usando la classe XmlSerializer.
Come scrivere i dati di un oggetto in un file XML
(C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio scrive l'oggetto da una classe in un file XML usando la classe XmlSerializer.

Esempio
public class XMLWrite
{

static void Main(string[] args)


{
WriteXML();
}

public class Book


{
public String title;
}

public static void WriteXML()


{
Book overview = new Book();
overview.title = "Serialization Overview";
System.Xml.Serialization.XmlSerializer writer =
new System.Xml.Serialization.XmlSerializer(typeof(Book));

var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +


"//SerializationOverview.xml";
System.IO.FileStream file = System.IO.File.Create(path);

writer.Serialize(file, overview);
file.Close();
}
}

Compilazione del codice


La classe da serializzare deve avere un costruttore pubblico senza parametri.

Programmazione efficiente
Le seguenti condizioni possono generare un'eccezione:
La classe da serializzare non ha un costruttore pubblico senza parametri.
Il file esiste ed è di sola lettura (IOException).
Percorso del file troppo lungo (PathTooLongException).
Il disco è pieno (IOException).

Sicurezza .NET
Questo esempio crea un nuovo file, se il file non esiste. Se un'applicazione deve creare un file, deve avere
accesso Create alla cartella. Se il file esiste già, per l'applicazione è sufficiente l'accesso Write , un privilegio di
livello inferiore. Se possibile, è più sicuro creare il file durante la distribuzione e concedere l'accesso Read a un
unico file, anziché l'accesso Create a una cartella.

Vedere anche
StreamWriter
Come leggere i dati di un oggetto da un file XML (C#)
Serializzazione (C#)
Come leggere i dati di un oggetto da un file XML
(C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio legge i dati oggetto scritti in precedenza in un file XML usando la classe XmlSerializer.

Esempio
public class Book
{
public String title;
}

public void ReadXML()


{
// First write something so that there is something to read ...
var b = new Book { title = "Serialization Overview" };
var writer = new System.Xml.Serialization.XmlSerializer(typeof(Book));
var wfile = new System.IO.StreamWriter(@"c:\temp\SerializationOverview.xml");
writer.Serialize(wfile, b);
wfile.Close();

// Now we can read the serialized book ...


System.Xml.Serialization.XmlSerializer reader =
new System.Xml.Serialization.XmlSerializer(typeof(Book));
System.IO.StreamReader file = new System.IO.StreamReader(
@"c:\temp\SerializationOverview.xml");
Book overview = (Book)reader.Deserialize(file);
file.Close();

Console.WriteLine(overview.title);

Compilazione del codice


Sostituire il nome di file "c:\temp\SerializationOverview.xml" con il nome del file contenente i dati serializzati.
Per ulteriori informazioni sulla serializzazione dei dati, vedere la pagina relativa alla modalità di scrittura dei dati
di un oggetto in un file XML (C#).
La classe deve avere un costruttore public senza parametri.
Solo le proprietà e i campi pubblici vengono deserializzati.

Programmazione efficiente
Le seguenti condizioni possono generare un'eccezione:
La classe da serializzare non ha un costruttore pubblico senza parametri.
I dati nel file non rappresentano i dati della classe da deserializzare.
Il file non esiste (IOException).
Sicurezza .NET
Verificare sempre gli input e non deserializzare mai i dati proveniente da un'origine non attendibile. L'oggetto
ricreato viene eseguito in un computer locale con le autorizzazioni del codice che ha eseguito la
deserializzazione. Prima di usare i dati nell'applicazione verificare tutti gli input.

Vedere anche
StreamWriter
Come scrivere i dati di un oggetto in un file XML (C#)
Serializzazione (C#)
Guida per programmatori C#
Procedura dettagliata: Persistenza di un oggetto
tramite C#
02/11/2020 • 8 minutes to read • Edit Online

È possibile usare la serializzazione per rendere persistenti i dati di un oggetto tra le istanze, consentendo di
archiviare i valori e di recuperarli alla successiva creazione di un'istanza dell'oggetto.
In questa procedura verrà creato un oggetto Loan di base i cui dati verranno resi persistenti in un file. I dati
verranno quindi recuperati dal file quando si ricrea l'oggetto.

IMPORTANT
Questo esempio crea un nuovo file se il file non esiste già. Se un'applicazione deve creare un file, tale applicazione deve
avere l'autorizzazione Create per la cartella. Le autorizzazioni vengono impostate usando gli elenchi di controllo di
accesso. Se il file esiste già, per l'applicazione è necessaria solo l'autorizzazione Write , di livello inferiore. Se possibile, è
più sicuro creare il file durante la distribuzione e concedere solo autorizzazioni Read per un singolo file, anziché
autorizzazioni Create per una cartella. È anche più sicuro scrivere i dati nelle cartelle utente anziché nella cartella radice o
nella cartella Programmi.

IMPORTANT
In questo esempio i dati vengono archiviati in un file in formato binario. Non usare questi formati per i dati riservati, ad
esempio password o informazioni sulla carta di credito.

Prerequisiti
Per consentire la compilazione e l'esecuzione, installare .NET Core SDK.
Installare l'editor del codice preferito, se non è già disponibile.

TIP
È necessario installare un editor del codice? Provare Visual Studio.

L'esempio richiede C# 7.3. Vedere Selezionare la versione del linguaggio C#


È possibile esaminare il codice di esempio online nel repository GitHub degli esempi .NET.

Creazione dell'oggetto Loan


Il primo passaggio consiste nel creare una classe Loan e un'applicazione console che usa la classe:
1. Creare una nuova applicazione. Digitare dotnet new console -o serialization per creare una nuova
applicazione console in una sottodirectory denominata serialization .
2. Aprire l'applicazione nell'editor e aggiungere una nuova classe denominata Loan.cs .
3. Aggiungere il codice seguente alla classe Loan :
public class Loan : INotifyPropertyChanged
{
public double LoanAmount { get; set; }
public double InterestRatePercent { get; set; }

[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }

public int Term { get; set; }

private string customer;


public string Customer
{
get { return customer; }
set
{
customer = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Customer)));
}
}

[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public Loan(double loanAmount,


double interestRate,
int term,
string customer)
{
this.LoanAmount = loanAmount;
this.InterestRatePercent = interestRate;
this.Term = term;
this.customer = customer;
}
}

Sarà anche necessario creare un'applicazione che usi la classe Loan .

Serializzare l'oggetto Loan


1. Aprire Program.cs . Aggiungere il codice seguente:

Loan TestLoan = new Loan(10000.0, 7.5, 36, "Neil Black");

Aggiungere un gestore per l'evento PropertyChanged e alcune righe per modificare l'oggetto Loan e
visualizzare le modifiche. Le aggiunte sono presenti nel codice seguente:

TestLoan.PropertyChanged += (_, __) => Console.WriteLine($"New customer value: {TestLoan.Customer}");

TestLoan.Customer = "Henry Clay";


Console.WriteLine(TestLoan.InterestRatePercent);
TestLoan.InterestRatePercent = 7.1;
Console.WriteLine(TestLoan.InterestRatePercent);

A questo punto è possibile eseguire il codice e visualizzare l'output corrente:


New customer value: Henry Clay
7.5
7.1

Se si esegue ripetutamente questa applicazione, vengono scritti sempre gli stessi valori. Un nuovo oggetto Loan
viene creato ogni volta che si esegue il programma. Nel mondo reale i tassi di interesse variano periodicamente,
ma non necessariamente ogni volta che l'applicazione viene eseguita. Il codice di serializzazione consente di
mantenere il tasso di interesse più recente tra istanze diverse dell'applicazione. Nel passaggio successivo verrà
eseguita questa operazione, aggiungendo la serializzazione alla classe Loan.

Usare la serializzazione per la persistenza dell'oggetto


Per rendere persistenti i valori per la classe Loan, per prima cosa contrassegnare la classe con l'attributo
Serializable . Aggiungere il codice riportato di seguito prima della dichiarazione della classe Loan:

[Serializable()]

SerializableAttribute indica al compilatore che tutti gli elementi nella classe possono essere resi persistenti in un
file. Poiché l'evento PropertyChanged non rappresenta una parte dell'oggetto grafico che deve essere archiviata,
non deve essere serializzato. La serializzazione interesserebbe infatti tutti gli oggetti associati all'evento. È
possibile aggiungere NonSerializedAttribute alla dichiarazione di campo per il gestore dell'evento
PropertyChanged .

[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

A partire da C# 7.3, è possibile collegare attributi al campo sottostante di una proprietà implementata
automaticamente usando il valore di destinazione field . Il codice seguente aggiunge la proprietà
TimeLastLoaded e la contrassegna come non serializzabile:

[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }

Il passaggio successivo consiste nell'aggiungere il codice di serializzazione all'applicazione LoanApp. Per


serializzare la classe e scriverla in un file, si usano gli spazi dei nomi System.IO e
System.Runtime.Serialization.Formatters.Binary. Per evitare di digitare i nomi completi, è possibile aggiungere
riferimenti agli spazi dei nomi necessari, come illustrato nel codice seguente:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

Il passaggio successivo consiste nell'aggiungere codice per deserializzare l'oggetto dal file quando viene creato
l'oggetto. Aggiungere una costante alla classe per il nome del file di dati serializzati, come illustrato nel codice
seguente:

const string FileName = @"../../../SavedLoan.bin";

Aggiungere quindi il codice seguente dopo la riga che crea l'oggetto TestLoan :
if (File.Exists(FileName))
{
Console.WriteLine("Reading saved file");
Stream openFileStream = File.OpenRead(FileName);
BinaryFormatter deserializer = new BinaryFormatter();
TestLoan = (Loan)deserializer.Deserialize(openFileStream);
TestLoan.TimeLastLoaded = DateTime.Now;
openFileStream.Close();
}

È prima necessario verificare che il file esista. Se esiste, creare una classe Stream per leggere il file binario e una
classe BinaryFormatter per convertire il file. È anche necessario eseguire la conversione dal tipo di flusso al tipo
di oggetto Loan.
È quindi necessario aggiungere codice per serializzare la classe in un file. Aggiungere il codice seguente dopo il
codice esistente nel metodo Main :

Stream SaveFileStream = File.Create(FileName);


BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(SaveFileStream, TestLoan);
SaveFileStream.Close();

A questo punto, è nuovamente possibile compilare ed eseguire l'applicazione. Si noti che, la prima volta che
viene eseguita, il tasso di interesse inizia a 7,5 e quindi passa a 7,1. Chiudere l'applicazione ed eseguirla
nuovamente. A questo punto, l'applicazione stampa un messaggio che comunica l'avvenuta lettura del file
salvato e che il tasso di interesse corrisponde a 7,1 anche prima del codice che lo modifica.

Vedere anche
Serializzazione (C#)
Guida per programmatori C#
Istruzioni, espressioni e operatori (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Il codice C# di un'applicazione è costituito da istruzioni che contengono parole chiave, espressioni e operatori.
Questa sezione contiene informazioni relative a tali elementi fondamentali di un programma C#.
Per altre informazioni, vedere:
Istruzioni
Operatori ed espressioni
Membri con corpo di espressione
Funzioni anonime
Confronti di uguaglianza

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Cast e conversioni di tipi (C#)
Istruzioni (Guida per programmatori C#)
02/11/2020 • 10 minutes to read • Edit Online

Le azioni accettate da un programma vengono espresse in istruzioni. Le azioni comuni includono la


dichiarazione di variabili, l'assegnazione di valori, le chiamate ai metodi, il ciclo delle raccolte e la creazione di
rami tra blocchi di codice, a seconda di una data condizione. L'ordine in cui vengono le istruzioni eseguite in un
programma viene chiamato "flusso di controllo" o "flusso di esecuzione". Il flusso di controllo può variare ogni
volta che viene eseguito un programma, a seconda della reazione del programma all'input ricevuto in fase di
esecuzione.
Un'istruzione può essere costituito da una singola riga di codice che termina con un punto e virgola o da una
serie di istruzioni a riga singola in un blocco. Un blocco di istruzioni è racchiuso tra parentesi {} e può contenere
blocchi annidati. Il codice seguente mostra due esempi di istruzioni a riga singola e un blocco di istruzioni a più
righe:

static void Main()


{
// Declaration statement.
int counter;

// Assignment statement.
counter = 1;

// Error! This is an expression, not an expression statement.


// counter + 1;

// Declaration statements with initializers are functionally


// equivalent to declaration statement followed by assignment statement:
int[] radii = { 15, 32, 108, 74, 9 }; // Declare and initialize an array.
const double pi = 3.14159; // Declare and initialize constant.

// foreach statement block that contains multiple statements.


foreach (int radius in radii)
{
// Declaration statement with initializer.
double circumference = pi * (2 * radius);

// Expression statement (method invocation). A single-line


// statement can span multiple text lines because line breaks
// are treated as white space, which is ignored by the compiler.
System.Console.WriteLine("Radius of circle #{0} is {1}. Circumference = {2:N2}",
counter, radius, circumference);

// Expression statement (postfix increment).


counter++;
} // End of foreach statement block
} // End of Main method body.
} // End of SimpleStatements class.
/*
Output:
Radius of circle #1 = 15. Circumference = 94.25
Radius of circle #2 = 32. Circumference = 201.06
Radius of circle #3 = 108. Circumference = 678.58
Radius of circle #4 = 74. Circumference = 464.96
Radius of circle #5 = 9. Circumference = 56.55
*/
Tipi di istruzioni
Nella tabella seguente sono elencati i vari tipi di istruzioni in C# e le parole chiave associate, con link ad
argomenti contenenti maggiori informazioni:

C AT EGO RY PA RO L E C H IAVE C #/ N OT E

Istruzioni di dichiarazione Un'istruzione di dichiarazione introduce una nuova variabile


o costante. Una dichiarazione di variabile può
facoltativamente assegnare un valore alla variabile. In una
dichiarazione di costante, l'assegnazione è necessaria.

Istruzioni di espressione Le istruzioni di espressione che calcolano un valore devono


archiviare il valore in una variabile.

Istruzioni di selezione Le istruzioni di selezione consentono di creare un ramo tra le


diverse sezioni di codice, in base a una o più condizioni
specificate. Per altre informazioni, vedere gli argomenti
seguenti:
if
else
switch
caso

Istruzioni di iterazione Le istruzioni di iterazione consentono di eseguire un ciclo tra


le raccolte come matrici o eseguono ripetutamente lo stesso
set di istruzioni finché non viene soddisfatta una condizione
specificata. Per altre informazioni, vedere gli argomenti
seguenti:
do
for
foreach
in
mentre

Istruzioni di salto Le istruzioni di salto trasferiscono il controllo a un'altra


sezione di codice. Per altre informazioni, vedere gli argomenti
seguenti:
break
continuare
predefinita
goto
ritorno
yield

Istruzioni di gestione delle eccezioni Le istruzioni di gestione delle eccezioni consentono di


recuperare condizioni eccezionali che si verificano in fase di
esecuzione. Per altre informazioni, vedere gli argomenti
seguenti:
throw
try-catch
try-finally
try-catch-finally
C AT EGO RY PA RO L E C H IAVE C #/ N OT E

Checked e unchecked Le istruzioni Checked e Unchecked consentono di specificare


se le operazioni numeriche possono provocare un overflow
quando il risultato viene archiviato in una variabile troppo
piccola per contenere il valore risultante. Per altre
informazioni, vedere checked e unchecked.

Istruzione await Se si contrassegna un metodo con il modificatore async, è


possibile usare l'operatore await nel metodo. Quando il
controllo raggiunge un'espressione await nel metodo
asincrono, il controllo torna al chiamante e l'avanzamento
nel metodo viene sospeso fino al completamento dell'attività
attesa. Una volta completata l'attività, l'esecuzione del
metodo può riprendere.

Per un esempio semplice, vedere la sezione "Metodi


asincroni" in Metodi. Per altre informazioni, vedere
Programmazione asincrona con async e await.

Istruzione yield return Un iteratore esegue un'iterazione personalizzata su una


raccolta, ad esempio un elenco o una matrice. Un iteratore
usa l'istruzione yield return per restituire un elemento alla
volta. Quando viene raggiunta un'istruzione yield return ,
la posizione corrente nel codice viene memorizzata.
L'esecuzione viene riavviata a partire da quella posizione la
volta successiva che viene chiamato l'iteratore.

Per ulteriori informazioni, vedere iteratori.

Istruzione fixed L'istruzione fixed impedisce che il Garbage Collector esegua


la rilocazione di una variabile mobile. Per altre informazioni,
vedere fixed.

Istruzione lock L'istruzione lock consente di limitare l'accesso ai blocchi di


codice a un solo thread per volta. Per altre informazioni,
vedere lock.

Istruzioni con etichetta È possibile assegnare un'etichetta a un'istruzione e quindi


usare la parola chiave goto per passare all'istruzione con
etichetta. Vedere l'esempio nell'argomento seguente.

Istruzione vuota L'istruzione vuota è costituita da un singolo punto e virgola.


Non esegue alcuna operazione e può essere usata in
posizioni in cui è richiesta un'istruzione ma non deve essere
eseguita alcuna azione.

Istruzioni di dichiarazione
Il codice seguente illustra esempi di dichiarazioni di variabili con e senza un'assegnazione iniziale e una
dichiarazione di costante con le inizializzazioni necessarie.

// Variable declaration statements.


double area;
double radius = 2;

// Constant declaration statement.


const double pi = 3.14159;
Istruzioni di espressione
Il codice seguente illustra esempi di istruzioni di espressione, tra cui assegnazione, creazione di oggetti con
assegnazione, e la chiamata al metodo.

// Expression statement (assignment).


area = 3.14 * (radius * radius);

// Error. Not statement because no assignment:


//circ * 2;

// Expression statement (method invocation).


System.Console.WriteLine();

// Expression statement (new object creation).


System.Collections.Generic.List<string> strings =
new System.Collections.Generic.List<string>();

Istruzione vuota
Gli esempi seguenti illustrano due usi di un'istruzione vuota:

void ProcessMessages()
{
while (ProcessMessage())
; // Statement needed here.
}

void F()
{
//...
if (done) goto exit;
//...
exit:
; // Statement needed here.
}

Istruzioni incorporate
Alcune istruzioni, fra cui do, while, for e foreach, dispongono sempre di un'istruzione incorporata che le segue.
Questa istruzione incorporata può essere una singola istruzione o più istruzioni racchiuse tra parentesi {} in un
blocco di istruzioni. Anche le istruzioni incorporate a riga singola possono essere racchiuse tra parentesi {}, come
illustrato nell'esempio seguente:

// Recommended style. Embedded statement in block.


foreach (string s in System.IO.Directory.GetDirectories(
System.Environment.CurrentDirectory))
{
System.Console.WriteLine(s);
}

// Not recommended.
foreach (string s in System.IO.Directory.GetDirectories(
System.Environment.CurrentDirectory))
System.Console.WriteLine(s);

Un'istruzione incorporata non racchiusa tra parentesi {} non può essere un'istruzione di dichiarazione o
un'istruzione con etichetta. Questa operazione è illustrata nell'esempio seguente:
if(pointB == true)
//Error CS1023:
int radius = 5;

Inserire l'istruzione incorporata in un blocco per correggere l'errore:

if (b == true)
{
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
}

Blocchi di istruzioni annidati


I blocchi di istruzioni possono essere annidati, come illustrato nel codice seguente:

foreach (string s in System.IO.Directory.GetDirectories(


System.Environment.CurrentDirectory))
{
if (s.StartsWith("CSharp"))
{
if (s.EndsWith("TempFolder"))
{
return s;
}
}
}
return "Not found.";

Istruzioni non raggiungibili


Se il compilatore determina che il flusso di controllo non può raggiungere mai una particolare istruzione in
nessuna circostanza, genererà l'avviso CS0162, come illustrato nell'esempio seguente:

// An over-simplified example of unreachable code.


const int val = 5;
if (val < 4)
{
System.Console.WriteLine("I'll never write anything."); //CS0162
}

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzioni della specifica del linguaggio C#.

Vedere anche
Guida per programmatori C#
Parole chiave dell'istruzione
Operatori ed espressioni C#
Membri con corpo di espressione (Guida per
programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

Le definizioni dei corpi di espressione consentono di eseguire l'implementazione di un membro in un modulo


molto conciso e leggibile. È possibile usare una definizione di corpo di espressione ogni volta che la logica per
un membro supportato, ad esempio un metodo o proprietà, è costituita da un'unica espressione. Una
definizione di corpo di espressione presenta la seguente sintassi generale:

member => expression;

dove expression è un'espressione valida.


Il supporto per le definizioni dei corpi di espressione è stato introdotto per i metodi e le proprietà di sola lettura
in C# 6 ed è stato ampliato in C# 7.0. Le definizioni dei corpi di espressione possono essere usate con i membri
dei tipi elencati nella tabella seguente:

M EM B RO SUP P O RTATO A PA RT IRE DA . . .

Metodo C# 6

Proprietà di sola lettura C# 6

Proprietà C# 7.0

Costruttore C# 7.0

Finalizer C# 7.0

Indicizzatore C# 7.0

Metodi
Un metodo con corpo di espressione è costituito da una singola espressione che restituisce un valore il cui tipo
corrisponde al tipo restituito del metodo oppure, per i metodi che restituiscono void , che esegue una
determinata operazione. Ad esempio, i tipi che eseguono l'override del metodo ToString di solito includono una
singola espressione che restituisce la rappresentazione di stringa dell'oggetto corrente.
L'esempio seguente definisce una classe Person che esegue l'override del metodo ToString con una definizione
di corpo di espressione. Definisce inoltre un metodo DisplayName che visualizza un nome nella console. Si noti
che la parola chiave return non viene usata nella definizione del corpo di espressione ToString .
using System;

public class Person


{
public Person(string firstName, string lastName)
{
fname = firstName;
lname = lastName;
}

private string fname;


private string lname;

public override string ToString() => $"{fname} {lname}".Trim();


public void DisplayName() => Console.WriteLine(ToString());
}

class Example
{
static void Main()
{
Person p = new Person("Mandy", "Dejesus");
Console.WriteLine(p);
p.DisplayName();
}
}

Per altre informazioni, vedere Metodi (Guida per programmatori C#).

Proprietà di sola lettura


A partire da C# 6, è possibile usare una definizione del corpo dell'espressione per implementare una proprietà
di sola lettura. A questo scopo, usare la sintassi seguente:

PropertyType PropertyName => expression;

L'esempio seguente definisce una classe Location la cui proprietà Name di sola lettura viene implementata
come definizione del corpo dell'espressione che restituisce il valore del campo locationName privato:

public class Location


{
private string locationName;

public Location(string name)


{
locationName = name;
}

public string Name => locationName;


}

Per altre informazioni sulle proprietà, vedere Proprietà (Guida per programmatori C#).

Proprietà
A partire da C# 7.0, è possibile usare definizioni del corpo dell'espressione per implementare la proprietà get e
le funzioni di accesso set . Nell'esempio riportato di seguito viene illustrato come procedere:
public class Location
{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}

Per altre informazioni sulle proprietà, vedere Proprietà (Guida per programmatori C#).

Costruttori
Una definizione di corpo di espressione per un costruttore in genere è costituita da una singola espressione di
assegnazione o da una chiamata al metodo che gestisce gli argomenti del costruttore o inizializza lo stato
dell'istanza.
L'esempio seguente definisce una classe Location il cui costruttore ha un solo parametro di stringa
denominato name. La definizione del corpo dell'espressione assegna l'argomento alla proprietà Name .

public class Location


{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}

Per altre informazioni, vedere Costruttori (Guida per programmatori C#).

Finalizzatori
Una definizione di corpo di espressione per un finalizzatore in genere contiene istruzioni di pulitura, ad esempio
le istruzioni che rilasciano risorse non gestite.
L'esempio seguente definisce un finalizzatore che usa una definizione di corpo di espressione per indicare che è
stato chiamato il finalizzatore.

using System;

public class Destroyer


{
public override string ToString() => GetType().Name;

~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");


}

Per altre informazioni, vedere Finalizzatori (Guida per programmatori C#).


Indicizzatori
Analogamente alle proprietà, get set le funzioni di accesso e gli indicizzatori sono costituiti da definizioni del
corpo dell'espressione se la get funzione di accesso è costituita da una singola espressione che restituisce un
valore o la funzione di accesso set esegue un'assegnazione semplice.
Nell'esempio seguente viene definita una classe denominata Sports che include una matrice String interna
contenente i nomi di alcuni sport. Sia l'indicizzatore get che le set funzioni di accesso sono implementate
come definizioni del corpo dell'espressione.

using System;
using System.Collections.Generic;

public class Sports


{
private string[] types = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" };

public string this[int i]


{
get => types[i];
set => types[i] = value;
}
}

Per altre informazioni, vedere Indicizzatori (Guida per programmatori C#).


Funzioni anonime (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Una funzione anonima è un'istruzione o un'espressione "inline" che può essere usata in tutti i casi in cui è
previsto un tipo delegato. Consente di inizializzare un delegato denominato o passarlo al posto di un tipo
delegato denominato come parametro del metodo.
Per creare una funzione anonima, è possibile usare un'espressione lambda o un metodo anonimo. È
consigliabile usare le espressioni lambda perché forniscono un modo più conciso ed espressivo per scrivere
codice inline. A differenza dei metodi anonimi, è possibile convertire alcuni tipi di espressioni lambda nei tipi di
albero delle espressioni.

Evoluzione dei delegati in C#


In C# 1.0 è stata creata un'istanza di un delegato inizializzandola in modo esplicito con un metodo che è stato
definito altrove nel codice. C# 2.0 ha introdotto il concetto di metodi anonimi come modo per scrivere blocchi di
istruzioni inline senza nome che possono essere eseguiti in una chiamata a delegati. C# 3.0 ha introdotto le
espressioni lambda, che sono concettualmente analoghe ai metodi anonimi, ma più espressive e concise. Queste
due funzionalità sono noti collettivamente come funzioni anonime. In generale, le applicazioni destinate a .NET
Framework 3,5 o versioni successive devono usare espressioni lambda.
L'esempio seguente illustra l'evoluzione della creazione di delegati da C# 1.0 a C# 3.0:
class Test
{
delegate void TestDelegate(string s);
static void M(string s)
{
Console.WriteLine(s);
}

static void Main(string[] args)


{
// Original delegate syntax required
// initialization with a named method.
TestDelegate testDelA = new TestDelegate(M);

// C# 2.0: A delegate can be initialized with


// inline code, called an "anonymous method." This
// method takes a string as an input parameter.
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

// C# 3.0. A delegate can be initialized with


// a lambda expression. The lambda also takes a string
// as an input parameter (x). The type of x is inferred by the compiler.
TestDelegate testDelC = (x) => { Console.WriteLine(x); };

// Invoke the delegates.


testDelA("Hello. My name is M and I write lines.");
testDelB("That's nothing. I'm anonymous and ");
testDelC("I'm a famous author.");

// Keep console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Hello. My name is M and I write lines.
That's nothing. I'm anonymous and
I'm a famous author.
Press any key to exit.
*/

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni di funzioni anonime della specifica del linguaggio C#.

Vedere anche
Istruzioni, espressioni e operatori
Espressioni lambda
Delegati
Alberi delle espressioni (C#)
Come usare le espressioni lambda in una query
(Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Le espressioni lambda non vengono usate direttamente nella sintassi delle query, ma nelle chiamate al metodo
e le espressioni di query possono contenere chiamate al metodo. Di fatto, alcune operazioni di query possono
essere espresse solo nella sintassi del metodo. Per altre informazioni sulle differenze tra la sintassi delle query e
la sintassi dei metodi, vedere Sintassi di query e sintassi di metodi in LINQ.

Esempio
Nell'esempio seguente viene illustrato come usare un'espressione lambda in una query basata sul metodo
tramite l'operatore query standard Enumerable.Where. Si noti che il metodo Where in questo esempio dispone
di un parametro di input del tipo delegato Func<T,TResult> e che il delegato accetta come input un numero
intero e restituisce un valore booleano. L'espressione lambda può essere convertita al delegato. Se si trattasse di
una query LINQ to SQL che usa il metodo Queryable.Where, il tipo di parametro sarebbe un
Expression<Func<int,bool>> ma l'espressione lambda avrebbe esattamente lo stesso aspetto. Per altre
informazioni sul tipo di espressione, vedere System.Linq.Expressions.Expression.

class SimpleLambda
{
static void Main()
{

// Data source.
int[] scores = { 90, 71, 82, 93, 75, 82 };

// The call to Count forces iteration of the source


int highScoreCount = scores.Where(n => n > 80).Count();

Console.WriteLine("{0} scores are greater than 80", highScoreCount);

// Outputs: 4 scores are greater than 80


}
}

Esempio
Nell'esempio seguente viene illustrato come usare un'espressione lambda in una chiamata al metodo di
un'espressione di query. L'espressione lambda è necessaria perché l'operatore query standard Sum non può
essere richiamato tramite la sintassi della query.
La query raggruppa innanzitutto gli studenti in base alla classe, come definito nel valore enum GradeLevel .
Quindi per ogni gruppo aggiunge il punteggio totale per ogni studente. Ciò richiede due operazioni Sum . La
Sum interna calcola il punteggio totale per ogni studente, mentre la Sum esterna mantiene un totale combinato
e in esecuzione per tutti gli studenti del gruppo.
private static void TotalsByGradeLevel()
{
// This query retrieves the total scores for First Year students, Second Years, and so on.
// The outer Sum method uses a lambda in order to specify which numbers to add together.
var categories =
from student in students
group student by student.Year into studentGroup
select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };

// Execute the query.


foreach (var cat in categories)
{
Console.WriteLine("Key = {0} Sum = {1}", cat.GradeLevel, cat.TotalScore);
}
}
/*
Outputs:
Key = SecondYear Sum = 1014
Key = ThirdYear Sum = 964
Key = FirstYear Sum = 1058
Key = FourthYear Sum = 974
*/

Compilazione del codice


Per eseguire questo codice, copiare e incollare il metodo nell'oggetto StudentClass fornito in eseguire una
query su una raccolta di oggetti e chiamarlo dal Main metodo.

Vedere anche
Espressioni lambda
Alberi delle espressioni (C#)
Confronti di uguaglianza (Guida per programmatori
C#)
02/11/2020 • 6 minutes to read • Edit Online

A volte è necessario confrontare due valori per verificarne l'uguaglianza. In alcuni casi si verifica l'uguaglianza
dei valori, nota anche come equivalenza, ovvero se i valori contenuti nelle due variabili sono uguali. In altri casi,
è necessario determinare se due variabili fanno riferimento allo stesso oggetto sottostante in memoria. Questo
tipo di uguaglianza è detto uguaglianza dei riferimenti o identità. In questo argomento vengono descritti questi
due tipi di uguaglianza e indicati i collegamenti ad altri argomenti per le informazioni dettagliate.

Uguaglianza di riferimenti
Uguaglianza di riferimenti significa che due riferimenti ad oggetti puntano allo stesso oggetto sottostante. Ciò
può verificarsi con un'assegnazione semplice, come illustrato nell'esempio seguente.

using System;
class Test
{
public int Num { get; set; }
public string Str { get; set; }

static void Main()


{
Test a = new Test() { Num = 1, Str = "Hi" };
Test b = new Test() { Num = 1, Str = "Hi" };

bool areEqual = System.Object.ReferenceEquals(a, b);


// False:
System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);

// Assign b to a.
b = a;

// Repeat calls with different results.


areEqual = System.Object.ReferenceEquals(a, b);
// True:
System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

In questo codice vengono creati due oggetti, ma dopo l'istruzione di assegnazione, entrambi i riferimenti fanno
riferimento allo stesso oggetto. Di conseguenza, esiste un'uguaglianza dei riferimenti. Usare il metodo
ReferenceEquals per determinare se due riferimenti fanno riferimento allo stesso oggetto.
Il concetto di uguaglianza dei riferimenti si applica solo ai tipi di riferimento. Per gli oggetti di tipo di valore non
può esistere l'uguaglianza dei riferimenti poiché quando un'istanza di un tipo di valore viene assegnata a una
variabile, viene creata una copia del valore. Di conseguenza, non è possibile avere due struct unboxed che fanno
riferimento alla stessa posizione in memoria. Se inoltre si usa ReferenceEquals per confrontare due tipi valore, il
risultato sarà sempre false , anche se i valori contenuti negli oggetti sono tutti identici. Ciò avviene perché ogni
variabile è di tipo boxed in un'istanza separata dell'oggetto. Per ulteriori informazioni, vedere come verificare
l'uguaglianza dei riferimenti (identità).

Uguaglianza di valori
Uguaglianza di valori significa che due oggetti contengono lo stesso valore o gli stessi valori. Per i tipi di valore
primitivi, ad esempio int o bool, i test per verificare l'uguaglianza di valori sono semplici. È possibile usare l' ==
operatore, come illustrato nell'esempio seguente.

int a = GetOriginalValue();
int b = GetCurrentValue();

// Test for value equality.


if (b == a)
{
// The two integers are equal.
}

Per la maggior parte degli altri tipi, il test dell'uguaglianza di valori è più complesso poiché è necessario sapere
in che modo viene definito dal tipo. Per le classi e gli struct con più campi o proprietà, l'uguaglianza di valori
viene spesso definita in modo che tutti i campi o tutte le proprietà abbiano lo stesso valore. Ad esempio, due
oggetti Point possono essere definiti equivalenti se pointA.X è uguale a pointB.X e pointA.Y è uguale a pointB.Y.
Tuttavia, nessun requisito prevede che l'equivalenza sia basata su tutti i campi in un tipo. Può essere basata su
un subset. Quando si confrontano i tipi di cui non si è proprietari, è necessario assicurarsi di sapere esattamente
in che modo viene definita l'equivalenza per quel tipo. Per ulteriori informazioni su come definire l'uguaglianza
di valori nelle classi e negli struct personalizzati, vedere come definire l'uguaglianza di valori per un tipo.
Uguaglianza di valori per i valori a virgola mobile
I confronti di uguaglianza dei valori a virgola mobile (double e float) sono problematici a causa dell'imprecisione
dell'aritmetica a virgola mobile nei computer binari. Per altre informazioni, vedere le note nell'argomento
System.Double.

Argomenti correlati
T ITO LO DESC RIZ IO N E

Come verificare l'uguaglianza dei riferimenti (identità) Descrive come determinare se per due variabili esiste
l'uguaglianza dei riferimenti.

Come definire l'uguaglianza di valori per un tipo Descrive come specificare una definizione personalizzata di
uguaglianza dei valori per un tipo.

Guida per programmatori C# Vengono forniti collegamenti a informazioni dettagliate sulle


funzionalità e le funzionalità del linguaggio C# più importanti
disponibili in C# tramite .NET.

Tipi Informazioni sul sistema di tipi C# e collegamenti a


informazioni aggiuntive.

Vedere anche
Guida per programmatori C#
Come definire l'uguaglianza di valori per un tipo
(Guida per programmatori C#)
02/11/2020 • 12 minutes to read • Edit Online

Quando si definisce una classe o uno struct, si decide se è opportuno creare una definizione personalizzata di
uguaglianza di valore, o equivalenza, per il tipo. In genere, l'uguaglianza di valori viene implementata quando si
prevede che oggetti del tipo vengano aggiunti a una raccolta, o quando lo scopo principale di tali oggetti
consiste nell'archiviare un set di campi o di proprietà. È possibile basare la definizione di uguaglianza di valori su
un confronto di tutti i campi e di tutte le proprietà nel tipo, oppure su un sottoinsieme.
In entrambi i casi, e in entrambe le classi e gli struct, l'implementazione deve seguire le cinque garanzie di
equivalenza (per le regole seguenti, si supponga che x y e z non siano null):
1. x.Equals(x) restituisce true . Questa viene denominata proprietà riflessiva.
2. y.Equals(x) restituisce lo stesso valore di x.Equals(y) . Questa viene denominata proprietà simmetrica.
3. Se (x.Equals(y) && y.Equals(z)) restituisce true , x.Equals(z) restituirà true . Questa viene
denominata proprietà transitiva.
4. Le successive chiamate di x.Equals(y) restituiscono lo stesso valore purché gli oggetti a cui x e y fanno
riferimento non vengano modificati.
5. Qualsiasi valore non null è diverso da null. Tuttavia, CLR verifica la presenza di valori null in tutte le
chiamate al metodo e genera un'eccezione NullReferenceException se il this riferimento è null.
Pertanto, x.Equals(y) genera un'eccezione quando x è null. Che interrompe le regole 1 o 2, a seconda
dell'argomento a Equals .
Gli struct definiti hanno già un'implementazione predefinita di uguaglianza di valore che eredita dall'override
System.ValueType del metodo Object.Equals(Object). Questa implementazione usa il processo di reflection per
esaminare tutti i campi e tutte le proprietà nel tipo. Sebbene questa implementazione produca risultati corretti, è
relativamente lenta rispetto a un'implementazione personalizzata che viene scritta specificamente per il tipo.
I dettagli di implementazione per l'uguaglianza di valori sono diversi per le classi e gli struct. Tuttavia, sia le classi
che gli struct richiedono gli stessi passaggi di base per l'implementazione dell'uguaglianza:
1. Eseguire l'override del metodo di tipo virtual Object.Equals(Object). Nella maggior parte dei casi
l'implementazione di bool Equals( object obj ) deve solo chiamare il metodo Equals specifico per il
tipo, che è l'implementazione dell'interfaccia System.IEquatable<T>. (Vedere il passaggio 2.)
2. Implementare l'interfaccia System.IEquatable<T> definendo un metodo Equals specifico per il tipo. È in
questo passaggio che viene eseguito il confronto di equivalenza effettivo. Ad esempio, è possibile definire
l'uguaglianza confrontando solo uno o due campi nel tipo. Non generare eccezioni da Equals . Solo per
le classi: questo metodo deve esaminare solo i campi che vengono dichiarati nella classe. Deve chiamare
base.Equals per esaminare i campi presenti nella classe di base. Non eseguire questa operazione se il
tipo eredita direttamente da Object, perché l'implementazione Object di Object.Equals(Object) esegue un
controllo di uguaglianza dei riferimenti.
3. Facoltativo ma consigliato: eseguire l'overload degli == operatori e ! = .
4. Eseguire l'override di Object.GetHashCode in modo che due oggetti con uguaglianza di valori producano
lo stesso codice hash.
5. Facoltativo: per supportare le definizioni di "maggiore di" o "minore di", implementare l'
IComparable<T> interfaccia per il tipo e anche eseguire l'overload degli <= >= operatori e.

NOTE
A partire da C# 9,0, è possibile usare i record per ottenere la semantica di uguaglianza dei valori senza codice standard
non necessario.

Esempio di classe
Nell'esempio seguente viene illustrato come implementare l'uguaglianza di valori in una classe (tipo
riferimento).

namespace ValueEquality
{
using System;
class TwoDPoint : IEquatable<TwoDPoint>
{
// Readonly auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }

// Set the properties in the constructor.


public TwoDPoint(int x, int y)
{
if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))
{
throw new System.ArgumentException("Point must be in range 1 - 2000");
}
this.X = x;
this.Y = y;
}

public override bool Equals(object obj)


{
return this.Equals(obj as TwoDPoint);
}

public bool Equals(TwoDPoint p)


{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}

// Optimization for a common success case.


if (Object.ReferenceEquals(this, p))
{
return true;
}

// If run-time types are not exactly the same, return false.


if (this.GetType() != p.GetType())
{
return false;
}

// Return true if the fields match.


// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return X * 0x00010000 + Y;
}

public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)


{
// Check for null on left side.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(rhs, null))
{
// null == null = true.
return true;
}

// Only the left side is null.


return false;
}
// Equals handles case of null on right side.
return lhs.Equals(rhs);
}

public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)


{
return !(lhs == rhs);
}
}

// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.


class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
public int Z { get; private set; }

public ThreeDPoint(int x, int y, int z)


: base(x, y)
{
if ((z < 1) || (z > 2000))
{
throw new System.ArgumentException("Point must be in range 1 - 2000");
}
this.Z = z;
}

public override bool Equals(object obj)


{
return this.Equals(obj as ThreeDPoint);
}

public bool Equals(ThreeDPoint p)


{
// If parameter is null, return false.
if (Object.ReferenceEquals(p, null))
{
return false;
}

// Optimization for a common success case.


if (Object.ReferenceEquals(this, p))
{
return true;
}

// Check properties that this class declares.


if (Z == p.Z)
{
// Let base class check its own fields
// and do the run-time type comparison.
return base.Equals((TwoDPoint)p);
return base.Equals((TwoDPoint)p);
}
else
{
return false;
}
}

public override int GetHashCode()


{
return (X * 0x100000) + (Y * 0x1000) + Z;
}

public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)


{
// Check for null.
if (Object.ReferenceEquals(lhs, null))
{
if (Object.ReferenceEquals(rhs, null))
{
// null == null = true.
return true;
}

// Only the left side is null.


return false;
}
// Equals handles the case of null on right side.
return lhs.Equals(rhs);
}

public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)


{
return !(lhs == rhs);
}
}

class Program
{
static void Main(string[] args)
{
ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointC = null;
int i = 5;

Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));


Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
Console.WriteLine("Compare to some other type = {0}", pointA.Equals(i));

TwoDPoint pointD = null;


TwoDPoint pointE = null;

Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD == pointE);

pointE = new TwoDPoint(3, 4);


Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);

System.Collections.ArrayList list = new System.Collections.ArrayList();


list.Add(new ThreeDPoint(3, 4, 5));
Console.WriteLine("pointE.Equals(list[0]): {0}", pointE.Equals(list[0]));

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}

/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
null comparison = False
Compare to some other type = False
Two null TwoDPoints are equal: True
(pointE == pointA) = False
(pointA == pointE) = False
(pointA != pointE) = True
pointE.Equals(list[0]): False
*/
}

Nelle classi (tipi riferimento) l'implementazione predefinita di entrambi i metodi Object.Equals(Object) esegue
un confronto di uguaglianza dei riferimenti, non un controllo di uguaglianza dei valori. Quando un responsabile
dell'implementazione esegue l'override del metodo virtuale, lo scopo è assegnare a tale metodo una semantica
di uguaglianza di valore.
Gli operatori == e != possono essere usati con le classi anche se la classe non ne esegue l'overload. Tuttavia, il
comportamento predefinito è eseguire una verifica dell'uguaglianza dei riferimenti. Se in una classe si esegue
l'overload del metodo Equals , è consigliabile eseguire l'overload degli operatori == e != , ma non è
obbligatorio.

Esempio di struct
Nell'esempio seguente viene illustrato come implementare l'uguaglianza di valori in uno struct (tipo valore):

using System;
struct TwoDPoint : IEquatable<TwoDPoint>
{
// Read/write auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }

public TwoDPoint(int x, int y)


: this()
{
X = x;
Y = x;
}

public override bool Equals(object obj)


{
if (obj is TwoDPoint)
{
return this.Equals((TwoDPoint)obj);
}
return false;
}

public bool Equals(TwoDPoint p)


{
return (X == p.X) && (Y == p.Y);
}

public override int GetHashCode()


{
return X ^ Y;
}

public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)


{
return lhs.Equals(rhs);
return lhs.Equals(rhs);
}

public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)


{
return !(lhs.Equals(rhs));
}
}

class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;

// Compare using virtual Equals, static Equals, and == and != operators.


// True:
Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
// True:
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
// True:
Console.WriteLine("object.Equals(pointA, pointB) = {0}", object.Equals(pointA, pointB));
// False:
Console.WriteLine("pointA.Equals(null) = {0}", pointA.Equals(null));
// False:
Console.WriteLine("(pointA == null) = {0}", pointA == null);
// True:
Console.WriteLine("(pointA != null) = {0}", pointA != null);
// False:
Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
// CS0019:
// Console.WriteLine("pointA == i = {0}", pointA == i);

// Compare unboxed to boxed.


System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add(new TwoDPoint(3, 4));
// True:
Console.WriteLine("pointA.Equals(list[0]): {0}", pointA.Equals(list[0]));

// Compare nullable to nullable and to non-nullable.


TwoDPoint? pointC = null;
TwoDPoint? pointD = null;
// False:
Console.WriteLine("pointA == (pointC = null) = {0}", pointA == pointC);
// True:
Console.WriteLine("pointC == pointD = {0}", pointC == pointD);

TwoDPoint temp = new TwoDPoint(3, 4);


pointC = temp;
// True:
Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA == pointC);

pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
Object.Equals(pointA, pointB) = True
pointA.Equals(null) = False
(pointA == null) = False
(pointA == null) = False
(pointA != null) = True
pointA.Equals(i) = False
pointE.Equals(list[0]): True
pointA == (pointC = null) = False
pointC == pointD = True
pointA == (pointC = 3,4) = True
pointD == (pointC = 3,4) = True
*/
}

Per gli struct, l'implementazione predefinita di Object.Equals(Object), che è la versione sottoposta a override in
System.ValueType, esegue un controllo di uguaglianza dei valori usando il processo di reflection per confrontare
i valori di ogni campo nel tipo. Quando un responsabile dell'implementazione esegue l'override del metodo
virtuale Equals in uno struct, lo scopo è specificare un mezzo più efficiente per la verifica dell'uguaglianza di
valori e facoltativamente basare il confronto su alcuni subset del campo o delle proprietà dello struct.
Gli == operatori e ! = non possono operare su uno struct, a meno che lo struct non li sovraccarichi in modo
esplicito.

Vedere anche
Confronti di uguaglianza
Guida per programmatori C#
Come verificare l'uguaglianza dei riferimenti
(identità) (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

Non è necessario implementare alcuna logica personalizzata per supportare i confronti di uguaglianza dei
riferimenti nei tipi. Questa funzionalità viene fornita per tutti i tipi dal metodo statico Object.ReferenceEquals.
L'esempio seguente mostra come determinare se due variabili presentano uguaglianza dei riferimenti, ovvero
se fanno riferimento allo stesso oggetto in memoria.
Nell'esempio viene inoltre illustrato perché Object.ReferenceEquals restituisce sempre false per i tipi di valore
e perché non è consigliabile utilizzare ReferenceEquals per determinare l'uguaglianza di stringhe.

Esempio
using System;
using System.Text;

namespace TestReferenceEquality
{
struct TestStruct
{
public int Num { get; private set; }
public string Name { get; private set; }

public TestStruct(int i, string s) : this()


{
Num = i;
Name = s;
}
}

class TestClass
{
public int Num { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main()
{
// Demonstrate reference equality with reference types.
#region ReferenceTypes

// Create two reference type instances that have identical values.


TestClass tcA = new TestClass() { Num = 1, Name = "New TestClass" };
TestClass tcB = new TestClass() { Num = 1, Name = "New TestClass" };

Console.WriteLine("ReferenceEquals(tcA, tcB) = {0}",


Object.ReferenceEquals(tcA, tcB)); // false

// After assignment, tcB and tcA refer to the same object.


// They now have reference equality.
tcB = tcA;
Console.WriteLine("After assignment: ReferenceEquals(tcA, tcB) = {0}",
Object.ReferenceEquals(tcA, tcB)); // true

// Changes made to tcA are reflected in tcB. Therefore, objects


// Changes made to tcA are reflected in tcB. Therefore, objects
// that have reference equality also have value equality.
tcA.Num = 42;
tcA.Name = "TestClass 42";
Console.WriteLine("tcB.Name = {0} tcB.Num: {1}", tcB.Name, tcB.Num);
#endregion

// Demonstrate that two value type instances never have reference equality.
#region ValueTypes

TestStruct tsC = new TestStruct( 1, "TestStruct 1");

// Value types are copied on assignment. tsD and tsC have


// the same values but are not the same object.
TestStruct tsD = tsC;
Console.WriteLine("After assignment: ReferenceEquals(tsC, tsD) = {0}",
Object.ReferenceEquals(tsC, tsD)); // false
#endregion

#region stringRefEquality
// Constant strings within the same assembly are always interned by the runtime.
// This means they are stored in the same location in memory. Therefore,
// the two strings have reference equality although no assignment takes place.
string strA = "Hello world!";
string strB = "Hello world!";
Console.WriteLine("ReferenceEquals(strA, strB) = {0}",
Object.ReferenceEquals(strA, strB)); // true

// After a new string is assigned to strA, strA and strB


// are no longer interned and no longer have reference equality.
strA = "Goodbye world!";
Console.WriteLine("strA = \"{0}\" strB = \"{1}\"", strA, strB);

Console.WriteLine("After strA changes, ReferenceEquals(strA, strB) = {0}",


Object.ReferenceEquals(strA, strB)); // false

// A string that is created at runtime cannot be interned.


StringBuilder sb = new StringBuilder("Hello world!");
string stringC = sb.ToString();
// False:
Console.WriteLine("ReferenceEquals(stringC, strB) = {0}",
Object.ReferenceEquals(stringC, strB));

// The string class overloads the == operator to perform an equality comparison.


Console.WriteLine("stringC == strB = {0}", stringC == strB); // true

#endregion

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}

/* Output:
ReferenceEquals(tcA, tcB) = False
After assignment: ReferenceEquals(tcA, tcB) = True
tcB.Name = TestClass 42 tcB.Num: 42
After assignment: ReferenceEquals(tsC, tsD) = False
ReferenceEquals(strA, strB) = True
strA = "Goodbye world!" strB = "Hello world!"
After strA changes, ReferenceEquals(strA, strB) = False
ReferenceEquals(stringC, strB) = False
stringC == strB = True
*/

L'implementazione di Equals nella classe di base universale System.Object esegue anche un controllo
dell'uguaglianza dei riferimenti, ma è meglio non servirsene perché, se una classe esegue l'override del metodo,
i risultati potrebbero non essere quelli previsti. Lo stesso vale per gli operatori == e != . Quando si opera sui
tipi di riferimento, il comportamento predefinito di == e != prevede l'esecuzione di un controllo
dell'uguaglianza dei riferimenti. Le classi derivate possono tuttavia eseguire l'overload dell'operatore per
eseguire un controllo dell'uguaglianza dei valori. Per ridurre al minimo la possibilità di errori, è preferibile usare
sempre ReferenceEquals quando è necessario determinare se due oggetti presentano uguaglianza dei
riferimenti.
Stringhe costanti all'interno dello stesso assembly vengono sempre archiviate dal runtime. Ciò significa che
viene mantenuta una sola istanza di ogni stringa letterale univoca. Il runtime tuttavia non garantisce che le
stringhe create in fase di esecuzione vengano archiviate né garantisce che due stringhe costanti uguali in
assembly diversi vengano centralizzate.

Vedi anche
Confronti di uguaglianza
Tipi (Guida per programmatori C#)
28/01/2021 • 24 minutes to read • Edit Online

Tipi, variabili e valori


C# è un linguaggio fortemente tipizzato. Ogni variabile e costante ha un tipo, così come ogni espressione che
restituisce un valore. Ogni dichiarazione di metodo specifica un nome, un numero di parametri, un tipo e un tipo
(valore, riferimento o output) per ogni parametro di input e per il valore restituito. La libreria di classi .NET
definisce un set di tipi numerici incorporati e tipi più complessi che rappresentano un'ampia gamma di costrutti
logici, ad esempio la file system, le connessioni di rete, le raccolte e le matrici di oggetti e le date. Un tipico
programma C# usa i tipi della libreria di classi e dei tipi definiti dall'utente che modellano i concetti specifici del
dominio del problema del programma.
Le informazioni archiviate in un tipo possono includere gli elementi seguenti:
Lo spazio di archiviazione richiesto da una variabile del tipo.
I valori minimi e massimi che può rappresentare.
I membri (metodi, campi, eventi e così via) in esso contenuti.
Il tipo di base da cui eredita.
Interfaccia o interfacce implementate.
Il percorso in cui viene allocata la memoria per le variabili in fase di esecuzione.
I tipi di operazioni consentite.
Il compilatore usa le informazioni sul tipo per assicurarsi che tutte le operazioni eseguite nel codice siano
indipendenti dai tipi. Se ad esempio si dichiara una variabile di tipo int, il compilatore consente di usare la
variabile anche in operazioni di addizione e sottrazione. Se si prova a eseguire le stesse operazioni su una
variabile di tipo bool, il compilatore genera un errore, come illustrato nell'esempio seguente:

int a = 5;
int b = a + 2; //OK

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;

NOTE
Gli sviluppatori C e C++ devono tenere presente che, in C#, bool non è convertibile in int.

Il compilatore incorpora le informazioni sul tipo nel file eseguibile come metadati. Il Common Language
Runtime (CLR) usa i metadati in fase di esecuzione per garantire una maggiore indipendenza dai tipi quando
alloca e recupera la memoria.
Specifica dei tipi nelle dichiarazioni di variabile
Quando si dichiara una variabile o una costante in un programma, è necessario specificarne il tipo oppure usare
la parola chiave var per consentire al compilatore di dedurre il tipo. L'esempio seguente illustra alcune
dichiarazioni di variabili che usano sia tipi numerici incorporati sia tipi complessi definiti dall'utente:
// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):


char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;

I tipi di parametri del metodo e i valori restituiti vengono specificati nella dichiarazione di metodo. La firma
seguente illustra un metodo che richiede un int come argomento di input e restituisce una stringa:

public string GetName(int ID)


{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Dopo aver dichiarato una variabile, non è possibile ridichiararla con un nuovo tipo e non è possibile assegnare
un valore non compatibile con il tipo dichiarato. Ad esempio, non è possibile dichiarare un valore int e quindi
assegnargli un valore booleano true . Tuttavia, i valori possono essere convertiti in altri tipi, ad esempio
quando sono assegnati a nuove variabili o passati come argomenti di metodo. Una conversione del tipo che non
provoca la perdita di dati viene eseguita automaticamente dal compilatore. mentre una conversione che può
causare la perdita di dati richiede un cast nel codice sorgente.
Per altre informazioni, vedere Cast e conversioni di tipi.

Tipi incorporati
C# fornisce un set standard di tipi predefiniti per rappresentare numeri interi, valori a virgola mobile,
espressioni booleane, caratteri di testo, valori decimali e altri tipi di dati. Sono anche disponibili tipi string e
object incorporati, Questi tipi sono disponibili per l'uso in qualsiasi programma C#. Per l'elenco completo dei
tipi incorporati, vedere tipi incorporati.

Tipi personalizzati
Usare i costrutti struct, class, interface e enum per creare tipi personalizzati. La libreria di classi .NET stessa è una
raccolta di tipi personalizzati offerti da Microsoft che è possibile usare nelle proprie applicazioni. Per
impostazione predefinita, i tipi più comunemente usati nella libreria di classi sono disponibili in qualsiasi
programma C#, Altri diventano disponibili solo quando si aggiunge in modo esplicito un riferimento di progetto
all'assembly in cui sono definiti. Nel momento in cui il compilatore ha un riferimento all'assembly, è possibile
dichiarare variabili (e costanti) dei tipi dichiarati nell'assembly in codice sorgente. Per altre informazioni, vedere
Libreria di classi .NET.

Common Type System


È importante comprendere due punti fondamentali sul sistema di tipi in .NET:
Supporta il principio di ereditarietà. I tipi possono derivare da altri tipi, denominati tipi di base. Il tipo derivato
eredita (con alcune limitazioni) metodi, proprietà e altri membri del tipo di base, che a sua volta può derivare
da un altro tipo. In questo caso, il tipo derivato eredita i membri di entrambi i tipi di base nella gerarchia di
ereditarietà. Tutti i tipi, inclusi i tipi numerici predefiniti, ad esempio System.Int32 (parola chiave C#: int),
derivano in definitiva da un unico tipo di base, ovvero System.Object (parola chiave C#: object). Questa
gerarchia di tipi unificata prende il nome di Common Type System (CTS). Per altre informazioni
sull'ereditarietà in C#, vedere Ereditarietà.
Nel CTS ogni tipo è definito come tipo valore o tipo riferimento. Questi tipi includono tutti i tipi personalizzati
nella libreria di classi .NET e anche i tipi definiti dall'utente. I tipi definiti tramite la parola chiave struct sono
tipi valore e tutti i tipi numerici incorporati sono tipi structs . I tipi definiti tramite la parola chiave class sono
tipi riferimento. I tipi di riferimento e i tipi di valore hanno regole diverse e un comportamento diverso in
fase di esecuzione.
La figura seguente illustra la relazione tra tipi valore e tipi riferimento nel CTS.

NOTE
È possibile osservare come i tipi usati con maggiore frequenza siano tutti organizzati nello spazio dei nomi System.
L'inserimento di un tipo in uno spazio dei nomi, tuttavia, è indipendente dalla categoria a cui appartiene il tipo.

Tipi valore
I tipi valore derivano da System.ValueType, che deriva da System.Object. I tipi che derivano da System.ValueType
hanno un comportamento speciale in CLR. Le variabili dei tipi valore contengono direttamente i rispettivi valori,
ovvero la memoria viene allocata inline nel contesto in cui è dichiarata la variabile. Non esiste un'allocazione
heap o un overhead di Garbage Collection separato per le variabili di tipo valore.
Esistono due categorie di tipi valore: struct e enum.
I tipi numerici incorporati sono struct e hanno campi e metodi a cui è possibile accedere:

// constant field on type byte.


byte b = byte.MaxValue;

Ma si dichiarano e si assegnano valori come se fossero tipi non aggregati semplici:


byte num = 0xA;
int i = 5;
char c = 'Z';

I tipi di valore sono sealed, il che significa che non è possibile derivare un tipo da un tipo di valore, ad esempio
System.Int32 . Non è possibile definire uno struct da ereditare da qualsiasi classe o struct definita dall'utente
perché uno struct può ereditare solo da System.ValueType . Un tipo struct può tuttavia implementare una o più
interfacce. È possibile eseguire il cast di un tipo struct a qualsiasi tipo di interfaccia implementato; questo cast fa
in modo che un'operazione di conversione boxing incapsulare lo struct all'interno di un oggetto tipo riferimento
sull'heap gestito. Le operazioni di conversione boxing si verificano quando si passa un tipo valore a un metodo
che accetta System.Object o qualsiasi tipo di interfaccia come parametro di input. Per altre informazioni, vedere
Boxing e unboxing.
Usare la parola chiave struct per creare tipi valore personalizzati. In genere, un tipo struct viene usato come
contenitore per un piccolo set di variabili correlate, come illustrato nell'esempio seguente:

public struct Coords


{
public int x, y;

public Coords(int p1, int p2)


{
x = p1;
y = p2;
}
}

Per ulteriori informazioni sugli struct, vedere tipi di struttura. Per ulteriori informazioni sui tipi di valore, vedere
tipi di valore.
L'altra categoria di tipi valore è enum. Un tipo enum definisce un set di costanti integrali denominate.
L'enumerazione System.IO.FileMode nella libreria di classi .NET, ad esempio, contiene un set di valori interi
costanti e denominati che specificano come deve essere aperto un file. Viene definito come illustrato
nell'esempio seguente:

public enum FileMode


{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}

Il valore della costante System.IO.FileMode.Create è 2. Tuttavia, il nome è molto più significativo per gli utenti
che leggono il codice sorgente e per questo motivo è preferibile usare le enumerazioni anziché i numeri letterali
costanti. Per altre informazioni, vedere System.IO.FileMode.
Tutte le enumerazioni ereditano da System.Enum, che eredita da System.ValueType. Tutte le regole valide per i
tipi struct sono valide anche per le enumerazioni. Per altre informazioni sulle enumerazioni, vedere tipi di
enumerazione.
Tipi riferimento
Un tipo definito come classe, delegato, matrice o interfaccia è un tipo riferimento. In fase di esecuzione, quando
si dichiara una variabile di un tipo riferimento, la variabile contiene il valore null fino a quando non si crea in
modo esplicito un oggetto usando l'operatore new o fino a quando non le viene assegnato un oggetto creato
altrove tramite new , come illustrato nell'esempio seguente:

MyClass mc = new MyClass();


MyClass mc2 = mc;

Un'interfaccia deve essere inizializzata insieme a un oggetto classe che la implementa. Se MyClass implementa
IMyInterface , si crea un'istanza di IMyInterface , come illustrato nell'esempio seguente:

IMyInterface iface = new MyClass();

Quando viene creato l'oggetto, la memoria viene allocata nell'heap gestito e la variabile mantiene solo un
riferimento al percorso dell'oggetto. I tipi nell'heap gestito richiedono un overhead quando vengono allocati e
quando vengono recuperati dalla funzionalità di gestione automatica della memoria di CLR, nota come Garbage
Collection. Tuttavia, anche Garbage Collection è altamente ottimizzata e, nella maggior parte degli scenari, non
crea un problema di prestazioni. Per altre informazioni sulla Garbage Collection, vedere Gestione automatica
della memoria.
Tutte le matrici sono tipi riferimento, anche se i relativi elementi sono tipi valore. Le matrici derivano in modo
implicito dalla classe System.Array, ma vengono dichiarate e usate con la sintassi semplificata fornita da C#,
come illustrato nell'esempio seguente:

// Declare and initialize an array of integers.


int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.


int len = nums.Length;

I tipi riferimento supportano completamente l'ereditarietà. Quando si crea una classe, è possibile ereditare da
qualsiasi altra interfaccia o classe che non è definita come sealede altre classi possono ereditare dalla classe ed
eseguire l'override dei metodi virtuali. Per altre informazioni su come creare classi personalizzate, vedere Classi
e struct. Per altre informazioni sull'ereditarietà e sui metodi virtuali, vedere Ereditarietà.

Tipi di valori letterali


In C# i valori letterali ricevono un tipo dal compilatore. È possibile specificare come deve essere tipizzato un
valore letterale numerico aggiungendo una lettera alla fine del numero. Per specificare, ad esempio, che il valore
4.56 deve essere considerato come un tipo float, aggiungere una "f" o una "F" dopo il numero: 4.56f . Se non
viene aggiunta alcuna lettera, il compilatore dedurrà un tipo per il valore letterale. Per ulteriori informazioni sui
tipi che è possibile specificare con i suffissi di lettera, vedere tipi numerici integrali e tipi numerici a virgola
mobile.
Poiché i valori letterali sono tipizzati e tutti i tipi derivano in ultima analisi da System.Object , è possibile scrivere
e compilare codice come il codice seguente:

string s = "The answer is " + 5.ToString();


// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();


// Outputs: "System.Int32"
Console.WriteLine(type);
Tipi generici
Un tipo può essere dichiarato con uno o più parametri di tipo che agiscono da segnaposto per il tipo effettivo
(tipo concreto) che il codice client specifica quando si crea un'istanza del tipo. Questi tipi sono definiti tipi
generici. Ad esempio, il tipo .NET System.Collections.Generic.List<T> ha un parametro di tipo a cui per
convenzione viene assegnato il nome T. Quando si crea un'istanza del tipo, si specifica il tipo degli oggetti che
l'elenco conterrà, ad esempio, String:

List<string> stringList = new List<string>();


stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);

L'uso del parametro di tipo consente di riutilizzare la stessa classe per contenere qualsiasi tipo di elemento
senza dover convertire ogni elemento in object. Le classi di raccolte generiche sono denominate raccolte
fortemente tipizzate perché il compilatore conosce il tipo specifico degli elementi della raccolta e può generare
un errore in fase di compilazione se, ad esempio, si tenta di aggiungere un numero intero all' stringList
oggetto nell'esempio precedente. Per altre informazioni, vedere Generics.

Tipi impliciti, tipi anonimi e tipi di valore Nullable


Come indicato in precedenza, è possibile tipizzare una variabile locale (ma non membri di classe) in modo
implicito usando la parola chiave var. Alla variabile viene comunque assegnato un tipo in fase di compilazione,
specificato dal compilatore. Per altre informazioni, vedere Variabili locali tipizzate in modo implicito.
Può essere scomodo creare un tipo denominato per set semplici di valori correlati che non si intende archiviare
o passare all'esterno dei limiti del metodo. A questo scopo è possibile creare tipi anonimi. Per ulteriori
informazioni, vedere tipi anonimi.
I tipi di valore normali non possono avere un valore null. Tuttavia, è possibile creare tipi di valore Nullable
accodando un oggetto ? dopo il tipo. Ad esempio, int? è un tipo int che può avere anche il valore null. I tipi
di valore nullable sono istanze del tipo di struct generico System.Nullable<T> . I tipi di valore nullable sono
particolarmente utili quando si passano dati da e verso database in cui i valori numerici potrebbero essere null.
Per altre informazioni, vedere tipi di valore Nullable.

Tipo in fase di compilazione e tipo di runtime


Una variabile può presentare tipi diversi in fase di compilazione e in fase di esecuzione. Il tipo in fase di
compilazione è il tipo dichiarato o derivato della variabile nel codice sorgente. Il tipo in fase di esecuzione è il
tipo dell'istanza a cui fa riferimento la variabile. Spesso questi due tipi sono uguali, come nell'esempio seguente:

string message = "This is a string of characters";

In altri casi, il tipo in fase di compilazione è diverso, come illustrato nei due esempi seguenti:

object anotherMessage = "This is another string of characters";


IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";

In entrambi gli esempi precedenti, il tipo in fase di esecuzione è string . Il tipo in fase di compilazione si trova
object nella prima riga e IEnumerable<char> nel secondo.

Se i due tipi sono diversi per una variabile, è importante comprendere quando si applicano il tipo in fase di
compilazione e il tipo di Runtime. Il tipo in fase di compilazione determina tutte le azioni eseguite dal
compilatore. Queste azioni del compilatore includono la risoluzione della chiamata al metodo, la risoluzione
dell'overload e i cast impliciti ed espliciti disponibili. Il tipo in fase di esecuzione determina tutte le azioni che
vengono risolte in fase di esecuzione. Queste azioni di run-time includono l'invio di chiamate al metodo virtuale,
la valutazione is di switch espressioni ed altre API di test dei tipi. Per comprendere meglio il modo in cui il
codice interagisce con i tipi, riconoscere l'azione da applicare al tipo.

Sezioni correlate
Per altre informazioni, vedere gli articoli seguenti:
Cast e conversioni di tipi (C#)
Conversione boxing e unboxing
Utilizzo del tipo dinamico
Tipi di valore
Tipi di riferimento
Classi e struct
Tipi anonimi
Generics

Specifiche del linguaggio C#


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

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Conversione dei tipi di dati XML
Tipi integrali
Cast e conversioni di tipi (Guida per programmatori
C#)
02/11/2020 • 9 minutes to read • Edit Online

Poiché C# è tipizzato in modo statico in fase di compilazione, dopo la prima dichiarazione, una variabile non può
essere dichiarata di nuovo o assegnata a un valore di un altro tipo, a meno che tale tipo non sia convertibile in
modo implicito nel tipo della variabile. Ad esempio, string non può essere convertito in modo implicito in
int . Pertanto, dopo che si dichiara i come int , non è possibile assegnargli la stringa "Hello", come illustrato
nel codice seguente:

int i;

// error CS0029: Cannot implicitly convert type 'string' to 'int'


i = "Hello";

Potrebbe tuttavia essere necessario copiare un valore in un variabile o un parametro di metodo di un altro tipo.
Ad esempio, si potrebbe avere una variabile intera da passare a un metodo il cui parametro è tipizzato come
double . O potrebbe essere necessario assegnare una variabile di classe a una variabile di un tipo interfaccia.
Questi tipi di operazioni sono detti conversioni di tipi. In C#, è possibile eseguire i tipi di conversioni seguenti:
Conversioni implicite : non è necessaria alcuna sintassi speciale perché la conversione ha sempre esito
positivo e nessun dato andrà perso. Gli esempi includono conversioni dai tipi integrali più piccoli ai più
grandi e conversioni dalle classi derivate alle classi di base.
Conversioni esplicite (cast) : le conversioni esplicite richiedono un' espressione cast. L'esecuzione di
cast è obbligatoria se si prevede una possibile perdita di informazioni durante la conversione oppure se
la conversione non riesce per altri motivi. Alcuni esempi tipici includono la conversione numerica in un
tipo con precisione inferiore o con un intervallo più piccolo e la conversione di un'istanza della classe di
base in una classe derivata.
Conversioni definite dall'utente : le conversioni definite dall'utente vengono eseguite da metodi
speciali che possono essere definiti per abilitare conversioni esplicite e implicite tra tipi personalizzati che
non hanno una relazione di classe basata su una classe di base. Per altre informazioni, vedere Operatori
di conversione definiti dall'utente.
Conversioni con le classi helper : per eseguire la conversione tra tipi non compatibili, ad esempio
numeri interi e oggetti System.DateTime oppure tra stringhe esadecimali e matrici di byte, è possibile
usare la classe System.BitConverter, la classe System.Convert e i metodi Parse dei tipi numerici
predefiniti, ad esempio Int32.Parse. Per ulteriori informazioni, vedere come convertire una matrice di byte
in un tipo int, come convertire una stringa in un numeroe come eseguire la conversione tra stringhe
esadecimali e tipi numerici.

Conversioni implicite
Per i tipi numerici predefiniti, è possibile eseguire una conversione implicita quando il valore da archiviare può
essere adattato nella variabile senza essere troncato o arrotondato. Per i tipi integrali, questo significa che
l'intervallo del tipo di origine è un subset appropriato dell'intervallo per il tipo di destinazione. Ad esempio, una
variabile di tipo long (integer a 64 bit) può archiviare qualsiasi valore archiviabile da un tipo int (integer a 32
bit). Nell'esempio seguente il compilatore converte in modo implicito il valore num a destra di un tipo long
prima di assegnarlo a bigNum .
// Implicit conversion. A long can
// hold any value an int can hold, and more!
int num = 2147483647;
long bigNum = num;

Per un elenco completo di tutte le conversioni numeriche implicite, vedere la sezione conversioni numeriche
implicite dell'articolo sulle conversioni numeriche predefinite .
Per i tipi di riferimento, è sempre disponibile una conversione implicita da una classe a qualsiasi classe di base
diretta o indiretta o interfacce. Poiché una classe derivata contiene sempre tutti i membri di una classe di base,
non è necessaria alcuna sintassi speciale.

Derived d = new Derived();

// Always OK.
Base b = d;

Conversioni esplicite
Tuttavia, se una conversione non può essere eseguita senza il rischio di perdita di informazioni, il compilatore
richiede di eseguire una conversione esplicita, che viene chiamata cast. Un cast è un modo per informare in
modo esplicito il compilatore che si intende eseguire la conversione e che si è a conoscenza del fatto che la
perdita di dati potrebbe verificarsi oppure il cast potrebbe non riuscire in fase di esecuzione. Per eseguire un
cast, specificare il tipo tra parentesi davanti al valore o alla variabile da convertire. Il programma seguente
esegue il cast di un valore Double a un valore int. Il programma non verrà compilato senza il cast.

class Test
{
static void Main()
{
double x = 1234.7;
int a;
// Cast double to int.
a = (int)x;
System.Console.WriteLine(a);
}
}
// Output: 1234

Per un elenco completo delle conversioni numeriche esplicite supportate, vedere la sezione conversioni
numeriche esplicite dell'articolo sulle conversioni numeriche predefinite .
Per i tipi di riferimento, è necessario un cast esplicito se si vuole eseguire la conversione da un tipo di base a un
tipo derivato:

// Create a new derived type.


Giraffe g = new Giraffe();

// Implicit conversion to base type is safe.


Animal a = g;

// Explicit conversion is required to cast back


// to derived type. Note: This will compile but will
// throw an exception at run time if the right-side
// object is not in fact a Giraffe.
Giraffe g2 = (Giraffe)a;
Un'operazione di cast tra tipi di riferimento non modifica il tipo in fase di esecuzione dell'oggetto sottostante.
Viene modificato solo il tipo del valore usato come riferimento a tale oggetto. Per altre informazioni, vedere
Polimorfismo.

Eccezioni nella conversione di tipi in fase di esecuzione


In alcune conversioni di tipi di riferimento, il compilatore non può determinare se un cast è valido. È possibile
che un'operazione di cast compilata correttamente non riesca in fase di esecuzione. Come illustrato nell'esempio
seguente, un cast di tipo che non riesce in fase di esecuzione genera un'eccezione InvalidCastException.

class Animal
{
public void Eat() => System.Console.WriteLine("Eating.");

public override string ToString() => "I am an animal.";


}

class Reptile : Animal { }


class Mammal : Animal { }

class UnSafeCast
{
static void Main()
{
Test(new Mammal());

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}

static void Test(Animal a)


{
// System.InvalidCastException at run time
// Unable to cast object of type 'Mammal' to type 'Reptile'
Reptile r = (Reptile)a;
}
}

Il Test metodo ha un Animal parametro, quindi il cast esplicito dell'argomento a a un oggetto Reptile
costituisce un presupposto pericoloso. È più sicuro non creare presupposti, ma controllare il tipo. Il linguaggio
C# offre l'operatore is che consente di testare la compatibilità prima di eseguire effettivamente un cast. Per
ulteriori informazioni, vedere come eseguire il cast sicuro usando i criteri di ricerca e gli operatori As e is.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Conversioni della specifica del linguaggio C#.

Vedere anche
Guida per programmatori C#
Tipi
Espressione cast
Operatori di conversione definiti dall'utente
Conversione di tipi generalizzata
Come convertire una stringa in un numero
Boxing e unboxing (Guida per programmatori C#)
02/11/2020 • 7 minutes to read • Edit Online

Il boxing è il processo di conversione di un tipo di valore nel tipo object o in qualsiasi tipo di interfaccia
implementato dal tipo valore. Quando il Common Language Runtime (CLR) esegue il wrapping di un tipo di
valore, esegue il wrapping del valore all'interno di un' System.Object istanza e lo archivia nell'heap gestito.
Mediante la conversione unboxing, invece, il tipo valore viene estratto dall'oggetto. La conversione boxing è
implicita; quella unboxing è esplicita. Il concetto di conversione boxing e unboxing è alla base della visione
unificata del sistema dei tipi in C#, in base alla quale un valore di qualsiasi tipo può essere considerato come un
oggetto.
Nell'esempio seguente viene eseguita la conversione boxing della variabile intera i e la sua assegnazione
all'oggetto o .

int i = 123;
// The following line boxes i.
object o = i;

È quindi possibile eseguire l'unboxing dell'oggetto o e la sua assegnazione alla variabile intera i :

o = 123;
i = (int)o; // unboxing

Nell'esempio seguente viene illustrato l'utilizzo della conversione boxing in C#.

byte[] array = { 0x64, 0x6f, 0x74, 0x63, 0x65, 0x74 };

string hexValue = Convert.ToHexString(array);


Console.WriteLine(hexValue);

/*Output:
646F74636574
*/

Prestazioni
Rispetto alle semplici assegnazioni, le conversioni boxing e unboxing sono processi onerosi dal punto di vista
del calcolo. La conversione boxing di un tipo valore comporta infatti l'allocazione e la costruzione di un nuovo
oggetto. A un livello inferiore, anche il cast richiesto per la conversione unboxing è oneroso dal punto di vista
del calcolo. Per altre informazioni, vedere Prestazioni.

Boxing
La conversione boxing viene utilizzata per archiviare tipi valore nell'heap sottoposto a Garbage Collection. Il
boxing è una conversione implicita di un tipo di valore al tipo object o a qualsiasi tipo di interfaccia
implementato da questo tipo di valore. La conversione boxing di un tipo valore prevede l'allocazione di
un'istanza dell'oggetto nell'heap e la copia del valore nel nuovo oggetto.
Si consideri la seguente dichiarazione di una variabile di tipo valore:
int i = 123;

L'istruzione seguente applica implicitamente l'operazione di conversione boxing alla variabile i :

// Boxing copies the value of i into object o.


object o = i;

Il risultato di questa istruzione è la creazione, sullo stack, di un riferimento all'oggetto o che fa riferimento a un
valore di tipo int nell'heap. Questo valore è una copia di quello di tipo valore assegnato alla variabile i . La
differenza tra le due variabili i e o è illustrata nella figura seguente della conversione boxing:

È inoltre possibile, anche se non obbligatorio, eseguire la conversione boxing in modo esplicito, come
nell'esempio seguente:

int i = 123;
object o = (object)i; // explicit boxing

Descrizione
In questo esempio viene eseguita la conversione boxing della variabile intera i in un oggetto o . Il valore
archiviato nella variabile i viene quindi modificato da 123 a 456 . Nell'esempio il tipo valore originale e
l'oggetto sottoposto a conversione boxing utilizzano posizioni di memoria separate, pertanto possono
archiviare valori diversi.

Esempio
class TestBoxing
{
static void Main()
{
int i = 123;

// Boxing copies the value of i into object o.


object o = i;

// Change the value of i.


i = 456;

// The change in i doesn't affect the value stored in o.


System.Console.WriteLine("The value-type value = {0}", i);
System.Console.WriteLine("The object-type value = {0}", o);
}
}
/* Output:
The value-type value = 456
The object-type value = 123
*/
Unboxing
L'unboxing è una conversione esplicita dal tipo object a un tipo di valore o da un tipo di interfaccia a un tipo di
valore che implementa l'interfaccia. Un'operazione unboxing prevede le operazioni seguenti:
Controllo dell'istanza di oggetto per verificare che si tratti di un valore sottoposto a conversione boxing
del tipo valore specificato.
Copia del valore dall'istanza alla variabile di tipo valore.
Le istruzioni seguenti illustrano operazioni di conversione boxing e unboxing:

int i = 123; // a value type


object o = i; // boxing
int j = (int)o; // unboxing

La figura seguente mostra il risultato delle istruzioni precedenti:

Per eseguire correttamente la conversione unboxing di tipi valore in fase di esecuzione, è necessario che
l'elemento sottoposto a conversione unboxing faccia riferimento a un oggetto creato in precedenza tramite la
conversione boxing di un'istanza di tale tipo valore. Il tentativo di eseguire la conversione unboxing di un valore
null genera NullReferenceException. Il tentativo di eseguire la conversione unboxing di un riferimento a un
tipo valore incompatibile genera InvalidCastException.

Esempio
Nell'esempio riportato di seguito viene illustrato un caso di unboxing non valido, nonché l'elemento
InvalidCastException risultante. Se si utilizza try e catch , quando si verifica l'errore viene visualizzato un
messaggio di errore.
class TestUnboxing
{
static void Main()
{
int i = 123;
object o = i; // implicit boxing

try
{
int j = (short)o; // attempt to unbox

System.Console.WriteLine("Unboxing OK.");
}
catch (System.InvalidCastException e)
{
System.Console.WriteLine("{0} Error: Incorrect unboxing.", e.Message);
}
}
}

Questo programma restituisce:


Specified cast is not valid. Error: Incorrect unboxing.

Se si modifica l'istruzione:

int j = (short) o;

in:

int j = (int) o;

la conversione verrà eseguita e si otterrà l'output:


Unboxing OK.

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Tipi riferimento
Tipi valore
Come convertire una matrice di byte in un int
(Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Questo esempio mostra come usare la classe BitConverter per convertire una matrice di byte in un valore int e
di nuovo in una matrice di byte. Può essere necessario ad esempio convertire i byte in un tipo di dati predefinito
dopo la lettura dei byte dalla rete. Oltre al metodo ToInt32 (byte [ ] , Int32) nell'esempio, nella tabella seguente
sono elencati i metodi della BitConverter classe che convertono i byte (da una matrice di byte) ad altri tipi
incorporati.

T IP O REST IT UITO M ETO DO

bool ToBoolean (byte [ ] , Int32)

char ToChar (byte [ ] , Int32)

double ToDouble (byte [ ] , Int32)

short ToInt16 (byte [ ] , Int32)

int ToInt32 (byte [ ] , Int32)

long ToInt64 (byte [ ] , Int32)

float ToSingle (byte [ ] , Int32)

ushort ToUInt16 (byte [ ] , Int32)

uint ToUInt32 (byte [ ] , Int32)

ulong ToUInt64 (byte [ ] , Int32)

Esempio
Questo esempio Inizializza una matrice di byte, inverte la matrice se l'architettura del computer è Little-endian
(ovvero il byte meno significativo viene archiviato per primo), quindi chiama il metodo ToInt32 (byte [ ] , Int32)
per convertire quattro byte nella matrice in un oggetto int . Il secondo argomento di ToInt32 (byte [ ] , Int32)
specifica l'indice iniziale della matrice di byte.

NOTE
L'output può variare a seconda dell'aspetto dell'architettura del computer.
byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first),


// reverse the byte array.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);


Console.WriteLine("int: {0}", i);
// Output: int: 25

Esempio
In questo esempio viene chiamato il metodo GetBytes(Int32) della classe BitConverter per convertire un valore
int in una matrice di byte.

NOTE
L'output può variare a seconda dell'aspetto dell'architettura del computer.

byte[] bytes = BitConverter.GetBytes(201805978);


Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
// Output: byte array: 9A-50-07-0C

Vedi anche
BitConverter
IsLittleEndian
Tipi
Come convertire una stringa in un numero (Guida
per programmatori C#)
28/01/2021 • 7 minutes to read • Edit Online

È possibile convertire una stringa in un numero chiamando il Parse metodo o TryParse trovato nei vari tipi
numerici ( int ,, long double e così via) oppure usando i metodi nella System.Convert classe.
È leggermente più efficiente e semplice chiamare un TryParse Metodo (ad esempio,
int.TryParse("11", out number) ) o un Parse Metodo (ad esempio, var number = int.Parse("11") ). L'uso di un
metodo Convert è più utile per gli oggetti generali che implementano IConvertible.
Usare i Parse TryParse metodi o sul tipo numerico che si prevede sia contenuto nella stringa, ad esempio il
System.Int32 tipo. Il metodo Convert.ToInt32 utilizza il metodo Parse internamente. Il Parse metodo restituisce il
numero convertito; il TryParse metodo restituisce un Boolean valore che indica se la conversione è stata
eseguita correttamente e restituisce il numero convertito in un out parametro. Se il formato della stringa non è
valido, viene Parse generata un'eccezione, ma viene TryParse restituito false . Quando si chiama un metodo
Parse , è sempre consigliabile usare la gestione delle eccezioni per intercettare FormatException in caso di esito
negativo dell'operazione di analisi.

Chiamata dei metodi Parse e TryParse


I Parse TryParse metodi e ignorano lo spazio vuoto all'inizio e alla fine della stringa, ma tutti gli altri caratteri
devono essere caratteri che formano il tipo numerico appropriato ( int , long , ulong , float , decimal e
così via). Gli spazi vuoti all'interno della stringa che sostituisce il numero generano un errore. Ad esempio, è
possibile usare decimal.TryParse per analizzare "10", "10,3" o "10", ma non è possibile usare questo metodo
per analizzare 10 da "10x", "1 0" (si noti lo spazio incorporato), "10,3" (si noti lo spazio incorporato), "10E1" (
float.TryParse funziona qui) e così via. Stringa il cui valore è null o String.Empty che non è possibile
analizzare correttamente. È possibile cercare una stringa vuota o Null prima di tentare di analizzarla chiamando
il metodo String.IsNullOrEmpty.
L'esempio seguente illustra le chiamate con esito positivo e negativo per Parse e TryParse .
using System;

public static class StringConversion


{
public static void Main()
{
string input = String.Empty;
try
{
int result = Int32.Parse(input);
Console.WriteLine(result);
}
catch (FormatException)
{
Console.WriteLine($"Unable to parse '{input}'");
}
// Output: Unable to parse ''

try
{
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: -105

if (Int32.TryParse("-105", out int j))


{
Console.WriteLine(j);
}
else
{
Console.WriteLine("String could not be parsed.");
}
// Output: -105

try
{
int m = Int32.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.

const string inputString = "abc";


if (Int32.TryParse(inputString, out int numValue))
{
Console.WriteLine(numValue);
}
else
{
Console.WriteLine($"Int32.TryParse could not parse '{inputString}' to an int.");
}
// Output: Int32.TryParse could not parse 'abc' to an int.
}
}

Nell'esempio seguente viene illustrato un approccio per l'analisi di una stringa che prevede l'inclusione di
caratteri numerici iniziali (inclusi i caratteri esadecimali) e caratteri non numerici finali. I caratteri validi dall'inizio
di una stringa vengono assegnati a una nuova stringa prima di chiamare il metodo TryParse. Dato che le
stringhe da analizzare contengono un numero ridotto di caratteri, nell'esempio viene chiamato il metodo
String.Concat per assegnare i caratteri validi a una nuova stringa. Per una stringa più grande, è invece possibile
usare la classe StringBuilder.

using System;

public static class StringConversion


{
public static void Main()
{
var str = " 10FFxxx";
string numericString = string.Empty;
foreach (var c in str)
{
// Check for numeric characters (hex in this case) or leading or trailing spaces.
if ((c >= '0' && c <= '9') || (char.ToUpperInvariant(c) >= 'A' && char.ToUpperInvariant(c) <=
'F') || c == ' ')
{
numericString = string.Concat(numericString, c.ToString());
}
else
{
break;
}
}

if (int.TryParse(numericString, System.Globalization.NumberStyles.HexNumber, null, out int i))


{
Console.WriteLine($"'{str}' --> '{numericString}' --> {i}");
}
// Output: ' 10FFxxx' --> ' 10FF' --> 4351

str = " -10FFXXX";


numericString = "";
foreach (char c in str)
{
// Check for numeric characters (0-9), a negative sign, or leading or trailing spaces.
if ((c >= '0' && c <= '9') || c == ' ' || c == '-')
{
numericString = string.Concat(numericString, c);
}
else
{
break;
}
}

if (int.TryParse(numericString, out int j))


{
Console.WriteLine($"'{str}' --> '{numericString}' --> {j}");
}
// Output: ' -10FFXXX' --> ' -10' --> -10
}
}

Chiamata dei metodi Convert


Nella tabella seguente sono elencati alcuni dei metodi della classe Convert che è possibile usare per convertire
una stringa in numero.

T IP O N UM ERIC O M ETO DO

decimal ToDecimal(String)
T IP O N UM ERIC O M ETO DO

float ToSingle(String)

double ToDouble(String)

short ToInt16(String)

int ToInt32(String)

long ToInt64(String)

ushort ToUInt16(String)

uint ToUInt32(String)

ulong ToUInt64(String)

Nell'esempio seguente viene chiamato il Convert.ToInt32(String) metodo per convertire una stringa di input in
un valore int. L'esempio rileva le due eccezioni più comuni che possono essere generate da questo metodo,
FormatException e OverflowException . Se il numero risultante può essere incrementato senza superare
Int32.MaxValue, l'esempio aggiunge 1 al risultato e visualizza l'output.
using System;

public class ConvertStringExample1


{
static void Main(string[] args)
{
int numVal = -1;
bool repeat = true;

while (repeat)
{
Console.Write("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive): ");

string input = Console.ReadLine();

// ToInt32 can throw FormatException or OverflowException.


try
{
numVal = Convert.ToInt32(input);
if (numVal < Int32.MaxValue)
{
Console.WriteLine("The new value is {0}", ++numVal);
}
else
{
Console.WriteLine("numVal cannot be incremented beyond its current value");
}
}
catch (FormatException)
{
Console.WriteLine("Input string is not a sequence of digits.");
}
catch (OverflowException)
{
Console.WriteLine("The number cannot fit in an Int32.");
}

Console.Write("Go again? Y/N: ");


string go = Console.ReadLine();
if (go.ToUpper() != "Y")
{
repeat = false;
}
}
}
}
// Sample Output:
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): 473
// The new value is 474
// Go again? Y/N: y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): 2147483647
// numVal cannot be incremented beyond its current value
// Go again? Y/N: y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): -1000
// The new value is -999
// Go again? Y/N: n

Vedi anche
Tipi
Come determinare se una stringa rappresenta un valore numerico
Esempio: Utilità di formattazione di .NET Core WinForms (C#)
Come eseguire la conversione tra stringhe
esadecimali e tipi numerici (Guida per
programmatori C#)
28/01/2021 • 5 minutes to read • Edit Online

In questi esempi viene mostrato come effettuare le seguenti attività:


Ottenere il valore esadecimale di ogni carattere in un oggetto string.
Ottenere l'oggetto char che corrisponde a ogni valore in una stringa esadecimale.
Convertire un oggetto string esadecimale in un oggetto int.
Convertire un oggetto string esadecimale in un oggetto float.
Convertire una matrice di byte in un oggetto string esadecimale.

Esempio
Questo esempio restituisce il valore esadecimale di ogni carattere in un oggetto string . Prima viene analizzato
l'oggetto string in una matrice di caratteri. Poi viene chiamato ToInt32(Char) in ogni carattere per ottenere il
rispettivo valore numerico. Il numero viene infine formattato come esadecimale in un oggetto string .

string input = "Hello World!";


char[] values = input.ToCharArray();
foreach (char letter in values)
{
// Get the integral value of the character.
int value = Convert.ToInt32(letter);
// Convert the integer value to a hexadecimal value in string form.
Console.WriteLine($"Hexadecimal value of {letter} is {value:X}");
}
/* Output:
Hexadecimal value of H is 48
Hexadecimal value of e is 65
Hexadecimal value of l is 6C
Hexadecimal value of l is 6C
Hexadecimal value of o is 6F
Hexadecimal value of is 20
Hexadecimal value of W is 57
Hexadecimal value of o is 6F
Hexadecimal value of r is 72
Hexadecimal value of l is 6C
Hexadecimal value of d is 64
Hexadecimal value of ! is 21
*/

Esempio
Questo esempio analizza un oggetto string di valori esadecimali e restituisce il carattere corrispondente a ogni
valore esadecimale. Prima chiama il metodo Split (char [ ] ) per ottenere ogni valore esadecimale come singolo
string in una matrice. Chiama quindi ToInt32(String, Int32) per convertire il valore esadecimale in un valore
decimale rappresentato come int. Mostra due modi diversi per ottenere il carattere corrispondente al codice
carattere. La prima tecnica usa string che restituisce il carattere corrispondente all'argomento Integer come
ConvertFromUtf32(Int32). La seconda tecnica esegue il cast in modo esplicito del valore int a un valore char.

string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";


string[] hexValuesSplit = hexValues.Split(' ');
foreach (string hex in hexValuesSplit)
{
// Convert the number expressed in base-16 to an integer.
int value = Convert.ToInt32(hex, 16);
// Get the character corresponding to the integral value.
string stringValue = Char.ConvertFromUtf32(value);
char charValue = (char)value;
Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}",
hex, value, stringValue, charValue);
}
/* Output:
hexadecimal value = 48, int value = 72, char value = H or H
hexadecimal value = 65, int value = 101, char value = e or e
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 6F, int value = 111, char value = o or o
hexadecimal value = 20, int value = 32, char value = or
hexadecimal value = 57, int value = 87, char value = W or W
hexadecimal value = 6F, int value = 111, char value = o or o
hexadecimal value = 72, int value = 114, char value = r or r
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 64, int value = 100, char value = d or d
hexadecimal value = 21, int value = 33, char value = ! or !
*/

Esempio
Questo esempio illustra un altro modo per convertire un valore esadecimale string in un Integer, chiamando il
metodo Parse(String, NumberStyles).

string hexString = "8E2";


int num = Int32.Parse(hexString, System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(num);
//Output: 2274

Esempio
L'esempio seguente illustra come convertire un tipo string esadecimale in float usando la classe
System.BitConverter e il metodo UInt32.Parse.

string hexString = "43480170";


uint num = uint.Parse(hexString, System.Globalization.NumberStyles.AllowHexSpecifier);

byte[] floatVals = BitConverter.GetBytes(num);


float f = BitConverter.ToSingle(floatVals, 0);
Console.WriteLine("float convert = {0}", f);

// Output: 200.0056

Esempio
L'esempio seguente illustra come convertire una matrice byte in una stringa esadecimale tramite la classe
System.BitConverter.

byte[] vals = { 0x01, 0xAA, 0xB1, 0xDC, 0x10, 0xDD };

string str = BitConverter.ToString(vals);


Console.WriteLine(str);

str = BitConverter.ToString(vals).Replace("-", "");


Console.WriteLine(str);

/*Output:
01-AA-B1-DC-10-DD
01AAB1DC10DD
*/

Esempio
Nell'esempio seguente viene illustrato come convertire una matrice di byte in una stringa esadecimale
chiamando il Convert.ToHexString metodo introdotto in .NET 5,0.

byte[] array = { 0x64, 0x6f, 0x74, 0x63, 0x65, 0x74 };

string hexValue = Convert.ToHexString(array);


Console.WriteLine(hexValue);

/*Output:
646F74636574
*/

Vedere anche
Stringhe di formato numerico standard
Tipi
Come determinare se una stringa rappresenta un valore numerico
Utilizzo del tipo dinamico (Guida per
programmatori C#)
02/11/2020 • 9 minutes to read • Edit Online

C# 4 introduce un nuovo tipo, dynamic . Il tipo è statico, ma un oggetto di tipo dynamic ignora il controllo del
tipo statico. Nella maggior parte dei casi, funziona come se disponesse del tipo object . In fase di compilazione,
si presume che un elemento tipizzato come dynamic dynamic supporti qualsiasi operazione. Non è pertanto
importante se l'oggetto ottiene il valore da un'API COM, da un linguaggio dinamico quale IronPython, dal
modello DOM (Document Object Model) HTML, dalla reflection o da un altro elemento del programma. Se,
tuttavia, il codice non è valido, vengono intercettati errori in fase di esecuzione.
Se, ad esempio, il metodo di istanza exampleMethod1 nel codice seguente dispone di un solo parametro, il
compilatore riconosce che la prima chiamata al metodo, ec.exampleMethod1(10, 4) , non è valida perché contiene
due argomenti. La chiamata genera errori di compilazione. La seconda chiamata al metodo,
dynamic_ec.exampleMethod1(10, 4) , non viene controllata dal compilatore poiché il tipo di dynamic_ec è dynamic
. Non viene, pertanto, segnalato alcun errore del compilatore. L'errore, tuttavia, non viene ignorato
indefinitamente. Viene intercettato in fase di esecuzione e provoca un'eccezione in fase di esecuzione.

static void Main(string[] args)


{
ExampleClass ec = new ExampleClass();
// The following call to exampleMethod1 causes a compiler error
// if exampleMethod1 has only one parameter. Uncomment the line
// to see the error.
//ec.exampleMethod1(10, 4);

dynamic dynamic_ec = new ExampleClass();


// The following line is not identified as an error by the
// compiler, but it causes a run-time exception.
dynamic_ec.exampleMethod1(10, 4);

// The following calls also do not cause compiler errors, whether


// appropriate methods exist or not.
dynamic_ec.someMethod("some argument", 7, null);
dynamic_ec.nonexistentMethod();
}

class ExampleClass
{
public ExampleClass() { }
public ExampleClass(int v) { }

public void exampleMethod1(int i) { }

public void exampleMethod2(string str) { }


}

Il ruolo del compilatore in questi esempi consiste nel raggruppare le informazioni sull'operazione prevista da
ogni istruzione in relazione all'oggetto o all'espressione tipizzata come dynamic . In fase di esecuzione, le
informazioni archiviate vengono esaminate e qualsiasi istruzione non valida provoca un'eccezione in fase di
esecuzione.
Il risultato della maggior parte delle operazioni dinamiche è dynamic . Se, ad esempio, si posiziona il puntatore
del mouse sull'utilizzo di testSum nell'esempio seguente, IntelliSense visualizza il tipo ** (variabile locale)
dynamic testSum**.

dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);

Le operazioni in cui il risultato non è dynamic includono:


Le conversioni da dynamic a un altro tipo.
Le chiamate al costruttore che includono argomenti di tipo dynamic .

Il tipo di testInstance , ad esempio, nella dichiarazione seguente è ExampleClass , non dynamic :

var testInstance = new ExampleClass(d);

Esempi di conversione sono illustrati nella sezione seguente, "Conversioni".

Conversioni
Le conversioni tra oggetti dinamici e altri tipi sono facili. In questo modo lo sviluppatore può passare dal
comportamento dinamico a quello non dinamico e viceversa.
Ogni oggetto può essere convertito in tipo dinamico in modo implicito, come illustrato negli esempi seguenti.

dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();

Al contrario, una conversione implicita può essere applicata in modo dinamico a qualsiasi espressione di tipo
dynamic .

int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;

Risoluzione dell'overload con argomenti di tipo dinamico


La risoluzione dell'overload si verifica in fase di esecuzione anziché in fase di compilazione se uno o più
argomenti in una chiamata al metodo dispongono del tipo dynamic o se il ricevitore della chiamata al metodo è
di tipo dynamic . Nell'esempio seguente, se il solo metodo exampleMethod2 accessibile è definito in modo che
accetti un argomento di tipo stringa, l'invio di d1 come argomento non provoca un errore del compilatore,
bensì un'eccezione in fase di esecuzione. La risoluzione dell'overload non riesce in fase di esecuzione perché il
tipo in fase di esecuzione di d1 è int e exampleMethod2 richiede una stringa.
// Valid.
ec.exampleMethod2("a string");

// The following statement does not cause a compiler error, even though ec is not
// dynamic. A run-time exception is raised because the run-time type of d1 is int.
ec.exampleMethod2(d1);
// The following statement does cause a compiler error.
//ec.exampleMethod2(7);

Dynamic Language Runtime


Dynamic Language Runtime (DLR) è un'API introdotta in .NET Framework 4. Fornisce l'infrastruttura che
supporta il tipo dynamic in C# oltre all'implementazione di linguaggi di programmazione dinamici, quali
IronPython e IronRuby. Per altre informazioni su DLR, vedere Dynamic Language Runtime Overview
(Panoramica su Dynamic Language Runtime).

interoperabilità COM
C# 4 include diverse funzionalità che migliorano l'esperienza di interoperabilità con le API COM, ad esempio le
API di automazione di Office. Tra i miglioramenti è compreso l'utilizzo del tipo dynamic e di argomenti
denominati e facoltativi.
Diversi metodi COM consentono la variazione nei tipi di argomento e nel tipo restituito designando i tipi come
object . Per questo motivo è necessario il cast esplicito dei valori per la coordinazione con le variabili
fortemente tipizzate in C#. Se si esegue la compilazione utilizzando l'opzione -link (opzioni del compilatore C#) ,
l'introduzione del dynamic tipo consente di gestire le occorrenze di object nelle firme com come se fossero di
tipo dynamic e di evitare così la maggior parte del cast. Le istruzioni seguenti sono ad esempio in contrasto con
la modalità di accesso a una cella in un foglio di calcolo di Microsoft Office Excel con il tipo dynamic e senza il
tipo dynamic .

// Before the introduction of dynamic.


((Excel.Range)excelApp.Cells[1, 1]).Value2 = "Name";
Excel.Range range2008 = (Excel.Range)excelApp.Cells[1, 1];

// After the introduction of dynamic, the access to the Value property and
// the conversion to Excel.Range are handled by the run-time COM binder.
excelApp.Cells[1, 1].Value = "Name";
Excel.Range range2010 = excelApp.Cells[1, 1];

Argomenti correlati
T ITO LO DESC RIZ IO N E

dinamico Viene descritto l'utilizzo della parola chiave dynamic .

Panoramica di Dynamic Language Runtime Viene fornita una panoramica di DLR, un ambiente di
runtime che estende Common Language Runtime (CLR) con
un set di servizi per linguaggi dinamici.

Procedura dettagliata: creazione e utilizzo di oggetti dinamici Fornisce istruzioni dettagliate per la creazione di un oggetto
dinamico personalizzato e per la creazione di un progetto
che accede a una libreria IronPython .
T ITO LO DESC RIZ IO N E

Come accedere agli oggetti di interoperabilità di Office Viene illustrato come creare un progetto che usa argomenti
usando le funzionalità di C# denominati e facoltativi, il tipo dynamic e altri
miglioramenti che semplificano l'accesso agli oggetti API di
Office.
Procedura dettagliata: Creazione e uso di oggetti
dinamici (C# e Visual Basic)
28/01/2021 • 19 minutes to read • Edit Online

Gli oggetti dinamici espongono i membri, ad esempio proprietà e metodi, in fase di esecuzione anziché in fase di
compilazione. In questo modo è possibile creare oggetti da usare con le strutture che non corrispondono a un
tipo o formato statico. Ad esempio, è possibile usare un oggetto dinamico per fare riferimento al modello a
oggetti documenti (DOM, Document Object Model) HTML, che può contenere qualsiasi combinazione di attributi
ed elementi di markup HTML validi. Poiché ogni documento HTML è univoco, i membri per un particolare
documento HTML vengono determinati in fase di esecuzione. Un metodo comune per fare riferimento a un
attributo di un elemento HTML consiste nel passare il nome dell'attributo al metodo GetProperty dell'elemento.
Per fare riferimento all'attributo id dell'elemento HTML <div id="Div1"> , per prima cosa è necessario ottenere
un riferimento all'elemento <div> e quindi usare divElement.GetProperty("id") . Se si usa un oggetto dinamico,
è possibile fare riferimento all'attributo id come divElement.id .
Gli oggetti dinamici offrono anche un comodo accesso a linguaggi dinamici come IronPython e IronRuby. È
possibile usare un oggetto dinamico per fare riferimento a uno script dinamico che viene interpretato in fase di
esecuzione.
Si fa riferimento a un oggetto dinamico usando l'associazione tardiva. In C# specificare il tipo di un oggetto ad
associazione tardiva come dynamic . In Visual Basic specificare il tipo di un oggetto ad associazione tardiva come
Object . Per altre informazioni, vedere dynamic e Associazione anticipata e tardiva.

È possibile creare oggetti dinamici personalizzati usando le classi dello spazio dei nomi System.Dynamic. Ad
esempio, è possibile creare un oggetto ExpandoObject e specificare i membri di tale oggetto in fase di
esecuzione. È anche possibile creare un proprio tipo che eredita la classe DynamicObject. È quindi possibile
eseguire l'override dei membri della classe DynamicObject per rendere disponibili funzionalità dinamiche in
fase di esecuzione.
In questa procedura verranno illustrate le attività seguenti:
Creare un oggetto personalizzato che espone dinamicamente il contenuto di un file di testo come
proprietà di un oggetto.
Creare un progetto che usa una libreria IronPython .

Prerequisiti
Per completare questa procedura, è necessario avere IronPython per .NET. Passare alla pagina di download per
ottenere la versione più recente.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Creare un oggetto dinamico personalizzato


Il primo progetto creato in questa procedura dettagliata definisce un oggetto dinamico personalizzato che
esegue la ricerca del contenuto di un file di testo. Il testo da cercare viene specificato dal nome di una proprietà
dinamica. Ad esempio, se il codice chiamante specifica dynamicFile.Sample , la classe dinamica restituisce un
elenco generico di stringhe che contiene tutte le righe del file che iniziano con "Sample". La ricerca non fa
distinzione tra maiuscole e minuscole. La classe dinamica supporta inoltre due argomenti facoltativi. Il primo
argomento è un valore di enumerazione dell'opzione di ricerca che indica che la classe dinamica deve cercare le
corrispondenze all'inizio della riga, alla fine della riga o in un punto qualsiasi nella riga. Il secondo argomento
specifica che la classe dinamica deve eliminare gli spazi iniziali e finali da ogni riga prima di eseguire la ricerca.
Ad esempio, se il codice chiamante specifica dynamicFile.Sample(StringSearchOption.Contains) , la classe
dinamica cerca "Sample" in qualsiasi punto di una riga. Se il codice chiamante specifica
dynamicFile.Sample(StringSearchOption.StartsWith, false) , la classe dinamica cerca "Sample" all'inizio di ogni
riga e non rimuove gli spazi iniziali e finali. Il comportamento predefinito della classe dinamica è cercare una
corrispondenza all'inizio di ogni riga e rimuovere gli spazi iniziali e finali.
Per creare una classe dinamica personalizzata
1. Avviare Visual Studio.
2. Scegliere nuovo dal menu file , quindi fare clic su progetto .
3. Nel riquadro Tipi di progetto della finestra di dialogo Nuovo progetto verificare che sia selezionata
l'opzione Windows . Selezionare Applicazione console nel riquadro Modelli . Nella casella Nome
digitare DynamicSample , quindi fare clic su OK . Viene creato il nuovo progetto.
4. Fare clic con il pulsante destro del mouse sul progetto DynamicSample e scegliere Aggiungi , quindi fare
clic su Classe . Nella casella Nome digitare ReadOnlyFile , quindi fare clic su OK . Viene aggiunto un
nuovo file che contiene la classe ReadOnlyFile.
5. Nella parte superiore del file ReadOnlyFile.cs o ReadOnlyFile.vb aggiungere il codice seguente per
importare gli spazi dei nomi System.IO e System.Dynamic.

using System.IO;
using System.Dynamic;

Imports System.IO
Imports System.Dynamic

6. L'oggetto dinamico personalizzato usa un'enumerazione per determinare i criteri di ricerca. Prima
dell'istruzione di classe, aggiungere la seguente definizione di enumerazione.

public enum StringSearchOption


{
StartsWith,
Contains,
EndsWith
}

Public Enum StringSearchOption


StartsWith
Contains
EndsWith
End Enum

7. Aggiornare la dichiarazione di classe per ereditare la classe DynamicObject , come illustrato nell'esempio
di codice seguente.
class ReadOnlyFile : DynamicObject

Public Class ReadOnlyFile


Inherits DynamicObject

8. Aggiungere il codice seguente alla classe ReadOnlyFile per definire un campo privato per il percorso del
file e un costruttore per la classe ReadOnlyFile .

// Store the path to the file and the initial line count value.
private string p_filePath;

// Public constructor. Verify that file exists and store the path in
// the private variable.
public ReadOnlyFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new Exception("File path does not exist.");
}

p_filePath = filePath;
}

' Store the path to the file and the initial line count value.
Private p_filePath As String

' Public constructor. Verify that file exists and store the path in
' the private variable.
Public Sub New(ByVal filePath As String)
If Not File.Exists(filePath) Then
Throw New Exception("File path does not exist.")
End If

p_filePath = filePath
End Sub

9. Aggiungere il metodo GetPropertyValue seguente alla classe ReadOnlyFile . Il metodo GetPropertyValue


accetta come input i criteri di ricerca e restituisce le righe di un file di testo che soddisfano tali criteri di
ricerca. I metodi dinamici specificati dalla classe ReadOnlyFile chiamano il metodo GetPropertyValue per
recuperare i rispettivi risultati.
public List<string> GetPropertyValue(string propertyName,
StringSearchOption StringSearchOption =
StringSearchOption.StartsWith,
bool trimSpaces = true)
{
StreamReader sr = null;
List<string> results = new List<string>();
string line = "";
string testLine = "";

try
{
sr = new StreamReader(p_filePath);

while (!sr.EndOfStream)
{
line = sr.ReadLine();

// Perform a case-insensitive search by using the specified search options.


testLine = line.ToUpper();
if (trimSpaces) { testLine = testLine.Trim(); }

switch (StringSearchOption)
{
case StringSearchOption.StartsWith:
if (testLine.StartsWith(propertyName.ToUpper())) { results.Add(line); }
break;
case StringSearchOption.Contains:
if (testLine.Contains(propertyName.ToUpper())) { results.Add(line); }
break;
case StringSearchOption.EndsWith:
if (testLine.EndsWith(propertyName.ToUpper())) { results.Add(line); }
break;
}
}
}
catch
{
// Trap any exception that occurs in reading the file and return null.
results = null;
}
finally
{
if (sr != null) {sr.Close();}
}

return results;
}
Public Function GetPropertyValue(ByVal propertyName As String,
Optional ByVal StringSearchOption As StringSearchOption =
StringSearchOption.StartsWith,
Optional ByVal trimSpaces As Boolean = True) As List(Of String)

Dim sr As StreamReader = Nothing


Dim results As New List(Of String)
Dim line = ""
Dim testLine = ""

Try
sr = New StreamReader(p_filePath)

While Not sr.EndOfStream


line = sr.ReadLine()

' Perform a case-insensitive search by using the specified search options.


testLine = UCase(line)
If trimSpaces Then testLine = Trim(testLine)

Select Case StringSearchOption


Case StringSearchOption.StartsWith
If testLine.StartsWith(UCase(propertyName)) Then results.Add(line)
Case StringSearchOption.Contains
If testLine.Contains(UCase(propertyName)) Then results.Add(line)
Case StringSearchOption.EndsWith
If testLine.EndsWith(UCase(propertyName)) Then results.Add(line)
End Select
End While
Catch
' Trap any exception that occurs in reading the file and return Nothing.
results = Nothing
Finally
If sr IsNot Nothing Then sr.Close()
End Try

Return results
End Function

10. Dopo il metodo GetPropertyValue aggiungere il codice seguente per eseguire l'override del metodo
TryGetMember della classe DynamicObject. Il metodo TryGetMember viene chiamato quando viene
richiesto un membro di una classe dinamica e non vengono specificati argomenti. L'argomento binder
contiene informazioni relative al membro a cui viene fatto riferimento e l'argomento result fa
riferimento al risultato restituito per il membro specificato. Il metodo TryGetMember restituisce un valore
booleano che restituisce true se il membro richiesto esiste, altrimenti restituisce false .

// Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
result = GetPropertyValue(binder.Name);
return result == null ? false : true;
}

' Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
11. Dopo il metodo TryGetMember aggiungere il codice seguente per eseguire l'override del metodo
TryInvokeMember della classe DynamicObject. Il metodo TryInvokeMember viene chiamato quando viene
richiesto un membro di una classe dinamica con argomenti. L'argomento binder contiene informazioni
relative al membro a cui viene fatto riferimento e l'argomento result fa riferimento al risultato restituito
per il membro specificato. L'argomento args contiene una matrice degli argomenti passati al membro. Il
metodo TryInvokeMember restituisce un valore booleano che restituisce true se il membro richiesto
esiste, altrimenti restituisce false .
La versione personalizzata del metodo TryInvokeMember prevede che il primo argomento sia un valore
dell'enumerazione StringSearchOption definita in un passaggio precedente. Il metodo TryInvokeMember
prevede che il secondo argomento sia un valore booleano. Se uno o entrambi gli argomenti sono valori
validi, vengono passati al metodo GetPropertyValue per recuperare i risultati.

// Implement the TryInvokeMember method of the DynamicObject class for


// dynamic member calls that have arguments.
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args,
out object result)
{
StringSearchOption StringSearchOption = StringSearchOption.StartsWith;
bool trimSpaces = true;

try
{
if (args.Length > 0) { StringSearchOption = (StringSearchOption)args[0]; }
}
catch
{
throw new ArgumentException("StringSearchOption argument must be a StringSearchOption enum
value.");
}

try
{
if (args.Length > 1) { trimSpaces = (bool)args[1]; }
}
catch
{
throw new ArgumentException("trimSpaces argument must be a Boolean value.");
}

result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces);

return result == null ? false : true;


}
' Implement the TryInvokeMember method of the DynamicObject class for
' dynamic member calls that have arguments.
Public Overrides Function TryInvokeMember(ByVal binder As InvokeMemberBinder,
ByVal args() As Object,
ByRef result As Object) As Boolean

Dim StringSearchOption As StringSearchOption = StringSearchOption.StartsWith


Dim trimSpaces = True

Try
If args.Length > 0 Then StringSearchOption = CType(args(0), StringSearchOption)
Catch
Throw New ArgumentException("StringSearchOption argument must be a StringSearchOption enum
value.")
End Try

Try
If args.Length > 1 Then trimSpaces = CType(args(1), Boolean)
Catch
Throw New ArgumentException("trimSpaces argument must be a Boolean value.")
End Try

result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces)

Return If(result Is Nothing, False, True)


End Function

12. Salvare e chiudere il file.


Per creare un file di testo di esempio
1. Fare clic con il pulsante destro del mouse sul progetto DynamicSample e scegliere Aggiungi , quindi fare
clic su Nuovo elemento . Nel riquadro Modelli installati selezionare Generale , quindi il modello File
di testo . Lasciare il nome predefinito TextFile1.txt nella casella Nome e quindi fare clic su Aggiungi . Un
nuovo file di testo viene aggiunto al progetto.
2. Copiare il testo seguente nel file TextFile1.txt.

List of customers and suppliers

Supplier: Lucerne Publishing (https://www.lucernepublishing.com/)


Customer: Preston, Chris
Customer: Hines, Patrick
Customer: Cameron, Maria
Supplier: Graphic Design Institute (https://www.graphicdesigninstitute.com/)
Supplier: Fabrikam, Inc. (https://www.fabrikam.com/)
Customer: Seubert, Roxanne
Supplier: Proseware, Inc. (http://www.proseware.com/)
Customer: Adolphi, Stephan
Customer: Koch, Paul

3. Salvare e chiudere il file.


Per creare un'applicazione di esempio che usa l'oggetto dinamico personalizzato
1. In Esplora Soluzioni fare doppio clic sul file Module1.vb se si usa Visual Basic o sul file Program.cs se si
usa Visual C#.
2. Aggiungere il seguente codice alla routine Main per creare un'istanza della classe ReadOnlyFile per il file
TextFile1.txt. Il codice usa l'associazione tardiva per chiamare i membri dinamici e recuperare le righe di
testo che contengono la stringa "Customer".
dynamic rFile = new ReadOnlyFile(@"..\..\TextFile1.txt");
foreach (string line in rFile.Customer)
{
Console.WriteLine(line);
}
Console.WriteLine("----------------------------");
foreach (string line in rFile.Customer(StringSearchOption.Contains, true))
{
Console.WriteLine(line);
}

Dim rFile As Object = New ReadOnlyFile("..\..\TextFile1.txt")


For Each line In rFile.Customer
Console.WriteLine(line)
Next
Console.WriteLine("----------------------------")
For Each line In rFile.Customer(StringSearchOption.Contains, True)
Console.WriteLine(line)
Next

3. Salvare il file e premere CTRL+F5 per compilare ed eseguire l'applicazione.

Chiamare una libreria di linguaggio dinamico


Il progetto successivo di questa procedura dettagliata accede a una libreria scritta nel linguaggio dinamico
IronPython.
Per creare una classe dinamica personalizzata
1. In Visual Studio scegliere nuovo dal menu file , quindi fare clic su progetto .
2. Nel riquadro Tipi di progetto della finestra di dialogo Nuovo progetto verificare che sia selezionata
l'opzione Windows . Selezionare Applicazione console nel riquadro Modelli . Nella casella Nome
digitare DynamicIronPythonSample , quindi fare clic su OK . Viene creato il nuovo progetto.
3. Se si usa Visual Basic, fare clic con il pulsante destro del mouse sul progetto DynamicIronPythonSample e
quindi fare clic su Proprietà . Fare clic sulla scheda riferimenti . Fare clic sul pulsante Aggiungi . Se si
usa Visual C#, in Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Riferimenti
e quindi fare clic su Aggiungi riferimento .
4. Nella scheda Sfoglia passare alla cartella in cui sono installate le librerie di IronPython. Ad esempio,
C:\Program Files\IronPython 2,6 per .NET Framework 4,0. Selezionare le librerie IronPython.dll ,
IronPython.Modules.dll , Microsoft.Scripting.dll e Microsoft.Dynamic.dll . Fare clic su OK .
5. Se si usa Visual Basic, modificare il file Module1.vb. Se si usa Visual C#, modificare il file Program.cs.
6. Nella parte superiore del file aggiungere il codice seguente per importare gli spazi dei nomi
Microsoft.Scripting.Hosting e IronPython.Hosting dalle librerie di IronPython.

using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

Imports Microsoft.Scripting.Hosting
Imports IronPython.Hosting

7. Nel metodo Main aggiungere il codice seguente per creare un nuovo oggetto
Microsoft.Scripting.Hosting.ScriptRuntime per ospitare le librerie di IronPython. L'oggetto
ScriptRuntime carica il modulo di libreria random.py di IronPython.

// Set the current directory to the IronPython libraries.


System.IO.Directory.SetCurrentDirectory(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
@"\IronPython 2.6 for .NET 4.0\Lib");

// Create an instance of the random.py IronPython library.


Console.WriteLine("Loading random.py");
ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");
Console.WriteLine("random.py loaded.");

' Set the current directory to the IronPython libraries.


My.Computer.FileSystem.CurrentDirectory =
My.Computer.FileSystem.SpecialDirectories.ProgramFiles &
"\IronPython 2.6 for .NET 4.0\Lib"

' Create an instance of the random.py IronPython library.


Console.WriteLine("Loading random.py")
Dim py = Python.CreateRuntime()
Dim random As Object = py.UseFile("random.py")
Console.WriteLine("random.py loaded.")

8. Dopo il codice che carica il modulo random.py, aggiungere il codice seguente per creare una matrice di
numeri interi. La matrice viene passata al metodo shuffle del modulo random.py, che ordina i valori
nella matrice in modo casuale.

// Initialize an enumerable set of integers.


int[] items = Enumerable.Range(1, 7).ToArray();

// Randomly shuffle the array of integers by using IronPython.


for (int i = 0; i < 5; i++)
{
random.shuffle(items);
foreach (int item in items)
{
Console.WriteLine(item);
}
Console.WriteLine("-------------------");
}

' Initialize an enumerable set of integers.


Dim items = Enumerable.Range(1, 7).ToArray()

' Randomly shuffle the array of integers by using IronPython.


For i = 0 To 4
random.shuffle(items)
For Each item In items
Console.WriteLine(item)
Next
Console.WriteLine("-------------------")
Next

9. Salvare il file e premere CTRL+F5 per compilare ed eseguire l'applicazione.

Vedere anche
System.Dynamic
System.Dynamic.DynamicObject
Utilizzo del tipo dinamico
Associazione anticipata e tardiva
dinamico
Implementazione delle interfacce dinamiche (PDF scaricabile da Microsoft TechNet)
Classi e struct (Guida per programmatori C#)
02/11/2020 • 12 minutes to read • Edit Online

Classi e struct sono due dei costrutti di base del Common Type System in .NET. Ognuno di essi è costituito
essenzialmente da una struttura di dati che incapsula un set di dati e comportamenti che formano insieme
un'unità logica. I dati e i comportamenti sono i membri della classe o del tipo struct e ne includono i metodi, le
proprietà, gli eventi e così via, come illustrato più avanti in questo argomento.
Una dichiarazione di classe o struct è come un progetto iniziale usato per creare istanze o oggetti in fase di
esecuzione. Se si definisce una classe o un tipo struct chiamato Person , Person è il nome del tipo. Se si dichiara
e inizializza una variabile p di tipo Person , p è definito oggetto o istanza di Person . È possibile creare più
istanze dello stesso tipo Person e nelle proprietà e nei campi di ogni istanza possono essere specificati valori
diversi.
Una classe è un tipo riferimento. Quando viene creato un oggetto della classe, la variabile a cui è assegnato
l'oggetto contiene solo un riferimento alla memoria. Se il riferimento all'oggetto viene assegnato a una nuova
variabile, questa fa riferimento all'oggetto originale. Le modifiche apportate tramite una variabile vengono
riflesse nell'altra variabile perché entrambe fanno riferimento agli stessi dati.
Un tipo struct è un tipo valore. Quando viene creato un tipo struct, la variabile a cui è assegnato questo tipo ne
contiene i dati effettivi. Quando viene assegnato a una nuova variabile, il tipo struct viene copiato. La nuova
variabile e quella originale contengono quindi due copie separate degli stessi dati. Eventuali modifiche
apportate a una copia non influiscono sull'altra copia.
In generale, le classi vengono usate per modellare un comportamento più complesso o dati destinati a essere
modificati dopo la creazione di un oggetto di classe. I tipi struct sono invece più adatti a piccole strutture che
contengono principalmente dati non destinati a essere modificati dopo la creazione del tipo.
Per ulteriori informazioni, vedere classi, oggettie tipi di struttura.

Esempio
Nell'esempio seguente la classe CustomClass inclusa nello spazio dei nomi ProgrammingGuide contiene tre
membri: un costruttore di istanze, una proprietà denominata Number e un metodo denominato Multiply . Il
Main metodo nella Program classe crea un'istanza (oggetto) di CustomClass e il metodo e la proprietà
dell'oggetto sono accessibili tramite la notazione del punto.
using System;

namespace ProgrammingGuide
{
// Class definition.
public class CustomClass
{
// Class members.
//
// Property.
public int Number { get; set; }

// Method.
public int Multiply(int num)
{
return num * Number;
}

// Instance Constructor.
public CustomClass()
{
Number = 0;
}
}

// Another class definition that contains Main, the program entry point.
class Program
{
static void Main(string[] args)
{
// Create an object of type CustomClass.
CustomClass custClass = new CustomClass();

// Set the value of the public property.


custClass.Number = 27;

// Call the public method.


int result = custClass.Multiply(4);
Console.WriteLine($"The result is {result}.");
}
}
}
// The example displays the following output:
// The result is 108.

Incapsulamento
L'incapsulamento è talvolta definito come il principio fondamentale della programmazione orientata agli
oggetti. In base al principio di incapsulamento, una classe o un tipo struct può specificare il modo in cui ognuno
dei rispettivi membri è accessibile da codice esterno. I metodi e le variabili destinati a non essere usati
all'esterno della classe o dell'assembly possono essere nascosti per limitare il rischio di errori di codifica o esiti
dannosi.
Per altre informazioni sulle classi, vedere Classi e Oggetti.
Membri
Tutti i metodi, i campi, le costanti, le proprietà e gli eventi devono essere dichiarati all'interno di un tipo e
prendono il nome di membri del tipo. In C# non esistono variabili o metodi globali come in altri linguaggi.
Anche il punto di ingresso di un programma, il metodo Main , deve essere dichiarato all'interno di una classe o
di un tipo struct. Di seguito sono elencati tutti i tipi di membri che possono essere dichiarati in una classe o in un
tipo struct.
Fields
Costanti
Proprietà
Metodi
Costruttori
Eventi
Finalizzatori
Indicizzatori
Operatori
Tipi annidati
Accessibilità
Alcuni metodi e proprietà sono progettati in modo da essere chiamabili o accessibili da codice esterno alla
classe o al tipo struct, noto come codice client. Altri metodi e proprietà possono invece essere usati
esclusivamente all'interno della classe o del tipo struct. Questa distinzione è importante per limitare
l'accessibilità del codice in modo che solo il codice client desiderato possa raggiungerlo. È possibile specificare
l'accessibilità dei tipi e dei loro membri dal codice client usando i modificatori di accesso public, protected,
internal, protected internal, private e private protected. L'accessibilità predefinita è private . Per altre
informazioni, vedere Modificatori di accesso.
Ereditarietà
Le classi (ma non i tipi struct) supportano il concetto di ereditarietà. Una classe che deriva da un'altra classe
definita classe di base contiene automaticamente tutti i membri pubblici, protetti e interni della classe di base, ad
eccezione di costruttori e finalizzatori. Per altre informazioni, vedere Ereditarietà e Polimorfismo.
Le classi possono essere dichiarate come astratte. Ciò significa che uno o più metodi di tali classi sono privi di
implementazione. Anche se non è possibile crearne direttamente un'istanza, le classi astratte possono svolgere
la funzione di classi di base per altre classi che forniscono l'implementazione mancante. Le classi possono anche
essere dichiarate come sealed per impedire che altre classi ereditino da esse. Per altre informazioni, vedere classi
e membri delle classi astratte e sealed.
Interfacce
Le classi e i tipi struct possono ereditare più interfacce. Quando eredita da un'interfaccia, un tipo implementa
tutti i metodi definiti in tale interfaccia. Per ulteriori informazioni, vedi Interfacce.
Tipi generici
Le classi e i tipi struct possono essere definiti con uno o più parametri di tipo. Il codice client fornisce il tipo
quando ne crea un'istanza. Ad esempio, la classe List<T> nello spazio dei nomi System.Collections.Generic viene
definita con un solo parametro di tipo. Il codice client crea un'istanza di List<string> o List<int> per
specificare il tipo che sarà contenuto nell'elenco. Per altre informazioni, vedere Generics.
Tipi statici
Le classi (ma non i tipi struct) possono essere dichiarate come statiche. Una classe statica può contenere solo
membri statici e non è possibile crearne un'istanza con la nuova parola chiave. Una copia della classe viene
caricata in memoria durante il caricamento del programma e i relativi membri sono accessibili tramite il nome
della classe. Sia le classi che i tipi struct possono contenere membri statici. Per altre informazioni, vedere classi
statiche e membri di classi statiche.
Tipi annidati
Una classe o un tipo struct può essere annidato all'interno di un'altra classe o di un altro tipo struct. Per ulteriori
informazioni, vedere tipi annidati.
Tipi parziali
È possibile definire una parte di una classe, un tipo struct o un metodo in un file di codice e un'altra parte in un
file di codice separato. Per altre informazioni, vedere Classi e metodi parziali.
Inizializzatori di oggetti
È possibile creare istanze e inizializzare oggetti classe o struct e raccolte di oggetti senza chiamare in modo
esplicito il relativo costruttore. Per altre informazioni, vedere Inizializzatori di oggetto e di raccolta.
Tipi anonimi
Nelle situazioni in cui non conviene o non è necessario creare una classe denominata, ad esempio quando si
popola un elenco con strutture di dati che non devono necessariamente essere persistenti o passate a un altro
metodo, è possibile usare i tipi anonimi. Per ulteriori informazioni, vedere tipi anonimi.
Metodi di estensione
È possibile estendere una classe senza creare una classe derivata creando un tipo distinto i cui metodi possono
essere chiamati come se appartenessero al tipo originale. Per altre informazioni, vedere Metodi di estensione.
Variabili locali tipizzate in modo implicito
All'interno di un metodo di classe o struct è possibile usare la tipizzazione implicita per indicare al compilatore
di determinare il tipo corretto in fase di compilazione. Per altre informazioni, vedere Variabili locali tipizzate in
modo implicito.

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Classi (Guida per programmatori C#)
28/01/2021 • 9 minutes to read • Edit Online

Tipi riferimento
Un tipo definito come classe è un tipo di riferimento. In fase di esecuzione, quando si dichiara una variabile di un
tipo riferimento, la variabile contiene il valore Null fino a quando non si crea in modo esplicito un'istanza della
classe usando l'operatore new o fino a quando non le viene assegnato un oggetto di un tipo compatibile creato
altrove, come illustrato nell'esempio seguente:

//Declaring an object of type MyClass.


MyClass mc = new MyClass();

//Declaring another object of the same type, assigning it the value of the first object.
MyClass mc2 = mc;

Quando viene creato l'oggetto, una quantità di memoria sufficiente viene allocata nell'heap gestito per l'oggetto
specifico e la variabile mantiene solo un riferimento al percorso dell'oggetto. I tipi nell'heap gestito richiedono
un overhead quando vengono allocati e recuperati dalla funzionalità di gestione automatica della memoria di
CLR, nota come Garbage Collection. La Garbage Collection, tuttavia, è anche altamente ottimizzata e, nella
maggior parte degli scenari, non genera un problema di prestazioni. Per altre informazioni sulla Garbage
Collection, vedere Gestione automatica della memoria e Garbage Collection.

Dichiarazione di classi
Le classi vengono dichiarate usando la parola chiave class seguita da un identificatore univoco, come illustrato
nell'esempio seguente:

//[access modifier] - [class] - [identifier]


public class Customer
{
// Fields, properties, methods and events go here...
}

La parola chiave class è preceduta dal livello di accesso. Poiché in questo caso viene usata la parola chiave
public, chiunque può creare istanze di questa classe. Il nome della classe segue la parola chiave class . Il nome
della classe deve essere un nome di identificatore C# valido. Il resto della definizione è il corpo della classe, in cui
vengono definiti il comportamento e i dati. I campi, le proprietà, i metodi e gli eventi in una classe vengono
collettivamente definiti membri della classe.

Creazione di oggetti
Anche se vengono talvolta usati in modo intercambiabile, una classe e un oggetto sono elementi diversi. Una
classe definisce un tipo di oggetto, ma non è un oggetto. Un oggetto è un'entità concreta ed è basato su una
classe. Talvolta si fa riferimento all'oggetto come istanza di una classe.
Gli oggetti possono essere creati tramite la parola chiave new seguita dal nome della classe su cui si baserà
l'oggetto, nel modo seguente:
Customer object1 = new Customer();

Quando viene creata un'istanza di una classe, viene passato al programmatore un riferimento all'oggetto.
Nell'esempio precedente, object1 è un riferimento a un oggetto basato su Customer . Questo riferimento indica
il nuovo oggetto, ma non contiene i dati dell'oggetto. Infatti, è possibile creare un riferimento all'oggetto senza
creare un oggetto:

Customer object2;

Non è consigliabile creare riferimenti a oggetti come questo che non fanno riferimento a un oggetto reale
perché il tentativo di accedere a un oggetto tramite tale riferimento avrà esito negativo in fase di esecuzione.
Tuttavia, è possibile fare riferimento a un oggetto in modo che faccia riferimento a un oggetto creando un nuovo
oggetto o assegnandogli un oggetto esistente, ad esempio:

Customer object3 = new Customer();


Customer object4 = object3;

Questo codice crea due riferimenti a oggetti che fanno entrambi riferimento allo stesso oggetto. Tutte le
modifiche effettuate all'oggetto tramite object3 si riflettono tuttavia nei successivi usi di object4 . Poiché gli
oggetti che si basano su classi vengono indicati tramite riferimenti, le classi sono note come tipi di riferimento.

Ereditarietà delle classi


Le classi supportano completamente l' ereditarietà , una caratteristica fondamentale nella programmazione
orientata a oggetti. Quando si crea una classe, è possibile ereditare da qualsiasi altra classe non definita come
sealede le altre classi possono ereditare dalla classe ed eseguire l'override dei metodi virtuali della classe.
Inoltre, è possibile implementare una o più interfacce.
L'ereditarietà si ottiene usando una derivazione , vale a dire che una classe viene dichiarata usando una classe di
base da cui eredita dati e comportamento. Una classe di base viene specificata tramite l'aggiunta di due punti e
il nome della classe di base dopo il nome della classe derivata, nel modo seguente:

public class Manager : Employee


{
// Employee fields, properties, methods and events are inherited
// New Manager fields, properties, methods and events go here...
}

Quando una classe dichiara una classe di base, eredita tutti i membri della classe di base, a eccezione dei
costruttori. Per altre informazioni, vedere Ereditarietà.
Diversamente da C++, una classe di C# può ereditare direttamente solo da una classe di base. Tuttavia, poiché
una classe di base può ereditare da un'altra classe, una classe può ereditare indirettamente più classi di base.
Una classe può inoltre implementare direttamente una o più interfacce. Per ulteriori informazioni, vedi
Interfacce.
Una classe può essere dichiarata come astratta. Una classe astratta contiene metodi astratti che hanno una
definizione di firma, ma senza implementazione. Non è possibile creare un'istanza di classi astratte. Le classi
astratte possono essere usate solo tramite classi derivate che implementano i metodi astratti. Al contrario, una
classe sealed non consente ad altre classi di derivare da tale classe. Per altre informazioni, vedere classi e
membri delle classi astratte e sealed.
Le definizioni di classe possono essere suddivise tra file di origine diversa. Per altre informazioni, vedere Classi e
metodi parziali.

Esempio
L'esempio seguente definisce una classe pubblica che contiene una proprietà implementata automaticamente,
un metodo e un metodo speciale denominato costruttore. Per altre informazioni, vedere gli argomenti Proprietà,
Metodi e Costruttori. Le istanze della classe vengono quindi create con la parola chiave new .

using System;

public class Person


{
// Constructor that takes no arguments:
public Person()
{
Name = "unknown";
}

// Constructor that takes one argument:


public Person(string name)
{
Name = name;
}

// Auto-implemented readonly property:


public string Name { get; }

// Method that overrides the base class (System.Object) implementation.


public override string ToString()
{
return Name;
}
}
class TestPerson
{
static void Main()
{
// Call the constructor that has no parameters.
var person1 = new Person();
Console.WriteLine(person1.Name);

// Call the constructor that has one parameter.


var person2 = new Person("Sarah Jones");
Console.WriteLine(person2.Name);
// Get the string representation of the person2 instance.
Console.WriteLine(person2);

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}
// Output:
// unknown
// Sarah Jones
// Sarah Jones

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Programmazione orientata a oggetti
Polimorfismo
Nomi di identificatore
Members
Metodi
Costruttori
Finalizzatori
Oggetti
Oggetti (Guida per programmatori C#)
02/11/2020 • 9 minutes to read • Edit Online

Una definizione di classe o struct è simile a un progetto iniziale in cui vengono specificate le funzionalità del tipo.
Un oggetto è essenzialmente un blocco di memoria che è stato allocato e configurato in base al progetto iniziale.
Un programma può creare molti oggetti della stessa classe. Gli oggetti, definiti anche istanze, possono essere
archiviati in una variabile denominata o in una matrice o raccolta. Il codice client è il codice che usa queste
variabili per chiamare i metodi e accedere alle proprietà pubbliche dell'oggetto. In un linguaggio orientato a
oggetti come C#, il programma tipico è costituito da più oggetti che interagiscono dinamicamente.

NOTE
I tipi statici si comportano in modo diverso da quanto descritto qui. Per altre informazioni, vedere classi statiche e membri
di classi statiche.

Istanze struct e istanze di classe


Poiché le classi sono tipi di riferimento, una variabile di un oggetto classe contiene un riferimento all'indirizzo
dell'oggetto sull'heap gestito. Se al primo oggetto viene assegnato un secondo oggetto dello stesso tipo,
entrambe le variabili fanno riferimento all'oggetto in quell'indirizzo. Questo punto viene illustrato più
dettagliatamente di seguito in questo argomento.
Le istanze delle classi vengono create usando l'operatore new. Nell'esempio seguente Person è il tipo e
person1 e person 2 sono le istanze o gli oggetti di tale tipo.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Other properties, methods, events...
}

class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);

// Declare new person, assign person1 to it.


Person person2 = person1;

// Change the name of person2, and person1 also changes.


person2.Name = "Molly";
person2.Age = 16;

Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name, person2.Age);


Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Output:
person1 Name = Leopold Age = 6
person2 Name = Molly Age = 16
person1 Name = Molly Age = 16
*/

Poiché gli struct sono tipi di valore, una variabile di un oggetto struct contiene una copia dell'intero oggetto. Le
istanze di struct possono essere create anche usando l'operatore new , sebbene non sia obbligatorio, come
illustrato nell'esempio seguente:
public struct Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}

public class Application


{
static void Main()
{
// Create struct instance and initialize by using "new".
// Memory is allocated on thread stack.
Person p1 = new Person("Alex", 9);
Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

// Create new struct object. Note that struct can be initialized


// without using "new".
Person p2 = p1;

// Assign values to p2 members.


p2.Name = "Spencer";
p2.Age = 7;
Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age);

// p1 values remain unchanged because p2 is copy.


Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Output:
p1 Name = Alex Age = 9
p2 Name = Spencer Age = 7
p1 Name = Alex Age = 9
*/

La memoria per p1 e p2 viene allocata nello stack di thread. La memoria viene recuperata insieme al tipo o al
metodo in cui è stata dichiarata. Questo è il motivo per cui gli struct vengono copiati per assegnazione. Al
contrario, la memoria allocata per l'istanza di una classe viene recuperata automaticamente (tramite Garbage
Collection) da Common Language Runtime quando tutti i riferimenti all'oggetto sono usciti dall'ambito. Non è
possibile eliminare in modo deterministico un oggetto di classe come avviene in C++. Per ulteriori informazioni
su Garbage Collection in .NET, vedere Garbage Collection.

NOTE
L'allocazione e la deallocazione di memoria sull'heap gestito sono estremamente ottimizzate in Common Language
Runtime. Nella maggior parte dei casi non esistono differenze significative in termini di impatto sulle prestazioni tra
l'allocazione di un'istanza di classe sull'heap e l'allocazione di un'istanza di struttura sullo stack.

Identità dell'oggetto e uguaglianza di valori


Quando si confrontano due oggetti per verificarne l'uguaglianza, è necessario innanzitutto distinguere se si
vuole determinare se le due variabili rappresentano lo stesso oggetto in memoria oppure se i valori di uno o più
campi sono equivalenti. Se si intende confrontare valori, è necessario considerare se gli oggetti sono istanze di
tipi di valore (struct) o di tipi di riferimento (classi, delegati, matrici).
Per determinare se due istanze di classe fanno riferimento alla stessa posizione in memoria (ovvero
hanno la stessa identità), usare il metodo statico Equals. System.Object è la classe di base implicita per
tutti i tipi valore e i tipi riferimento, inclusi struct e classi definiti dall'utente.
Per determinare se i campi di istanza in due istanze di struct hanno gli stessi valori, usare il metodo
ValueType.Equals. Poiché tutti gli struct ereditano implicitamente da System.ValueType, il metodo viene
chiamato direttamente nell'oggetto, come illustrato nell'esempio seguente:

// Person is defined in the previous example.

//public struct Person


//{
// public string Name;
// public int Age;
// public Person(string name, int age)
// {
// Name = name;
// Age = age;
// }
//}

Person p1 = new Person("Wallace", 75);


Person p2;
p2.Name = "Wallace";
p2.Age = 75;

if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");

// Output: p2 and p1 have the same values.

L'implementazione System.ValueType di Equals usa la reflection perché deve essere in grado di determinare i
campi presenti in tutti gli struct. Quando si creano struct, eseguire l'override del metodo Equals per specificare
un algoritmo di uguaglianza efficiente specifico del tipo.
Per determinare se i valori dei campi in due istanze di classe sono uguali, è possibile usare il metodo Equals o
l'operatore ==. Tuttavia, usarli solo se la classe ha eseguito il loro override o overload per offrire una
definizione personalizzata di cosa significa "uguaglianza" per gli oggetti di quel tipo. La classe può anche
implementare l'interfaccia IEquatable<T> o IEqualityComparer<T>. Entrambe le interfacce offrono metodi
che possono essere usati per verificare l'uguaglianza dei valori. Quando si progettano classi personalizzate
che eseguono l'override di Equals , assicurarsi di seguire le linee guida indicate in come definire
l'uguaglianza di valori per un tipo e Object.Equals(Object) .

Sezioni correlate
Per altre informazioni:
Classi
Costruttori
Finalizzatori
Eventi

Vedere anche
Guida per programmatori C#
object
Ereditarietà
class
Tipi di struttura
Operatore New
Common Type System
Ereditarietà (Guida per programmatori C#)
02/11/2020 • 11 minutes to read • Edit Online

L'ereditarietà, insieme all'incapsulamento e al polimorfismo, rappresenta una delle tre principali caratteristiche
(o pilastri) della programmazione orientata a oggetti. L'ereditarietà consente di creare nuove classi che
riutilizzano, estendono e modificano il comportamento definito in altre classi. La classe i cui membri vengono
ereditati è denominata classe di base, mentre la classe che eredita tali membri è denominata classe derivata. Una
classe derivata può avere una sola classe di base diretta. L'ereditarietà, tuttavia, è transitiva. Se ClassC è
derivato da ClassB ed ClassB è derivato da ClassA , ClassC eredita i membri dichiarati in ClassB e ClassA .

NOTE
Gli struct non supportano l'ereditarietà, mentre possono implementare interfacce. Per ulteriori informazioni, vedi
Interfacce.

Concettualmente, una classe derivata rappresenta una specializzazione della classe di base. Ad esempio, avendo
una classe di base Animal , è possibile definire una classe derivata denominata Mammal e un'altra classe derivata
denominata Reptile . Un oggetto Mammal è anche un oggetto Animal e un oggetto Reptile è anche un
Animal , ma ogni classe derivata rappresenta una diversa specializzazione della classe di base.

Le dichiarazioni di interfaccia possono definire un'implementazione predefinita per i relativi membri. Queste
implementazioni vengono ereditate dalle interfacce derivate e dalle classi che implementano tali interfacce. Per
ulteriori informazioni sui metodi di interfaccia predefiniti, vedere l'articolo sulle interfacce nella sezione
riferimento al linguaggio.
Quando si definisce una classe derivandola da un'altra classe, la classe derivata acquista implicitamente tutti i
membri della classe di base, con l'eccezione dei costruttori e dei finalizzatori. La classe derivata riutilizza il codice
nella classe di base senza doverlo reimplementare. È possibile aggiungere altri membri nella classe derivata. La
classe derivata estende la funzionalità della classe di base.
La figura riportata di seguito illustra una classe WorkItem che rappresenta un elemento di lavoro in un qualche
processo aziendale. Come per tutte le classi, è derivata da System.Object ed eredita tutti i metodi di tale classe.
WorkItem aggiunge cinque propri membri, Questi membri includono un costruttore, perché i costruttori non
vengono ereditati. La classe ChangeRequest eredita da WorkItem e rappresenta un particolare tipo di elemento
di lavoro. ChangeRequest aggiunge altri due membri ai membri che eredita da WorkItem e da Object. Deve
aggiungere il proprio costruttore e aggiunge anche originalItemID . La proprietà originalItemID consente
l'associazione dell'istanza di ChangeRequest all'oggetto WorkItem originale a cui si applica la richiesta di
modifica.
Nell'esempio seguente viene illustrato come le relazioni tra le classi mostrate nella precedente illustrazione
vengono espresse in C#. Nell'esempio viene descritto anche come WorkItem esegue l'override del metodo
virtuale Object.ToString e come la classe ChangeRequest eredita l'implementazione del metodo propria della
classe WorkItem . Il primo blocco definisce le classi:

// WorkItem implicitly inherits from the Object class.


public class WorkItem
{
// Static field currentID stores the job ID of the last WorkItem that
// has been created.
private static int currentID;

//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }

// Default constructor. If a derived class does not invoke a base-


// class constructor explicitly, the default constructor is called
// implicitly.
public WorkItem()
{
ID = 0;
Title = "Default title";
Description = "Default description.";
jobLength = new TimeSpan();
}

// Instance constructor that has three parameters.


public WorkItem(string title, string desc, TimeSpan joblen)
{
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = joblen;
}

// Static constructor to initialize the static member, currentID. This


// constructor is called one time, automatically, before any instance
// of WorkItem or ChangeRequest is created, or currentID is referenced.
static WorkItem() => currentID = 0;

// currentID is a static field. It is incremented each time a new


// instance of WorkItem is created.
protected int GetNextID() => ++currentID;

// Method Update enables you to update the title and job length of an
// existing WorkItem object.
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
{
this.Title = title;
this.jobLength = joblen;
}

// Virtual method override of the ToString method that is inherited


// from System.Object.
public override string ToString() =>
$"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)


// and two constructors.
public class ChangeRequest : WorkItem
{
protected int originalItemID { get; set; }

// Constructors. Because neither constructor calls a base-class


// constructor explicitly, the default constructor in the base class
// is called implicitly. The base class must contain a default
// constructor.

// Default constructor for the derived class.


public ChangeRequest() { }

// Instance constructor that has four parameters.


public ChangeRequest(string title, string desc, TimeSpan jobLen,
int originalID)
{
// The following properties and the GetNexID method are inherited
// from WorkItem.
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = jobLen;

// Property originalItemId is a member of ChangeRequest, but not


// of WorkItem.
this.originalItemID = originalID;
}
}

Questo blocco successivo Mostra come usare le classi base e derivate:


// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
"Fix all bugs in my code branch",
new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in


// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
"Add members to the class",
new TimeSpan(4, 0, 0),
1);

// Use the ToString method defined in WorkItem.


Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the


// ChangeRequest object.
change.Update("Change the Design of the Base Class",
new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.


Console.WriteLine(change.ToString());
/* Output:
1 - Fix Bugs
2 - Change the Design of the Base Class
*/

Metodi astratti e virtuali


Quando una classe di base dichiara un metodo come virtual , una classe derivata può usare override il
metodo con la propria implementazione. Se una classe di base dichiara un membro come abstract , è
necessario eseguire l'override di tale metodo in una classe non astratta che eredita direttamente da tale classe.
Quando una classe derivata è essa stessa astratta, eredita i membri astratti senza implementarli. I membri
astratti e virtuali costituiscono la base del polimorfismo, che rappresenta la seconda principale caratteristica
della programmazione orientata a oggetti. Per altre informazioni, vedere Polimorfismo.

Classi base astratte


Se si vuole evitare la generazione di istanze dirette di una classe, è possibile dichiarare una classe come abstract
usando l'operatore new. Una classe astratta può essere utilizzata solo se viene derivata una nuova classe. Una
classe astratta può contenere una o più firme di metodi, a loro volta dichiarate come astratte. Tali firme
specificano i parametri e il valore restituito, ma non definiscono alcuna implementazione (corpo del metodo).
Una classe astratta non deve contenere membri astratti; Tuttavia, se una classe contiene un membro astratto, la
classe deve essere dichiarata come astratta. Le classi derivate che non sono astratte devono fornire
l'implementazione per tutti i metodi astratti da una classe base astratta. Per altre informazioni, vedere classi e
membri delle classi astratte e sealed.

Interfacce
Un' interfaccia è un tipo riferimento che definisce un set di membri. Tutte le classi e gli struct che implementano
tale interfaccia devono implementare tale set di membri. Un'interfaccia può definire un'implementazione
predefinita per uno o tutti questi membri. Una classe può implementare più interfacce, anche se può essere
derivata solo da una singola classe di base diretta.
Le interfacce vengono usate per definire funzionalità specifiche per le classi che non hanno necessariamente una
relazione "is a". Ad esempio, l' System.IEquatable<T> interfaccia può essere implementata da qualsiasi classe o
struct per determinare se due oggetti del tipo sono equivalenti, ma il tipo definisce l'equivalenza.
IEquatable<T>non implica lo stesso tipo di relazione "is a" esistente tra una classe di base e una classe derivata,
ad esempio un oggetto Mammal Animal . Per ulteriori informazioni, vedi Interfacce.

Impedisci ulteriore derivazione


Una classe può impedire che altre classi ereditino da esso o da uno dei relativi membri, dichiarando se stesso o
il membro come sealed . Per altre informazioni, vedere classi e membri delle classi astratte e sealed.

Classe derivata che nasconde i membri della classe base


Una classe derivata può nascondere i membri di una classe di base dichiarando dei membri con lo stesso nome
e la stessa firma. Il new modificatore può essere usato per indicare in modo esplicito che il membro non è
destinato a essere un override del membro di base. L'uso di new non è obbligatorio, ma viene generato un
avviso del compilatore se new non viene usato. Per altre informazioni, vedere Controllo delle versioni con le
parole chiave Override e New e Sapere quando usare le parole chiave Override e New.

Vedi anche
Guida per programmatori C#
Classi e struct
class
Polimorfismo (Guida per programmatori C#)
02/11/2020 • 13 minutes to read • Edit Online

Il polimorfismo è spesso definito il terzo pilastro della programmazione orientata a oggetti, dopo
l'incapsulamento e l'ereditarietà. Polimorfismo è una parola che deriva dal greco e significa "multiforme". Il
polimorfismo presenta due aspetti distinti:
In fase di esecuzione, oggetti di una classe derivata possono essere trattati come oggetti di una classe base in
posizioni quali parametri del metodo e raccolte o matrici. Quando si verifica questo polimorfismo, il tipo
dichiarato dell'oggetto non è più identico al tipo in fase di esecuzione.
Le classi base possono definire e implementare Metodi virtuali e le classi derivate possono eseguirne
l'override , il che significa che forniscono la definizione e l'implementazione. Durante la fase di esecuzione,
quando il codice client chiama il metodo, CLR cerca il tipo in fase di esecuzione dell'oggetto e richiama
quell'override del metodo virtuale. Nel codice sorgente è possibile chiamare un metodo su una classe di base
e causare l'esecuzione di una versione della classe derivata del metodo.
I metodi virtuali consentono di usare gruppi di oggetti correlati in modo uniforme. Si supponga ad esempio di
avere un'applicazione di disegno che consenta a un utente di creare vari tipi di forme in un'area di disegno. In
fase di compilazione non è possibile sapere i tipi specifici di forme che l'utente creerà. L'applicazione deve
tuttavia tenere traccia di tutti i vari tipi di forme create e deve aggiornarli in risposta alle azioni del mouse
dell'utente. È possibile usare il polimorfismo per risolvere questo problema in due passaggi di base:
1. Creare una gerarchia di classi nella quale ogni classe della forma specifica deriva da una classe base comune.
2. Usare un metodo virtuale per richiamare il metodo adatto su qualsiasi classe derivata tramite una sola
chiamata al metodo della classe base.
Prima di tutto, creare una classe base denominata Shape e delle classi derivate quali Rectangle , Circle e
Triangle . Definire nella classe Shape un metodo virtuale denominato Draw ed eseguirne l'override in ogni
classe derivata per disegnare la particolare forma che la classe rappresenta. Creare un List<Shape> oggetto e
aggiungervi un oggetto Circle , Triangle e Rectangle .
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }

// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}

public class Circle : Shape


{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
public class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
public class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}

Per aggiornare l'area di disegno, usare un ciclo foreach per scorrere l'elenco e chiamare il metodo Draw su ogni
oggetto Shape nell'elenco. Anche se ogni oggetto nell'elenco ha un tipo dichiarato di Shape , è il tipo in fase di
esecuzione (la versione sottoposta a override del metodo in ogni classe derivata) che verrà richiamato.
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};

// Polymorphism at work #2: the virtual method Draw is


// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
shape.Draw();
}
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/

In C# ogni tipo è polimorfico perché tutti i tipi, incluso i tipi definiti dall'utente, ereditano da Object.

Cenni preliminari sul polimorfismo


Membri virtuali
Quando una classe derivata eredita da una classe base, ottiene tutti i metodi, i campi, le proprietà e gli eventi
della classe di base. La finestra di progettazione della classe derivata ha scelte diverse per il comportamento dei
metodi virtuali:
La classe derivata può eseguire l'override dei membri virtuali della classe di base, definendo un nuovo
comportamento.
La classe derivata eredita il metodo della classe base più vicino senza eseguirne l'override, mantenendo il
comportamento esistente ma abilitando ulteriori classi derivate per eseguire l'override del metodo.
La classe derivata può definire una nuova implementazione non virtuale dei membri che nascondono le
implementazioni della classe di base.
Una classe derivata può eseguire l'override di un membro della classe base solo se quest'ultimo è dichiarato
come virtuale o astratto. Il membro derivato deve usare la parola chiave override per indicare esplicitamente
che il metodo deve partecipare alla chiamata virtuale. Nel codice seguente ne viene illustrato un esempio:
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}

I campi non possono essere virtuali. solo i metodi, le proprietà, gli eventi e gli indicizzatori possono essere
virtuali. Quando una classe derivata esegue l'override di un membro virtuale, quest'ultimo viene chiamato
anche nel caso in cui si acceda a un'istanza di tale classe come istanza della classe base. Nel codice seguente ne
viene illustrato un esempio:

DerivedClass B = new DerivedClass();


B.DoWork(); // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.

Metodi virtuali e proprietà consentono alle classi derivate di estendere una classe base senza dover usare
l'implementazione della classe base di un metodo. Per altre informazioni, vedere Controllo delle versioni con le
parole chiave Override e New. Un'interfaccia fornisce un'altra modalità per definire un metodo o un insieme di
metodi la cui implementazione è lasciata alle classi derivate. Per ulteriori informazioni, vedi Interfacce.
Nascondi membri della classe di base con nuovi membri
Se si vuole che la classe derivata disponga di un membro con lo stesso nome di un membro in una classe di
base, è possibile usare la parola chiave New per nascondere il membro della classe base. La parola chiave new
viene inserita prima del tipo restituito di un membro di classe che viene sostituito. Nel codice seguente ne viene
illustrato un esempio:

public class BaseClass


{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}

public class DerivedClass : BaseClass


{
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
È possibile accedere ai membri nascosti della classe base dal codice client eseguendo il cast dell'istanza della
classe derivata a un'istanza della classe di base. Ad esempio:

DerivedClass B = new DerivedClass();


B.DoWork(); // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.

Impedire alle classi derivate di eseguire l'override dei membri virtuali


I membri virtuali rimangono virtuali, indipendentemente dal numero di classi dichiarate tra il membro virtuale e
la classe che l'ha originariamente dichiarata. Se la classe A dichiara un membro virtuale e la classe B deriva da
A , e la classe C deriva da B , la classe C eredita il membro virtuale ed è possibile eseguirne l'override,
indipendentemente dal fatto che la classe abbia B dichiarato una sostituzione per quel membro. Nel codice
seguente ne viene illustrato un esempio:

public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}

Una classe derivata può interrompere l'ereditarietà virtuale dichiarando un override come sealed. Per arrestare
l'ereditarietà è necessario inserire la sealed parola chiave prima della override parola chiave nella
dichiarazione del membro della classe. Nel codice seguente ne viene illustrato un esempio:

public class C : B
{
public sealed override void DoWork() { }
}

Nell'esempio precedente, il metodo DoWork non è più virtuale per le classi derivate da C . È ancora virtuale per
le istanze di C , anche se ne viene eseguito il cast al tipo B o al tipo A . I metodi sealed possono essere
sostituiti da classi derivate tramite la new parola chiave, come illustrato nell'esempio seguente:

public class D : C
{
public new void DoWork() { }
}

In questo caso, se DoWork viene chiamato su D utilizzando una variabile di tipo D , DoWork viene chiamato il
nuovo. Se una variabile di tipo C , B o A viene utilizzata per accedere a un'istanza di D , una chiamata a
DoWork seguirà le regole dell'ereditarietà virtuale, indirizzando tali chiamate all'implementazione di DoWork
nella classe C .
Accedere ai membri virtuali della classe di base dalle classi derivate
Una classe derivata che ha sostituito un metodo o una proprietà, o ne ha eseguito l'override, può ancora
accedere al metodo o alla proprietà sulla classe base usando la parola chiave base . Nel codice seguente ne
viene illustrato un esempio:
public class Base
{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}

Per altre informazioni, vedere base.

NOTE
Nell'implementazione dei membri virtuali è consigliabile l'uso della parola chiave base per le chiamate
all'implementazione della classe base di tali membri. In questo modo, nella classe derivata sarà possibile definire la sola
implementazione del comportamento specifico per tale classe. Se l'implementazione della classe base non viene chiamata,
spetterà alla classe derivata rendere il proprio comportamento compatibile con quello della classe base.

Contenuto della sezione


Controllo delle versioni con le parole chiave Override e New
Sapere quando utilizzare le parole chiave Override e New
Come eseguire l'override del metodo ToString

Vedere anche
Guida per programmatori C#
Ereditarietà
Classi e membri delle classi astratte e sealed
Metodi
Events
Proprietà
Indicizzatori
Tipi
Controllo delle versioni con le parole chiave
Override e New (Guida per programmatori C#)
02/11/2020 • 10 minutes to read • Edit Online

Il linguaggio C# è progettato in modo che il controllo delle versioni tra le classi di base e le classi derivate in
diverse librerie possa svilupparsi e mantenere la compatibilità con le versioni precedenti. Ciò significa ad
esempio che l'introduzione di un nuovo membro in una classe di base con lo stesso nome di un membro in una
classe derivata è completamente supportata da C# e non causa comportamenti imprevisti. Significa inoltre che
una classe deve dichiarare in modo esplicito se un metodo deve eseguire l'override di un metodo ereditato o se
si tratta di un nuovo metodo che consente di nascondere un metodo ereditato con nome simile.
In C# le classi derivate possono contenere metodi con lo stesso nome dei metodi delle classi di base.
Se il metodo della classe derivata non è preceduto dalle parole chiave new o override, il compilatore
genera un avviso e il metodo si comporta come se fosse presente la parola chiave new .
Se il metodo della classe derivata è preceduto dalla parola chiave new , il metodo è definito come
indipendente dal metodo della classe di base.
Se il metodo della classe derivata è preceduto dalla parola chiave override , gli oggetti della classe
derivata chiameranno tale metodo anziché il metodo della classe di base.
Per applicare la override parola chiave al metodo nella classe derivata, il metodo della classe base deve
essere definito come Virtual.
Il metodo della classe di base può essere chiamato dall'interno della classe derivata usando la parola
chiave base .
Le parole chiave override , virtual e new possono essere applicate anche a proprietà, indicizzatori ed
eventi.
Per impostazione predefinita, i metodi C# non sono virtuali. Se un metodo viene dichiarato come virtuale,
qualsiasi classe che eredita il metodo può implementare la propria versione. Per rendere un metodo virtuale, si
usa il modificatore virtual nella dichiarazione del metodo della classe di base. La classe derivata può quindi
eseguire l'override del metodo di base virtuale usando la parola chiave override oppure nascondere il metodo
virtuale nella classe di base usando la parola chiave new . Se non si specifica né la parola chiave override né la
parola chiave new , il compilatore genera un avviso e il metodo della classe derivata nasconde il metodo della
classe di base.
Per dimostrare questo concetto in pratica, si supponga che la società A abbia creato una classe denominata
GraphicsClass che viene usata dal programma. Di seguito è illustrata la classe GraphicsClass :

class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}

La società usa questa classe e l'utente la usa per derivare la propria classe, aggiungendo un nuovo metodo:
class YourDerivedGraphicsClass : GraphicsClass
{
public void DrawRectangle() { }
}

L'applicazione viene eseguita senza problemi, finché la società A rilascia una nuova versione di GraphicsClass ,
simile al codice seguente:

class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
public virtual void DrawRectangle() { }
}

La nuova versione di GraphicsClass ora contiene un metodo denominato DrawRectangle . Inizialmente non
accade nulla. La nuova versione è ancora compatibile a livello binario con la versione precedente. Qualsiasi
software già distribuito continuerà a funzionare, anche se la nuova classe viene installata nei relativi computer.
Le eventuali chiamate al metodo DrawRectangle continueranno a fare riferimento alla versione in uso nella
classe derivata.
Tuttavia, non appena l'applicazione viene ricompilata usando la nuova versione di GraphicsClass , si riceve un
avviso del compilatore, CS0108. L'avviso informa che è necessario stabilire in che modo dovrà funzionare il
metodo DrawRectangle nell'applicazione.
Se il metodo deve eseguire l'override del nuovo metodo della classe di base, usare la parola chiave override :

class YourDerivedGraphicsClass : GraphicsClass


{
public override void DrawRectangle() { }
}

La parola chiave override garantisce che qualsiasi oggetto derivato da YourDerivedGraphicsClass userà la
versione della classe derivata di DrawRectangle . Gli oggetti derivati da YourDerivedGraphicsClass possono
comunque accedere alla versione della classe di base DrawRectangle usando la parola chiave di base:

base.DrawRectangle();

Se si preferisce che il metodo non esegua l'override del nuovo metodo della classe di base, attenersi alle
indicazioni che seguono. Per evitare confusione tra i due metodi, è possibile rinominare il metodo da usare.
Questa operazione può richiedere molto tempo e causare errori e in alcuni casi è poco pratica. Tuttavia, se il
progetto è relativamente piccolo, è possibile usare le opzioni di refactoring di Visual Studio per rinominare il
metodo. Per altre informazioni, vedere Refactoring di classi e tipi (Progettazione classi).
In alternativa, è possibile evitare l'avviso usando la parola chiave new nella definizione della classe derivata:

class YourDerivedGraphicsClass : GraphicsClass


{
public new void DrawRectangle() { }
}

L'uso della parola chiave new indica al compilatore che la definizione nasconde la definizione contenuta nella
classe di base. Questo è il comportamento predefinito.
Override e selezione del metodo
Quando un metodo è denominato in una classe, il compilatore C# seleziona il metodo migliore per la chiamata
se più metodi sono compatibili con la chiamata, ad esempio quando esistono due metodi con lo stesso nome e
parametri compatibili con il parametro passato. I seguenti metodi sarebbero compatibili:

public class Derived : Base


{
public override void DoWork(int param) { }
public void DoWork(double param) { }
}

Quando si chiama DoWork per un'istanza di Derived , il compilatore C# tenta per prima cosa di rendere la
chiamata compatibile con le versioni di DoWork originariamente dichiarate in Derived . I metodi di override non
vengono considerati come dichiarati per una classe, sono nuove implementazioni di un metodo dichiarato per
una classe di base. Solo se il compilatore C# non è in grado di associare la chiamata al metodo a un metodo
originale in Derived , tenterà di associare la chiamata a un metodo sottoposto a override con lo stesso nome e
parametri compatibili. Ad esempio:

int val = 5;
Derived d = new Derived();
d.DoWork(val); // Calls DoWork(double).

Poiché la variabile val può essere convertita in un valore double in modo implicito, il compilatore C# chiama
DoWork(double) anziché DoWork(int) . Questa situazione può essere evitata in due modi. Primo, evitare di
dichiarare i nuovi metodi con lo stesso nome dei metodi virtuali. Secondo, è possibile indicare al compilatore C#
di chiamare il metodo virtuale facendo in modo che esegua una ricerca nell'elenco dei metodi della classe di
base eseguendo il cast dell'istanza di Derived a Base . Poiché il metodo è virtuale, verrà chiamata
l'implementazione di DoWork(int) per Derived . Ad esempio:

((Base)d).DoWork(val); // Calls DoWork(int) on Derived.

Per altri esempi di new e override , vedere Sapere quando usare le parole chiave Override e New.

Vedere anche
Guida per programmatori C#
Classi e struct
Metodi
Ereditarietà
Sapere quando utilizzare le parole chiave Override
e New (Guida per programmatori C#)
02/11/2020 • 15 minutes to read • Edit Online

Nel linguaggio C# un metodo in una classe derivata può avere lo stesso nome di un metodo in una classe di
base. È possibile specificare in che modo avviene l'interazione tra i metodi usando le parole chiave new e
override. Il modificatore override estende il metodo virtual della classe di base e il modificatore new
nasconde il metodo della classe di base accessibile. La differenza è illustrata negli esempi riportati in questo
argomento.
In un'applicazione console, dichiarare le due classi BaseClass e DerivedClass . DerivedClass eredita da
BaseClass .

class BaseClass
{
public void Method1()
{
Console.WriteLine("Base - Method1");
}
}

class DerivedClass : BaseClass


{
public void Method2()
{
Console.WriteLine("Derived - Method2");
}
}

Nel metodo Main dichiarare le variabili bc , dc e bcdc .


bc è di tipo BaseClass e il suo valore è di tipo BaseClass .
dc è di tipo DerivedClass e il suo valore è di tipo DerivedClass .
bcdc è di tipo BaseClass e il suo valore è di tipo DerivedClass . Si tratta della variabile a cui prestare
attenzione.
Poiché bc e bcdc sono di tipo BaseClass , possono solo accedere direttamente a Method1 , a meno che non
venga usato il cast. Attraverso la variabile dc è possibile accedere sia a Method1 che a Method2 . Queste
relazioni sono illustrate nel codice seguente.
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();

bc.Method1();
dc.Method1();
dc.Method2();
bcdc.Method1();
}
// Output:
// Base - Method1
// Base - Method1
// Derived - Method2
// Base - Method1
}

Quindi aggiungere il metodo Method2 seguente a BaseClass . La firma di questo metodo corrisponde alla firma
del metodo Method2 in DerivedClass .

public void Method2()


{
Console.WriteLine("Base - Method2");
}

Poiché BaseClass ha ora un metodo Method2 , è possibile aggiungere una seconda istruzione di chiamata per le
variabili di BaseClass``bc e bcdc , come illustrato nel codice seguente.

bc.Method1();
bc.Method2();
dc.Method1();
dc.Method2();
bcdc.Method1();
bcdc.Method2();

Quando si compila il progetto, l'aggiunta del metodo Method2 in BaseClass genera un avviso. L'avviso indica
che il metodo Method2 in DerivedClass nasconde il metodo Method2 in BaseClass . Se si intende ottenere tale
risultato, è consigliabile l'uso della parola chiave new nella definizione di Method2 . In alternativa, per risolvere il
problema è possibile rinominare uno dei metodi Method2 , ma ciò non è sempre pratico.
Prima di aggiungere new , eseguire il programma per verificare l'output prodotto da altre istruzioni di chiamata.
Vengono visualizzati i risultati seguenti.

// Output:
// Base - Method1
// Base - Method2
// Base - Method1
// Derived - Method2
// Base - Method1
// Base - Method2

La parola chiave new mantiene le relazioni che producono l'output, ma elimina l'avviso. Le variabili di tipo
BaseClass continuano ad accedere ai membri di BaseClass , mentre la variabile di tipo DerivedClass continua
ad accedere prima ai membri in DerivedClass e in seguito prende in considerazione i membri ereditati da
BaseClass .
Per eliminare l'avviso, aggiungere il modificatore new alla definizione di Method2 in DerivedClass , come
illustrato nel codice seguente. Il modificatore può essere aggiunto prima o dopo l'oggetto public .

public new void Method2()


{
Console.WriteLine("Derived - Method2");
}

Eseguire nuovamente il programma per verificare che l'output non sia stato modificato. Verificare anche che
l'avviso non venga più visualizzato. Usando new si afferma di essere informati che il membro di cui si sta
eseguendo la modifica nasconde un membro ereditato dalla classe di base. Per altre informazioni su come viene
nascosto un nome tramite ereditarietà, vedere Modificatore new.
Per contrastare questo comportamento agli effetti dell'uso di override , aggiungere il metodo seguente a
DerivedClass . Il modificatore override può essere aggiunto prima o dopo l'oggetto public .

public override void Method1()


{
Console.WriteLine("Derived - Method1");
}

Aggiungere il modificatore virtual alla definizione di Method1 in BaseClass . Il modificatore virtual può
essere aggiunto prima o dopo l'oggetto public .

public virtual void Method1()


{
Console.WriteLine("Base - Method1");
}

Eseguire di nuovo il progetto. Si notino soprattutto le ultime due linee dell'output riportato di seguito.

// Output:
// Base - Method1
// Base - Method2
// Derived - Method1
// Derived - Method2
// Derived - Method1
// Base - Method2

L'uso del modificatore override consente a bcdc di accedere al metodo Method1 definito in DerivedClass . In
genere si tratta del comportamento previsto nelle gerarchie di ereditarietà. Si vuole che gli oggetti dotati di
valori creati dalla classe derivata usino i metodi definiti nella classe derivata stessa. Si ottiene questo
comportamento usando override per estendere il metodo della classe di base.
Il codice seguente contiene l'esempio completo.
using System;
using System.Text;

namespace OverrideAndNew
{
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();

// The following two calls do what you would expect. They call
// the methods that are defined in BaseClass.
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2

// The following two calls do what you would expect. They call
// the methods that are defined in DerivedClass.
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2

// The following two calls produce different results, depending


// on whether override (Method1) or new (Method2) is used.
bcdc.Method1();
bcdc.Method2();
// Output:
// Derived - Method1
// Base - Method2
}
}

class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}

public virtual void Method2()


{
Console.WriteLine("Base - Method2");
}
}

class DerivedClass : BaseClass


{
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}

public new void Method2()


{
Console.WriteLine("Derived - Method2");
}
}
}
L'esempio riportato di seguito illustra un comportamento simile in un contesto diverso. L'esempio definisce tre
classi: una classe di base denominata Car e due classi derivate da essa, ConvertibleCar e Minivan . La classe di
base contiene un metodo DescribeCar . Il metodo visualizza una descrizione di base di un'automobile, quindi
chiama l'oggetto ShowDetails per fornire informazioni aggiuntive. Ognuna delle tre classi definisce un metodo
ShowDetails . Il modificatore new viene usato per definire ShowDetails nella classe ConvertibleCar . Il
modificatore override viene usato per definire ShowDetails nella classe Minivan .

// Define the base class, Car. The class defines two methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
// class also defines a ShowDetails method. The example tests which version of
// ShowDetails is selected, the base class method or the derived class method.
class Car
{
public void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}

public virtual void ShowDetails()


{
System.Console.WriteLine("Standard transportation.");
}
}

// Define the derived classes.

// Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails


// hides the base class method.
class ConvertibleCar : Car
{
public new void ShowDetails()
{
System.Console.WriteLine("A roof that opens up.");
}
}

// Class Minivan uses the override modifier to specify that ShowDetails


// extends the base class method.
class Minivan : Car
{
public override void ShowDetails()
{
System.Console.WriteLine("Carries seven people.");
}
}

L'esempio verifica quale versione di ShowDetails viene chiamata. Il metodo seguente, TestCars1 , dichiara
un'istanza di ogni classe e quindi chiama DescribeCar per ogni istanza.
public static void TestCars1()
{
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");

Car car1 = new Car();


car1.DescribeCar();
System.Console.WriteLine("----------");

// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.

ConvertibleCar car2 = new ConvertibleCar();


car2.DescribeCar();
System.Console.WriteLine("----------");

Minivan car3 = new Minivan();


car3.DescribeCar();
System.Console.WriteLine("----------");
}

TestCars1 produce l'output seguente. Si notino in particolare i risultati per car2 , che probabilmente non sono
quelli previsti. Il tipo dell'oggetto è ConvertibleCar , ma DescribeCar non accede alla versione di ShowDetails
definita nella classe ConvertibleCar perché tale metodo è dichiarato con il modificatore new , non con il
modificatore override . Di conseguenza, un oggetto ConvertibleCar visualizza la stessa descrizione di un
oggetto Car . Confrontare i risultati per car3 , che è un oggetto Minivan . In questo caso, il metodo
ShowDetails che viene dichiarato nella classe Minivan esegue l'override del metodo ShowDetails che viene
dichiarato nella classe Car e la descrizione visualizzata descrive un furgoncino.

// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

TestCars2 determina la creazione di oggetti di tipo Car . Le istanze dei valori degli oggetti vengono create dalle
classi Car , ConvertibleCar e Minivan . DescribeCar viene chiamato per ogni elemento dell'elenco. Il codice
seguente illustra la definizione di TestCars2 .

public static void TestCars2()


{
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");

var cars = new List<Car> { new Car(), new ConvertibleCar(),


new Minivan() };

foreach (var car in cars)


{
car.DescribeCar();
System.Console.WriteLine("----------");
}
}
Verrà visualizzato l'output seguente. Si noti che corrisponde all'output visualizzato da TestCars1 . Il metodo
ShowDetails della classe ConvertibleCar non viene chiamato, indipendentemente dal fatto che il tipo di oggetto
sia ConvertibleCar , come in TestCars1 o Car come in TestCars2 . Al contrario, car3 chiama il metodo
ShowDetails della classe Minivan in entrambi i casi, sia se è di tipo Minivan che se è di tipo Car .

// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

I metodi TestCars3 e TestCars4 completano l'esempio. Questi metodi chiamano direttamente ShowDetails ,
prima da oggetti dichiarati con tipo ConvertibleCar e Minivan ( TestCars3 ) e quindi da oggetti dichiarati con
tipo Car ( TestCars4 ). Il codice seguente definisce i due metodi.

public static void TestCars3()


{
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
Minivan car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}

public static void TestCars4()


{
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
Car car2 = new ConvertibleCar();
Car car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}

I metodi producono l'output seguente, che corrisponde ai risultati del primo esempio di questo argomento.

// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.

// TestCars4
// ----------
// Standard transportation.
// Carries seven people.

Il codice seguente illustra l'esempio completo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OverrideAndNew2
namespace OverrideAndNew2
{
class Program
{
static void Main(string[] args)
{
// Declare objects of the derived classes and test which version
// of ShowDetails is run, base or derived.
TestCars1();

// Declare objects of the base class, instantiated with the


// derived classes, and repeat the tests.
TestCars2();

// Declare objects of the derived classes and call ShowDetails


// directly.
TestCars3();

// Declare objects of the base class, instantiated with the


// derived classes, and repeat the tests.
TestCars4();
}

public static void TestCars1()


{
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");

Car car1 = new Car();


car1.DescribeCar();
System.Console.WriteLine("----------");

// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
ConvertibleCar car2 = new ConvertibleCar();
car2.DescribeCar();
System.Console.WriteLine("----------");

Minivan car3 = new Minivan();


car3.DescribeCar();
System.Console.WriteLine("----------");
}
// Output:
// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

public static void TestCars2()


{
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");

var cars = new List<Car> { new Car(), new ConvertibleCar(),


new Minivan() };

foreach (var car in cars)


{
car.DescribeCar();
System.Console.WriteLine("----------");
}
}
}
// Output:
// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

public static void TestCars3()


{
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
Minivan car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.

public static void TestCars4()


{
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
Car car2 = new ConvertibleCar();
Car car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars4
// ----------
// Standard transportation.
// Carries seven people.
}

// Define the base class, Car. The class defines two virtual methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
// class also defines a ShowDetails method. The example tests which version of
// ShowDetails is used, the base class method or the derived class method.
class Car
{
public virtual void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}

public virtual void ShowDetails()


{
System.Console.WriteLine("Standard transportation.");
}
}

// Define the derived classes.

// Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails


// hides the base class method.
class ConvertibleCar : Car
{
public new void ShowDetails()
{
System.Console.WriteLine("A roof that opens up.");
}
}

// Class Minivan uses the override modifier to specify that ShowDetails


// extends the base class method.
class Minivan : Car
{
public override void ShowDetails()
{
System.Console.WriteLine("Carries seven people.");
}
}

Vedi anche
Guida per programmatori C#
Classi e struct
Controllo delle versioni con le parole chiave Override e New
base
astratta
Come eseguire l'override del metodo ToString
(Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Ogni classe o struct in C# eredita in modo implicito la classe Object. Ogni oggetto in C# ottiene quindi il metodo
ToString, che restituisce una rappresentazione di stringa dell'oggetto. Ad esempio, tutte le variabili di tipo int
hanno un metodo ToString che consente loro di restituire il rispettivo contenuto sotto forma di stringa:

int x = 42;
string strx = x.ToString();
Console.WriteLine(strx);
// Output:
// 42

Quando si crea una classe o uno struct personalizzato, eseguire l'override del metodo ToString per fornire
informazioni sul tipo al codice client.
Per informazioni sull'uso di stringhe di formato e altri tipi di formattazione personalizzata con il metodo
ToString , vedere Formattazione di tipi.

IMPORTANT
Dopo aver deciso quali informazioni comunicare tramite questo metodo, considerare se la classe o lo struct sarà usato da
codice non attendibile. Verificare attentamente di non indicare informazioni che potrebbero essere sfruttate da malware.

Per eseguire l'override del metodo ToString nella classe o nello struct:
1. Dichiarare un metodo ToString con i modificatori e il tipo restituito seguenti:

public override string ToString(){}

2. Implementare il metodo in modo che restituisca una stringa.


L'esempio seguente restituisce il nome della classe oltre ai dati specifici per una particolare istanza della
classe.

class Person
{
public string Name { get; set; }
public int Age { get; set; }

public override string ToString()


{
return "Person: " + Name + " " + Age;
}
}

È anche possibile testare il metodo ToString come illustrato nel codice di esempio seguente:
Person person = new Person { Name = "John", Age = 12 };
Console.WriteLine(person);
// Output:
// Person: John 12

Vedi anche
IFormattable
Guida per programmatori C#
Classi e struct
Stringhe
string
override
virtuale
Formattazione di tipi
Membri (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Le classi e gli struct contengono membri che ne rappresentano i dati e il comportamento. I membri di una classe
includono tutti i membri dichiarati nella classe, oltre a tutti i membri (ad eccezione di costruttori e finalizzatori)
dichiarati in tutte le classi nella relativa gerarchia di ereditarietà. I membri privati nelle classi base vengono
ereditati ma non sono accessibili dalle classi derivate.
Nella tabella seguente sono elencati i tipi di membri che possono essere contenuti in una classe o in uno struct:

M EM B RO DESC RIZ IO N E

Fields I campi sono variabili dichiarate nell'ambito della classe. Un


campo può essere un tipo numerico incorporato o un'istanza
di un'altra classe. Una classe calendario può ad esempio
avere un campo che contiene la data corrente.

Costanti Le costanti sono campi il cui valore è impostato in fase di


compilazione e non può essere modificato.

Proprietà Le proprietà sono metodi di una classe ai quali si accede


come se si trattasse di campi della classe. Possono garantire
la sicurezza di un campo di una classe in modo da impedire
che venga modificato all'insaputa dell'oggetto.

Metodi I metodi definiscono le azioni che una classe è in grado di


eseguire. Possono accettare parametri che forniscono dati di
input e restituire dati di output tramite i parametri. Possono
anche restituire un valore direttamente, senza l'uso di
parametri.

Events Gli eventi forniscono notifiche ad altri oggetti su ciò che si


verifica, ad esempio le operazioni di clic su pulsanti o il
completamento corretto di un metodo. Vengono definiti e
generati tramite i delegati.

Operatori Gli operatori sottoposti a overload sono considerati membri


del tipo. Quando si esegue l'overload di un operatore, lo si
definisce come metodo statico pubblico in un tipo. Per altre
informazioni, vedere Overload degli operatori.

Indicizzatori Gli indicizzatori consentono di eseguire l'indicizzazione di un


oggetto in modo simile a quanto avviene per le matrici.

Costruttori I costruttori sono metodi che vengono chiamati la prima


volta che viene creato l'oggetto. Vengono spesso usati per
inizializzare i dati di un oggetto.

Finalizzatori I finalizzatori vengono usati molto raramente in C#. Questi


sono metodi che vengono chiamati dal motore di esecuzione
di runtime quando l'oggetto sta per essere rimosso dalla
memoria. Vengono in genere usati per assicurarsi che le
eventuali risorse da rilasciare vengano gestite nel modo
appropriato.
M EM B RO DESC RIZ IO N E

Tipi annidati I tipi nidificati sono tipi dichiarati all'interno di un altro tipo.
Vengono spesso usati per descrivere gli oggetti usati solo dai
tipi che li contengono.

Vedi anche
Guida per programmatori C#
Classi
Classi e membri delle classi astratte e sealed (Guida
per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

La parola chiave abstract consente di creare classi e membri di classe, che sono incompleti e devono essere
implementati in una classe derivata.
La parola chiave sealed consente di impedire l'ereditarietà di una classe o di determinati membri di classe
contrassegnati in precedenza come virtuali.

Classi e membri di classi astratte


Una classe può essere dichiarata astratta inserendo la parola chiave abstract prima della definizione della
classe. Ad esempio:

public abstract class A


{
// Class members here.
}

Non è possibile creare un'istanza di una classe astratta. Lo scopo di una classe astratta è quello di fornire una
definizione comune di una classe base condivisibile da più classi derivate. Una libreria di classi può, ad esempio,
definire una classe astratta utilizzata come parametro per molte funzioni e richiedere ai programmatori che
utilizzano tale libreria di fornire un'implementazione personalizzata della classe creando una classe derivata.
Le classi astratte possono inoltre definire metodi astratti aggiungendo la parola chiave abstract prima del tipo
restituito del metodo. Ad esempio:

public abstract class A


{
public abstract void DoWork(int i);
}

I metodi astratti non prevedono implementazione, pertanto la definizione del metodo è seguita da un punto e
virgola, anziché da un normale blocco di metodi. Le classi derivate della classe astratta devono implementare
tutti i metodi astratti. Quando una classe astratta eredita un metodo virtuale da una classe base, la classe
astratta può eseguire l'override del metodo virtuale con un metodo astratto. Ad esempio:
// compile with: -target:library
public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}

public abstract class E : D


{
public abstract override void DoWork(int i);
}

public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}

Un metodo virtual dichiarato abstract rimane virtuale in qualsiasi classe che eredita dalla classe astratta.
Una classe che eredita un metodo astratto non può accedere all'implementazione originale del metodo.
Nell'esempio precedente, DoWork sulla classe F non può chiamare DoWork sulla classe D. In questo modo una
classe astratta può imporre alle classi derivate di fornire nuove implementazioni per i metodi virtuali.

Classi e membri di classi sealed


Le classi possono essere dichiarate sealed inserendo la parola chiave sealed prima della definizione della
classe. Ad esempio:

public sealed class D


{
// Class members here.
}

Una classe sealed non può essere utilizzata come classe base. Per questo motivo non può neppure essere una
classe astratta. Le classi sealed impediscono la derivazione. Poiché non possono mai essere utilizzate come
classe base, in alcune ottimizzazioni runtime la chiamata ai membri di classi sealed può risultare leggermente
più rapida.
Un metodo, un indicizzatore, una proprietà o un evento di una classe derivata che esegue l'override di un
membro virtuale della classe base può dichiarare tale membro come sealed. In questo modo viene negato
l'aspetto virtuale del membro per qualsiasi ulteriore classe derivata. A questo scopo è necessario inserire la
parola chiave sealed prima della parola chiave override nella dichiarazione del membro di classe. Ad esempio:

public class D : C
{
public sealed override void DoWork() { }
}

Vedere anche
Guida per programmatori C#
Classi e struct
Ereditarietà
Metodi
Fields
Come definire proprietà astratte
Classi statiche e membri di classi statiche (Guida per
programmatori C#)
28/01/2021 • 10 minutes to read • Edit Online

Una classe statica corrisponde fondamentalmente a una classe non statica, ma c'è una differenza: di una classe
statica non è possibile creare un'istanza. In altre parole, non è possibile usare l'operatore new per creare una
variabile del tipo di classe. Poiché non esiste una variabile dell'istanza, si accede ai membri di una classe statica
tramite il nome stesso della classe. Se ad esempio si dispone di una classe statica denominata UtilityClass che
ha un metodo statico pubblico denominato MethodA , si chiama il metodo come illustrato nell'esempio seguente:

UtilityClass.MethodA();

Una classe statica può essere usata come un contenitore adatto per insiemi di metodi che funzionano solo sui
parametri di input e non devono ottenere o impostare campi di istanza interni. Nella libreria di classi .NET, ad
esempio, la System.Math classe statica contiene metodi che eseguono operazioni matematiche, senza alcun
requisito per archiviare o recuperare dati univoci per una particolare istanza della Math classe. In altre parole, si
applicano i membri della classe specificando il nome della classe e il nome del metodo, come illustrato
nell'esempio seguente.

double dub = -3.14;


Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:
// 3.14
// -4
// 3

Come nel caso di tutti i tipi di classe, le informazioni sul tipo per una classe statica vengono caricate dal runtime
.NET quando viene caricato il programma che fa riferimento alla classe. Il programma non può specificare
esattamente quando la classe verrà caricata. Tuttavia, è garantito che la classe sarà caricata, che i suoi campi
saranno inizializzati e che il costruttore statico sarà chiamato prima che il programma faccia riferimento per la
prima volta alla classe stessa. Un costruttore statico viene chiamato solo un volta e una classe statica rimane in
memoria per la durata del dominio dell'applicazione in cui risiede il programma.

NOTE
Per creare una classe non statica che consente la creazione di un'unica istanza di se stessa, vedere Implementing Singleton
in C# (Implementare Singleton in C#).

Nell'esempio riportato di seguito sono indicate le principali funzionalità delle classi statiche:
Contiene solo membri statici.
Non è possibile crearne istanze.
È sealed.
Non può contenere costruttori di istanze.
La creazione di una classe statica è pertanto analoga alla creazione di una classe che contiene solo membri
statici e un costruttore privato, che impedisce la creazione di istanze della classe. Una classe statica presenta un
indubbio vantaggio. Consente infatti al compilatore di verificare che non vengano aggiunti accidentalmente
membri di istanze e quindi di garantire che non vengano create istanze di questa classe.
Le classi statiche sono sealed e pertanto non possono essere ereditate. Possono ereditare solo dalla classe
Object. Le classi statiche non possono contenere un costruttore di istanza. Tuttavia, possono contenere un
costruttore statico. Le classi non statiche devono definire anche un costruttore statico se la classe contiene
membri statici che richiedono un'inizializzazione più complessa. Per altre informazioni, vedere Costruttori statici.

Esempio
Di seguito è riportato un esempio di una classe statica contenente due metodi che consentono di convertire i
valori relativi alla temperatura da gradi Celsius a gradi Fahrenheit e viceversa:

public static class TemperatureConverter


{
public static double CelsiusToFahrenheit(string temperatureCelsius)
{
// Convert argument to double for calculations.
double celsius = Double.Parse(temperatureCelsius);

// Convert Celsius to Fahrenheit.


double fahrenheit = (celsius * 9 / 5) + 32;

return fahrenheit;
}

public static double FahrenheitToCelsius(string temperatureFahrenheit)


{
// Convert argument to double for calculations.
double fahrenheit = Double.Parse(temperatureFahrenheit);

// Convert Fahrenheit to Celsius.


double celsius = (fahrenheit - 32) * 5 / 9;

return celsius;
}
}

class TestTemperatureConverter
{
static void Main()
{
Console.WriteLine("Please select the convertor direction");
Console.WriteLine("1. From Celsius to Fahrenheit.");
Console.WriteLine("2. From Fahrenheit to Celsius.");
Console.Write(":");

string selection = Console.ReadLine();


double F, C = 0;

switch (selection)
{
case "1":
Console.Write("Please enter the Celsius temperature: ");
F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
break;

case "2":
Console.Write("Please enter the Fahrenheit temperature: ");
C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());
Console.WriteLine("Temperature in Celsius: {0:F2}", C);
break;
break;

default:
Console.WriteLine("Please select a convertor.");
break;
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Example Output:
Please select the convertor direction
1. From Celsius to Fahrenheit.
2. From Fahrenheit to Celsius.
:2
Please enter the Fahrenheit temperature: 20
Temperature in Celsius: -6.67
Press any key to exit.
*/

Membri static
Una classe non statica può contenere metodi, campi, proprietà o eventi statici. È possibile chiamare il membro
statico di una classe anche quando non sono state create istanze della classe. Al membro statico si accede
sempre tramite il nome della classe, non tramite il nome dell'istanza. Di un membro statico esiste una sola copia,
indipendentemente dal numero di istanze della classe create. I metodi e le proprietà statici non possono
accedere a eventi e campi non statici nel tipo contenitore e non possono accedere a una variabile di istanza di un
oggetto a meno che non venga passato in modo esplicito in un parametro del metodo.
È più frequente dichiarare una classe non statica con alcuni membri statici, che dichiarare un'intera classe come
statica. Due utilizzi comuni di campi statici sono: tenere un conteggio del numero di oggetti di cui è stata creata
un'istanza o archiviare un valore che deve essere condiviso fra tutte le istanze.
I metodi statici possono essere sottoposti a overload ma non a override, perché appartengono alla classe e non
a qualsiasi istanza della classe.
Sebbene non sia possibile dichiarare un campo come static const , il campo const è essenzialmente statico nel
comportamento. Appartiene al tipo, non a istanze del tipo. È pertanto const possibile accedere ai campi
utilizzando la stessa ClassName.MemberName notazione utilizzata per i campi statici. Non è richiesta alcuna istanza
dell'oggetto.
C# non supporta variabili locali statiche, ovvero variabili dichiarate nell'ambito del metodo.
I membri delle classi statiche vengono dichiarati tramite la parola chiave static prima del tipo restituito, come
illustrato nell'esempio seguente:
public class Automobile
{
public static int NumberOfWheels = 4;

public static int SizeOfGasTank


{
get
{
return 15;
}
}

public static void Drive() { }

public static event EventType RunOutOfGas;

// Other non-static fields and properties...


}

I membri statici vengono inizializzati prima dell'accesso iniziale e prima dell'eventuale chiamata al costruttore
statico, se presente. Per accedere a un membro di una classe statica, usare il nome della classe anziché il nome di
una variabile per specificare la posizione del membro, come illustrato nell'esempio seguente:

Automobile.Drive();
int i = Automobile.NumberOfWheels;

Se la classe contiene campi statici, fornire un costruttore statico che li inizializzi al caricamento della classe.
Una chiamata a un metodo statico genera un'istruzione Call in Microsoft Intermediate Language (MSIL), mentre
una chiamata a un metodo di istanza genera un' callvirt istruzione, che verifica anche la presenza di
riferimenti a oggetti null. Tuttavia, nella maggior parte dei casi, la differenza di prestazioni tra i due non è
significativa.

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
static
Classi
class
Costruttori statici
Costruttori di istanza
Modificatori di accesso (Guida per programmatori
C#)
28/01/2021 • 8 minutes to read • Edit Online

Tutti i tipi e i membri dei tipi hanno un livello di accessibilità. Il livello di accessibilità controlla se possono essere
usati da altro codice nell'assembly o in altri assembly. Usare i modificatori di accesso seguenti per specificare
l'accessibilità di un tipo o di un membro quando lo si dichiara:
public: il tipo o il membro può accedere a qualsiasi altro codice nello stesso assembly o in un altro assembly
che vi fa riferimento.
private: il tipo o il membro è accessibile solo dal codice nello stesso oggetto class o struct .
protected: il tipo o il membro è accessibile solo dal codice nello stesso class o in un class derivato da tale
class .
Internal: il tipo o il membro è accessibile da qualsiasi codice nello stesso assembly, ma non da un altro
assembly.
interno protetto: il tipo o il membro è accessibile da qualsiasi codice nell'assembly in cui è dichiarato o
dall'interno di un oggetto derivato class in un altro assembly.
privato protetto: il tipo o il membro è accessibile solo all'interno dell'assembly dichiarante, dal codice nello
stesso class o in un tipo derivato da tale class .
L'esempio seguente illustra come specificare i modificatori di accesso in un tipo e in un membro:

public class Bicycle


{
public void Pedal() { }
}

Non tutti i modificatori di accesso sono validi per tutti i tipi o i membri in tutti i contesti. In alcuni casi,
l'accessibilità di un membro del tipo è vincolata dall'accessibilità del tipo che lo contiene.

Accessibilità di classi e struct


Le classi e gli struct dichiarati direttamente all'interno di uno spazio dei nomi (in altre parole, che non sono
annidati all'interno di altre classi o struct) possono essere public o internal . internal è il valore predefinito
se non è specificato alcun modificatore di accesso.
I membri struct, incluse le classi e gli struct annidati, possono essere dichiarati public , internal o private . I
membri della classe, incluse le classi e gli struct annidati, possono essere public ,, protected internal
protected , internal , private protected o private . I membri della classe e dello struct, incluse le classi e gli
struct annidati, hanno private accesso per impostazione predefinita. I tipi annidati privati non sono accessibili
dall'esterno del tipo che lo contiene.
Le classi derivate non possono avere maggiore accessibilità rispetto ai relativi tipi di base. Non è possibile
dichiarare una classe pubblica B che deriva da una classe interna A . Se consentito, avrebbe l'effetto di
rendere A pubblico, perché tutti i protected membri o internal di A sono accessibili dalla classe derivata.
È possibile abilitare altri assembly specifici per accedere ai tipi interni usando InternalsVisibleToAttribute . Per
ulteriori informazioni, vedere assembly Friend.
Accessibilità membri classe e struct
I membri di classe (inclusi le classi e gli struct annidati) possono essere dichiarati con uno qualsiasi dei sei tipi di
accesso. I membri struct non possono essere dichiarati come protected , protected internal o
private protected perché gli struct non supportano l'ereditarietà.

In genere, l'accessibilità di un membro non è maggiore dell'accessibilità del tipo che lo contiene. Tuttavia, un
public membro di una classe interna potrebbe essere accessibile dall'esterno dell'assembly se il membro
implementa metodi di interfaccia o esegue l'override di metodi virtuali definiti in una classe di base pubblica.
Il tipo di qualsiasi campo, proprietà o evento del membro deve essere accessibile almeno quanto il membro
stesso. Analogamente, il tipo restituito e i tipi di parametro di qualsiasi metodo, indicizzatore o delegato devono
essere accessibili almeno quanto il membro stesso. Ad esempio, non è possibile avere un public metodo M
che restituisca una classe a C meno che non C sia anche public . Analogamente, non è possibile avere una
protected proprietà di tipo A se A viene dichiarato come private .

Gli operatori definiti dall'utente devono essere sempre dichiarati come public e static . Per altre
informazioni, vedere Overload degli operatori.
I finalizzatori non possono avere modificatori di accessibilità.
Per impostare il livello di accesso per class un struct membro o, aggiungere la parola chiave appropriata alla
dichiarazione del membro, come illustrato nell'esempio seguente.

// public class:
public class Tricycle
{
// protected method:
protected void Pedal() { }

// private field:
private int wheels = 3;

// protected internal property:


protected internal int Wheels
{
get { return wheels; }
}
}

Altri tipi
Le interfacce dichiarate direttamente all'interno di uno spazio dei nomi possono essere public o internal e,
proprio come le classi e gli struct, l'accesso predefinito alle interfacce internal . I membri di interfaccia sono
public , per impostazione predefinita, perché lo scopo di un'interfaccia è quello di consentire ad altri tipi di
accedere a una classe o uno struct. Le dichiarazioni dei membri di interfaccia possono includere qualsiasi
modificatore di accesso. Questa operazione è particolarmente utile per i metodi statici per fornire
implementazioni comuni necessarie a tutti gli implementatori di una classe.
I membri dell'enumerazione sono sempre public e nessun modificatore di accesso può essere applicato.
I delegati si comportano come classi e struct. Per impostazione predefinita, gli utenti hanno internal accesso
quando vengono dichiarati direttamente all'interno di uno spazio dei nomi e private l'accesso viene annidato

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Classi e struct
Interfacce
privata
public
interno
protetto
protected internal
protetto privato
class
struct
interface
Campi (Guida per programmatori C#)
28/01/2021 • 6 minutes to read • Edit Online

Un campo è una variabile di qualsiasi tipo che viene dichiarata direttamente in una classe o struct. I campi sono
membri del rispettivo tipo contenitore.
Una classe o uno struct può avere campi di istanza, campi statici o entrambi. I campi di istanza sono specifici di
un'istanza di tipo. Se si ha una classe T con un campo di istanza F, è possibile creare due oggetti di tipo T e
modificare il valore di F in ciascun oggetto senza modificare il valore nell'altro oggetto. Al contrario, un campo
statico appartiene alla classe stessa ed è condiviso tra tutte le istanze della classe. È possibile accedere al campo
statico solo usando il nome della classe. Se si accede al campo statico tramite un nome di istanza, si ottiene un
errore in fase di compilazione CS0176 .
In genere è necessario usare i campi solo per le variabili che hanno accesso privato o protetto. I dati esposti dalla
classe al codice client devono essere forniti tramite Metodi, Proprietàe indicizzatori. Usando questi costrutti per
l'accesso indiretto ai campi interni, è possibile evitare valori di input non validi. Un campo privato che archivia i
dati esposti da una proprietà pubblica è chiamato archivio di backup o campo sottostante.
Di solito i campi archiviano dati che devono essere accessibili a più metodi della classe e devono essere
archiviati per un tempo maggiore rispetto alla durata di ogni singolo metodo. Ad esempio, una classe che
rappresenta una data di calendario potrebbe contenere tre campi interi: uno per il mese, uno per il giorno e uno
per l'anno. Le variabili che vengono usate solo all'interno dell'ambito di un singolo metodo devono essere
dichiarate come variabili locali all'interno del corpo del metodo stesso.
I campi vengono dichiarati nel blocco della classe, specificando il livello di accesso del campo, seguito dal tipo di
campo e poi dal nome del campo. Ad esempio:
public class CalendarEntry
{
// private field
private DateTime date;

// public field (Generally not recommended.)


public string day;

// Public property exposes date field safely.


public DateTime Date
{
get
{
return date;
}
set
{
// Set some reasonable boundaries for likely birth dates.
if (value.Year > 1900 && value.Year <= DateTime.Today.Year)
{
date = value;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
}

// Public method also exposes date field safely.


// Example call: birthday.SetDate("1975, 6, 30");
public void SetDate(string dateString)
{
DateTime dt = Convert.ToDateTime(dateString);

// Set some reasonable boundaries for likely birth dates.


if (dt.Year > 1900 && dt.Year <= DateTime.Today.Year)
{
date = dt;
}
else
{
throw new ArgumentOutOfRangeException();
}
}

public TimeSpan GetTimeSpan(string dateString)


{
DateTime dt = Convert.ToDateTime(dateString);

if (dt.Ticks < date.Ticks)


{
return date - dt;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
}

Per accedere a un campo in un oggetto, aggiungere un punto dopo il nome dell'oggetto, seguito dal nome del
campo, come in objectname.fieldname . Ad esempio:
CalendarEntry birthday = new CalendarEntry();
birthday.day = "Saturday";

È possibile assegnare a un campo un valore iniziale usando l'operatore di assegnazione quando il campo viene
dichiarato. Per assegnare automaticamente il campo day a "Monday" , ad esempio, è necessario dichiarare day
come nell'esempio seguente:

public class CalendarDateWithInitialization


{
public string day = "Monday";
//...
}

I campi vengono inizializzati immediatamente prima della chiamata del costruttore per l'istanza dell'oggetto. Se
il costruttore assegna il valore di un campo, sovrascriverà qualsiasi valore assegnato nella dichiarazione del
campo. Per altre informazioni, vedere Uso dei costruttori.

NOTE
Un inizializzatore di campo non può fare riferimento ad altri campi di istanza.

I campi possono essere contrassegnati come public, private, protected, Internal, protected internalo private
protected. Questi modificatori di accesso definiscono in che modo gli utenti della classe possono accedere ai
campi. Per altre informazioni, vedere Modificatori di accesso.
È possibile facoltativamente dichiarare un campo come static. Questo rende il campo disponibile per i chiamanti
in qualsiasi momento, anche se non esiste alcuna istanza della classe. Per altre informazioni, vedere classi
statiche e membri di classi statiche.
È possibile dichiarare un campo come readonly. A un campo di sola lettura può essere assegnato un valore solo
durante l'inizializzazione o in un costruttore. Un campo static readonly è molto simile a una costante, a parte il
fatto che il compilatore C# non ha accesso al valore di un campo statico di sola lettura in fase di compilazione,
ma solo in fase di esecuzione. Per altre informazioni, vedere Costanti.

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi e struct
Utilizzo di costruttori
Ereditarietà
Modificatori di accesso
Classi e membri delle classi astratte e sealed
Costanti (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

Le costanti sono valori non modificabili, che sono noti nella fase di compilazione e non cambiano per la durata
del programma. Le costanti vengono dichiarate con il modificatore const. Solo i tipi incorporati C# (esclusi
System.Object ) possono essere dichiarati come const . I tipi definiti dall'utente, incluse le classi, gli struct e le
matrici, non possono essere const . Usare il modificatore readonly per creare una classe, una matrice o uno
struct che viene inizializzato una sola volta in fase di runtime (ad esempio in un costruttore) e successivamente
non può più essere modificato.
C# non supporta metodi, proprietà o eventi const .
Il tipo enum consente di definire costanti denominate per i tipi incorporati interi (ad esempio int , uint , long
e così via). Per altre informazioni, vedere enum.
Le costanti devono essere inizializzate come vengono dichiarate. Ad esempio:

class Calendar1
{
public const int Months = 12;
}

In questo esempio la costante Months è sempre 12 e non può essere modificata neanche dalla stessa classe. In
realtà, quando il compilatore incontra un identificatore costante nel codice sorgente C# (ad esempio Months ), lo
sostituisce direttamente con il relativo valore letterale nel codice Intermediate Language (IL) che produce. Poiché
non esiste alcun indirizzo di variabile associato a una costante in fase di esecuzione, i campi const non possono
essere passati per riferimento e non possono sostituire un l-value in un'espressione.

NOTE
Prestare attenzione quando si creano riferimenti a valori costanti definiti in un altro codice, ad esempio in file DLL. Se una
nuova versione della DLL definisce un nuovo valore per la costante, il programma mantiene il vecchio valore letterale
finché non viene ricompilato per la nuova versione.

È possibile dichiarare contemporaneamente più costanti dello stesso tipo, ad esempio:

class Calendar2
{
public const int Months = 12, Weeks = 52, Days = 365;
}

L'espressione usata per inizializzare una costante può fare riferimento a un'altra costante, a condizione che crei
un riferimento circolare. Ad esempio:
class Calendar3
{
public const int Months = 12;
public const int Weeks = 52;
public const int Days = 365;

public const double DaysPerWeek = (double) Days / (double) Weeks;


public const double DaysPerMonth = (double) Days / (double) Months;
}

Le costanti possono essere contrassegnate come public, private, protected, internal, protected internal o private
protected. Questi modificatori definiscono l'accesso alla costante per gli utenti della classe. Per altre
informazioni, vedere Modificatori di accesso.
L’accesso alle costanti avviene come se fossero campi statici, perché il valore della costante è lo stesso per tutte
le istanze del tipo. Per dichiararle non viene usata la parola chiave static . Per accedere alla costante, le
espressioni non incluse nella classe che la definisce devono usare il nome della classe seguito da un punto e dal
nome della costante stessa. Ad esempio:

int birthstones = Calendar.Months;

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi e struct
Proprietà
Tipi
ReadOnly
Immutability in C# Part One: Kinds of Immutability (Immutabilità in C# - Parte 1: tipi di immutabilità)
Come definire proprietà astratte (Guida per
programmatori C#)
28/01/2021 • 3 minutes to read • Edit Online

L'esempio seguente mostra come definire proprietà di tipo abstract. La dichiarazione di una proprietà astratta
non fornisce un'implementazione delle funzioni di accesso della proprietà. Dichiara che la classe supporta le
proprietà, ma l'implementazione delle funzioni di accesso viene demandata alle classi derivate. L'esempio
seguente illustra come implementare le proprietà astratte ereditate da una classe di base.
L'esempio include tre file, ognuno dei quali viene compilato singolarmente e al cui assembly risultante viene
fatto riferimento nella compilazione successiva:
abstractshape.cs: classe Shape che contiene una proprietà Area astratta.
shapes.cs: sottoclassi della classe Shape .
shapetest.cs: programma di test per la visualizzazione delle aree di alcuni oggetti derivati da Shape .
Per compilare l'esempio, usare il comando seguente:
csc abstractshape.cs shapes.cs shapetest.cs

Verrà creato il file eseguibile shapetest.exe.

Esempio
Questo file dichiara la classe Shape che contiene la proprietà Area del tipo double .
// compile with: csc -target:library abstractshape.cs
public abstract class Shape
{
private string name;

public Shape(string s)
{
// calling the set accessor of the Id property.
Id = s;
}

public string Id
{
get
{
return name;
}

set
{
name = value;
}
}

// Area is a read-only property - only a get accessor is needed:


public abstract double Area
{
get;
}

public override string ToString()


{
return $"{Id} Area = {Area:F2}";
}
}

I modificatori della proprietà vengono inseriti nella dichiarazione della proprietà stessa. ad esempio:

public abstract double Area

Quando si dichiara una proprietà astratta, come Area in questo esempio, è sufficiente indicare le
funzioni di accesso disponibili per la proprietà, senza implementarle. In questo esempio è disponibile solo
una funzione di accesso get, quindi la proprietà è di sola lettura.

Esempio
Il codice seguente mostra tre sottoclassi di Shape e il modo in cui eseguono l'override della proprietà Area per
fornire la propria implementazione.
// compile with: csc -target:library -reference:abstractshape.dll shapes.cs
public class Square : Shape
{
private int side;

public Square(int side, string id)


: base(id)
{
this.side = side;
}

public override double Area


{
get
{
// Given the side, return the area of a square:
return side * side;
}
}
}

public class Circle : Shape


{
private int radius;

public Circle(int radius, string id)


: base(id)
{
this.radius = radius;
}

public override double Area


{
get
{
// Given the radius, return the area of a circle:
return radius * radius * System.Math.PI;
}
}
}

public class Rectangle : Shape


{
private int width;
private int height;

public Rectangle(int width, int height, string id)


: base(id)
{
this.width = width;
this.height = height;
}

public override double Area


{
get
{
// Given the width and height, return the area of a rectangle:
return width * height;
}
}
}

Esempio
Il codice seguente mostra un programma di test che crea diversi oggetti derivati da Shape e stampa le relative
aree.

// compile with: csc -reference:abstractshape.dll;shapes.dll shapetest.cs


class TestClass
{
static void Main()
{
Shape[] shapes =
{
new Square(5, "Square #1"),
new Circle(3, "Circle #1"),
new Rectangle( 4, 5, "Rectangle #1")
};

System.Console.WriteLine("Shapes Collection");
foreach (Shape s in shapes)
{
System.Console.WriteLine(s);
}
}
}
/* Output:
Shapes Collection
Square #1 Area = 25.00
Circle #1 Area = 28.27
Rectangle #1 Area = 20.00
*/

Vedi anche
Guida per programmatori C#
Classi e struct
Classi e membri delle classi astratte e sealed
Proprietà
Come definire le costanti in C#
28/01/2021 • 2 minutes to read • Edit Online

Le costanti sono campi i cui valori vengono impostati in fase di compilazione e non possono mai essere
modificati. Usare le costanti per specificare nomi significativi invece di valori letterali numerici ("numeri chiave")
per valori particolari.

NOTE
In C# la direttiva per il preprocessore #define non può essere usata per definire le costanti nel modo adottato in genere in
C e C++.

Per definire valori costanti di tipi integrali ( int , byte e così via) usare un tipo enumerato. Per altre
informazioni, vedere enum.
Per definire costanti non integrali, è possibile raggrupparle in una singola classe statica denominata Constants .
A questo scopo sarà necessario che tutti i riferimenti alle costanti siano preceduti dal nome della classe, come
illustrato nell'esempio seguente.

Esempio
using System;

static class Constants


{
public const double Pi = 3.14159;
public const int SpeedOfLight = 300000; // km per sec.
}

class Program
{
static void Main()
{
double radius = 5.3;
double area = Constants.Pi * (radius * radius);
int secsFromSun = 149476000 / Constants.SpeedOfLight; // in km
Console.WriteLine(secsFromSun);
}
}

L'uso del qualificatore del nome della classe consente di fare in modo che tutti gli utenti che usano la costante
comprendano che si tratta di una costante e che non può essere modificata.

Vedi anche
Classi e struct
Proprietà (Guida per programmatori C#)
02/11/2020 • 8 minutes to read • Edit Online

Una proprietà è un membro che fornisce un meccanismo flessibile per leggere, scrivere o calcolare il valore di
un campo privato. Le proprietà possono essere usate come se fossero membri dati pubblici, ma sono in realtà
metodi speciali denominati funzioni di accesso. Questo consente di accedere facilmente ai dati e di alzare di
livello la sicurezza e la flessibilità dei metodi.

Panoramica delle proprietà


Le proprietà consentono a una classe di esporre un modo pubblico per ottenere e impostare i valori,
nascondendo però il codice di implementazione o di verifica.
Una funzione di accesso della proprietà get viene usata per restituire il valore della proprietà, mentre una
funzione di accesso della proprietà set viene usata per assegnare un nuovo valore. Queste funzioni di
accesso possono avere diversi livelli di accesso. Per altre informazioni, vedere Limitazione
dell'accessibilità delle funzioni di accesso.
La parola chiave value viene usata per definire il valore che deve essere assegnato dalla funzione di
accesso set .
Le proprietà possono essere di lettura/scrittura con entrambe le funzione di accesso get e set , di sola
lettura con la funzione di accesso get e senza la funzione di accesso set o di sola scrittura con la
funzione di accesso set e senza la funzione di accesso get . Le proprietà di sola scrittura sono rare e
vengono in genere usate per limitare l'accesso ai dati sensibili.
Le proprietà semplici che non richiedono alcun codice di funzione di accesso personalizzata possono
essere implementate come definizioni del corpo dell'espressione o come proprietà implementate
automaticamente.

Proprietà con campi sottostanti


Un modello di base per l'implementazione di una proprietà prevede l'uso di un campo sottostante privato per
l'impostazione e il recupero del valore della proprietà. La funzione di accesso get restituisce il valore del campo
privato e la funzione di accesso set può eseguire una convalida dei dati prima di assegnare un valore al campo
privato. Entrambe le funzioni di accesso possono anche eseguire una conversione o un calcolo nei dati prima
che vengano archiviati o restituiti.
L'esempio seguente illustra il modello. Nell'esempio la classe TimePeriod rappresenta un intervallo di tempo.
Internamente la classe archivia l'intervallo di tempo in secondi in un campo privato denominato _seconds . Una
proprietà di lettura/scrittura denominata Hours consente al cliente di specificare l'intervallo di tempo in ore.
Entrambe le funzioni di accesso get e set eseguono la conversione necessaria tra ore e secondi. Inoltre, la
funzione di accesso set convalida i dati e genera ArgumentOutOfRangeException se il numero di ore non è
valido.
using System;

class TimePeriod
{
private double _seconds;

public double Hours


{
get { return _seconds / 3600; }
set {
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(
$"{nameof(value)} must be between 0 and 24.");

_seconds = value * 3600;


}
}
}

class Program
{
static void Main()
{
TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;

// Retrieving the property causes the 'get' accessor to be called.


Console.WriteLine($"Time in hours: {t.Hours}");
}
}
// The example displays the following output:
// Time in hours: 24

Definizioni del corpo dell'espressione


Le funzioni di accesso delle proprietà sono spesso costituite da istruzioni a riga singola che assegnano o
restituiscono il risultato di un'espressione. È possibile implementare queste proprietà come membri con corpo
di espressione. Le definizioni del corpo dell'espressione sono costituite dal simbolo => seguito dall'espressione
per l'assegnazione o il recupero dalla proprietà.
A partire da C# 6, le proprietà di sola lettura possono implementare la funzione di accesso get come membro
con corpo di espressione. In questo caso non viene usata la parola chiave della funzione di accesso get né la
parola chiave return . L'esempio seguente implementa la proprietà Name di sola lettura come membro con
corpo di espressione.
using System;

public class Person


{
private string _firstName;
private string _lastName;

public Person(string first, string last)


{
_firstName = first;
_lastName = last;
}

public string Name => $"{_firstName} {_lastName}";


}

public class Example


{
public static void Main()
{
var person = new Person("Magnus", "Hedlund");
Console.WriteLine(person.Name);
}
}
// The example displays the following output:
// Magnus Hedlund

A partire da C# 7.0, entrambe le funzioni di accesso get e set possono essere implementate come membri
con corpo di espressione. In questo caso, è necessario che siano presenti le parole chiave get e set . L'esempio
seguente illustra l'uso di definizioni del corpo dell'espressione per entrambe le funzioni di accesso. Si noti che la
parola chiave return non viene usata con la funzione di accesso get .
using System;

public class SaleItem


{
string _name;
decimal _cost;

public SaleItem(string name, decimal cost)


{
_name = name;
_cost = cost;
}

public string Name


{
get => _name;
set => _name = value;
}

public decimal Price


{
get => _cost;
set => _cost = value;
}
}

class Program
{
static void Main(string[] args)
{
var item = new SaleItem("Shoes", 19.95m);
Console.WriteLine($"{item.Name}: sells for {item.Price:C2}");
}
}
// The example displays output like the following:
// Shoes: sells for $19.95

Proprietà implementate automaticamente


In alcuni casi, le funzioni di accesso get e set delle proprietà si limitano ad assegnare o a recuperare un
valore da un campo sottostante senza includere alcuna logica aggiuntiva. Usando le proprietà implementate
automaticamente è possibile semplificare il codice e fare in modo che il compilatore C# specifichi
automaticamente in modo trasparente il campo sottostante.
Se una proprietà ha entrambe le funzioni di accesso get e set , entrambe le funzioni devono essere
implementate automaticamente. È possibile definire una proprietà implementata automaticamente usando le
parole chiave get e set senza specificare alcuna implementazione. L'esempio seguente ripete l'esempio
precedente, ad eccezione del fatto che Name e Price sono proprietà implementate automaticamente. Si noti
che l'esempio rimuove anche il costruttore con parametri in modo che gli oggetti SaleItem vengano ora
inizializzati con una chiamata al costruttore senza parametri e un inizializzatore di oggetto.
using System;

public class SaleItem


{
public string Name
{ get; set; }

public decimal Price


{ get; set; }
}

class Program
{
static void Main(string[] args)
{
var item = new SaleItem{ Name = "Shoes", Price = 19.95m };
Console.WriteLine($"{item.Name}: sells for {item.Price:C2}");
}
}
// The example displays output like the following:
// Shoes: sells for $19.95

Sezioni correlate
Utilizzo delle proprietà
Proprietà dell'interfaccia
Confronto tra proprietà e indicizzatori
Limitazione dell'accessibilità delle funzioni di accesso
Proprietà implementate automaticamente

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Utilizzo delle proprietà
Indicizzatori
Parola chiave get
Parola chiave set
Utilizzo delle proprietà (Guida per programmatori
C#)
02/11/2020 • 14 minutes to read • Edit Online

Le proprietà combinano gli aspetti sia dei campi che dei metodi. Per l'utente di un oggetto, una proprietà si
presenta come un campo: l'accesso alla proprietà richiede la stessa sintassi. Per il responsabile
dell'implementazione di una classe, una proprietà è costituita da uno o due blocchi di codice, che rappresentano
una funzione di accesso get e/o una funzione di accesso set. Il blocco di codice per la funzione di accesso get
viene eseguito al momento della lettura della proprietà; il blocco di codice per la funzione di accesso set viene
eseguito quando viene assegnato un nuovo valore alla proprietà. Una proprietà senza una funzione di accesso
set viene considerata di sola lettura. Una proprietà senza una funzione di accesso get viene considerata di
sola scrittura. Una proprietà con entrambe le funzioni di accesso è di lettura/scrittura.
A differenza dei campi, le proprietà non sono classificate come variabili. Non è pertanto possibile passare una
proprietà come un parametro ref o out.
Le proprietà possono essere usate per diversi scopi: possono convalidare i dati prima di consentire una
modifica, esporre in modo trasparente i dati in una classe in cui i dati vengono effettivamente recuperati da
un'altra origine come un database oppure eseguire un'azione quando i dati vengono modificati, ad esempio
generare un evento o modificare il valore di altri campi.
Le proprietà sono dichiarate nel blocco della classe specificando il livello di accesso del campo, seguito dal tipo
della proprietà, seguito dal nome della proprietà, seguito da un blocco di codice che dichiara una funzione di
accesso get e/o una funzione di accesso set . ad esempio:

public class Date


{
private int _month = 7; // Backing store

public int Month


{
get => _month;
set
{
if ((value > 0) && (value < 13))
{
_month = value;
}
}
}
}

In questo esempio, Month è dichiarato come una proprietà in modo che la funzione di accesso set possa
verificare che il valore Month sia compreso tra 1 e 12. La proprietà Month usa un campo privato per tenere
traccia del valore effettivo. La posizione reale dei dati di una proprietà è spesso denominata "archivio di backup"
della proprietà. Generalmente, le proprietà usano campi privati come archivio di backup. Il campo viene
contrassegnato come privato per assicurare che possa essere modificato solo chiamando la proprietà. Per altre
informazioni sulle restrizioni di accesso pubblico e privato, vedere Modificatori di accesso.
Le proprietà implementate automaticamente forniscono una sintassi semplificata per le dichiarazioni di
proprietà semplici. Per altre informazioni, vedere Proprietà implementate automaticamente.
Funzione di accesso get
Il corpo della funzione di accesso get è simile a quello di un metodo. Deve restituire un valore del tipo di
proprietà. L'esecuzione della funzione di accesso get è equivalente alla lettura del valore del campo. Ad
esempio, quando si restituisce la variabile privata dalla funzione di accesso get e le ottimizzazioni sono
abilitate, la chiamata al metodo della funzione di accesso get viene impostata come inline dal compilatore,
pertanto non c'è alcun overhead di chiamata al metodo. Tuttavia, un metodo della funzione di accesso get
virtuale non può essere impostato come inline perché il compilatore non può stabilire in fase di compilazione
quale metodo potrebbe essere effettivamente chiamato in fase di esecuzione. Di seguito è riportata una
funzione di accesso get che restituisce il valore di un campo privato _name :

class Person
{
private string _name; // the name field
public string Name => _name; // the Name property
}

Quando si fa riferimento alla proprietà, tranne che come destinazione di un'assegnazione, viene chiamata la
funzione di accesso get per leggere il valore della proprietà. ad esempio:

Person person = new Person();


//...

System.Console.Write(person.Name); // the get accessor is invoked here

La funzione di accesso get deve terminare con un'istruzione return o throw e il controllo non può uscire dal
corpo della funzione di accesso.
È preferibile evitare di modificare lo stato dell'oggetto usando la funzione di accesso get . La seguente funzione
di accesso, ad esempio, produce l'effetto collaterale di cambiare lo stato dell'oggetto ogni volta che si accede al
campo _number .

private int _number;


public int Number => _number++; // Don't do this

La funzione di accesso get può essere usata per restituire il valore del campo o per calcolarlo e restituirlo. ad
esempio:

class Employee
{
private string _name;
public string Name => _name != null ? _name : "NA";
}

Nel segmento di codice precedente, se non si assegna un valore alla Name proprietà, restituirà il valore NA .

Funzione di accesso set


La funzione di accesso set è simile a un metodo il cui tipo restituito è void. Usa un parametro implicito
denominato value , il cui tipo è il tipo della proprietà. Nell'esempio seguente, viene aggiunta una funzione di
accesso set alla proprietà Name :
class Person
{
private string _name; // the name field
public string Name // the Name property
{
get => _name;
set => _name = value;
}
}

Quando si assegna un valore alla proprietà, viene richiamata la funzione di accesso set tramite un argomento
che fornisce il nuovo valore. ad esempio:

Person person = new Person();


person.Name = "Joe"; // the set accessor is invoked here

System.Console.Write(person.Name); // the get accessor is invoked here

È un errore usare il nome del parametro implicito, value , per una dichiarazione di variabile locale in una
funzione di accesso set .

Commenti
Le proprietà possono essere contrassegnate come public , private , protected , internal , protected internal
o private protected . Questi modificatori di accesso definiscono in che modo gli utenti della classe possono
accedere alla proprietà. Le funzioni di accesso get e set per la stessa proprietà possono avere modificatori di
accesso diversi. Ad esempio, get potrebbe essere public per consentire l'accesso in sola lettura dall'esterno
del tipo e set potrebbe essere private o protected . Per altre informazioni, vedere Modificatori di accesso.
Una proprietà può essere dichiarata come proprietà statica tramite la parola chiave static . Questo rende la
proprietà disponibile per i chiamanti in qualsiasi momento, anche se non esiste alcuna istanza della classe. Per
altre informazioni, vedere classi statiche e membri di classi statiche.
Una proprietà può essere contrassegnata come virtuale mediante la parola chiave virtual. Ciò consente alle
classi derivate di eseguire l'override del comportamento della proprietà tramite la parola chiave override. Per
altre informazioni su queste opzioni, vedere Ereditarietà.
Una proprietà che esegue l'override di una proprietà virtuale può anche essere contrassegnata come sealed, in
modo che non risulti più virtuale per le classi derivate. Infine, una proprietà può essere dichiarata astratta. Ciò
significa che non vi è alcuna implementazione nella classe e le classi derivate devono scrivere la propria
implementazione. Per altre informazioni su queste opzioni, vedere Classi e membri delle classi astratte e sealed.

NOTE
È un errore usare un modificatore virtual, abstract o override in una funzione di accesso di una proprietà statica.

Esempio
Questo esempio illustra le proprietà di istanza, statiche e di sola lettura. Accetta il nome del dipendente dalla
tastiera, incrementa NumberOfEmployees di 1 e visualizza il nome e il numero del dipendente.
public class Employee
{
public static int NumberOfEmployees;
private static int _counter;
private string _name;

// A read-write instance property:


public string Name
{
get => _name;
set => _name = value;
}

// A read-only static property:


public static int Counter => _counter;

// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}

class TestEmployee
{
static void Main()
{
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee();
e1.Name = "Claude Vige";

System.Console.WriteLine("Employee number: {0}", Employee.Counter);


System.Console.WriteLine("Employee name: {0}", e1.Name);
}
}
/* Output:
Employee number: 108
Employee name: Claude Vige
*/

Esempio
Questo esempio illustra come accedere a una proprietà in una classe di base nascosta da un'altra proprietà con
lo stesso nome in una classe derivata:
public class Employee
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}

public class Manager : Employee


{
private string _name;

// Notice the use of the new modifier:


public new string Name
{
get => _name;
set => _name = value + ", Manager";
}
}

class TestHiding
{
static void Main()
{
Manager m1 = new Manager();

// Derived class property.


m1.Name = "John";

// Base class property.


((Employee)m1).Name = "Mary";

System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);


System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
}
}
/* Output:
Name in the derived class is: John, Manager
Name in the base class is: Mary
*/

Di seguito sono riportati gli aspetti più importanti relativi all'esempio precedente:
La proprietà Name nella classe derivata nasconde la proprietà Name nella classe di base. In tal caso, viene
usato il modificatore new nella dichiarazione della proprietà nella classe derivata:

public new string Name

Il cast (Employee) viene usato per accedere alla proprietà nascosta nella classe di base:

((Employee)m1).Name = "Mary";

Per altre informazioni su come nascondere i membri, vedere Modificatore new.

Esempio
In questo esempio, due classi, Cube e Square , implementano una classe astratta, Shape , ed eseguono
l'override della proprietà astratta Area . Si noti l'uso del modificatore override nelle proprietà. Il programma
accetta il lato come input e calcola le aree per il quadrato e il cubo. Inoltre, accetta l'area come input e calcola il
lato corrispondente per il quadrato e il cubo.

abstract class Shape


{
public abstract double Area
{
get;
set;
}
}

class Square : Shape


{
public double side;

//constructor
public Square(double s) => side = s;

public override double Area


{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}

class Cube : Shape


{
public double side;

//constructor
public Cube(double s) => side = s;

public override double Area


{
get => 6 * side * side;
set => side = System.Math.Sqrt(value / 6);
}
}

class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());

// Compute the areas:


Square s = new Square(side);
Cube c = new Cube(side);

// Display the results:


System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
System.Console.WriteLine();

// Input the area:


System.Console.Write("Enter the area: ");
double area = double.Parse(System.Console.ReadLine());

// Compute the sides:


s.Area = area;
c.Area = area;

// Display the results:


System.Console.WriteLine("Side of the square = {0:F2}", s.side);
System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
}
}
}
/* Example Output:
Enter the side: 4
Area of the square = 16.00
Area of the cube = 96.00

Enter the area: 24


Side of the square = 4.90
Side of the cube = 2.00
*/

Vedi anche
Guida per programmatori C#
Proprietà
Proprietà dell'interfaccia
Proprietà implementate automaticamente
Proprietà dell'interfaccia (Guida per programmatori
C#)
02/11/2020 • 3 minutes to read • Edit Online

Le proprietà possono essere dichiarate su una interfaccia. Nell'esempio seguente viene dichiarata una funzione
di accesso alla proprietà di interfaccia:

public interface ISampleInterface


{
// Property declaration:
string Name
{
get;
set;
}
}

Le proprietà dell'interfaccia in genere non dispongono di un corpo. Le funzioni di accesso indicano se la


proprietà è di lettura/scrittura, di sola lettura o di sola scrittura. Diversamente dalle classi e dagli struct, la
dichiarazione delle funzioni di accesso senza un corpo non dichiara una proprietà implementata
automaticamente. A partire da C# 8,0, un'interfaccia può definire un'implementazione predefinita per i membri,
incluse le proprietà. La definizione di un'implementazione predefinita per una proprietà in un'interfaccia è rara
poiché le interfacce potrebbero non definire campi dati dell'istanza.

Esempio
Nell'esempio seguente l'interfaccia IEmployee include una proprietà in lettura-scrittura, Name , e una proprietà
in sola lettura, Counter . La classe Employee implementa l'interfaccia IEmployee e usa le due proprietà. Il
programma legge il nome di un nuovo dipendente e il numero corrente di dipendenti e quindi visualizza il
nome del dipendente e il relativo numero calcolato.
È possibile usare il nome completo della proprietà, che fa riferimento all'interfaccia in cui il membro è dichiarato.
ad esempio:

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}

Nell'esempio precedente viene illustrata l' implementazione esplicita dell'interfaccia. Se la classe Employee
implementa, ad esempio, due interfacce, ICitizen e IEmployee , ed entrambe le interfacce includono la
proprietà Name , sarà necessaria l'implementazione esplicita del membro dell'interfaccia. In altre parole, la
dichiarazione di proprietà seguente:

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
implementa la proprietà Name nell'interfaccia IEmployee , mentre la dichiarazione seguente:

string ICitizen.Name
{
get { return "Citizen Name"; }
set { }
}

implementa la proprietà Name nell'interfaccia ICitizen .

interface IEmployee
{
string Name
{
get;
set;
}

int Counter
{
get;
}
}

public class Employee : IEmployee


{
public static int numberOfEmployees;

private string _name;


public string Name // read-write instance property
{
get => _name;
set => _name = value;
}

private int _counter;


public int Counter // read-only instance property
{
get => _counter;
}

// constructor
public Employee() => _counter = ++numberOfEmployees;
}

System.Console.Write("Enter number of employees: ");


Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());

Employee e1 = new Employee();


System.Console.Write("Enter the name of the new employee: ");
e1.Name = System.Console.ReadLine();

System.Console.WriteLine("The employee information:");


System.Console.WriteLine("Employee number: {0}", e1.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);

210 Hazem Abolrous

Output di esempio
Enter number of employees: 210
Enter the name of the new employee: Hazem Abolrous
The employee information:
Employee number: 211
Employee name: Hazem Abolrous

Vedi anche
Guida per programmatori C#
Proprietà
Utilizzo delle proprietà
Confronto tra proprietà e indicizzatori
Indicizzatori
Interfacce
Limitazione dell'accessibilità delle funzioni di
accesso (Guida per programmatori C#)
02/11/2020 • 7 minutes to read • Edit Online

Le parti get e set di una proprietà o un indicizzatore sono denominate funzioni di accesso. Per impostazione
predefinita, queste funzioni di accesso hanno la stessa visibilità o livello di accesso della proprietà o
dell'indicizzatore a cui appartengono. Per altre informazioni, vedere Livelli di accessibilità. Tuttavia, talvolta è
utile limitare l'accesso a una di queste funzioni di accesso. In genere, ciò comporta la limitazione
dell'accessibilità della funzione di accesso set , mantenendo la funzione di accesso get accessibile
pubblicamente. Ad esempio:

private string _name = "Hello";

public string Name


{
get
{
return _name;
}
protected set
{
_name = value;
}
}

In questo esempio, una proprietà denominata Name definisce una funzione di accesso get e set . La funzione
di accesso get riceve il livello di accessibilità della proprietà stessa, in questo caso public , mentre la funzione
di accesso set è limitata in modo esplicito applicando il modificatore di accesso protected alla funzione di
accesso stessa.

Restrizioni dei modificatori di accesso per le funzioni di accesso


L'uso dei modificatori delle funzioni di accesso nelle proprietà o negli indicizzatori è soggetto a queste
condizioni:
È possibile usare i modificatori delle funzioni di accesso su un'interfaccia o su un'implementazione
esplicita di un membro interface.
È possibile usare i modificatori delle funzioni di accesso solo se la proprietà o l'indicizzatore ha entrambe
le funzioni di accesso set e get . In questo caso, il modificatore è consentito solo per una delle due
funzioni di accesso.
Se la proprietà o l'indicizzatore ha un modificatore override, il modificatore della funzione di accesso
deve corrispondere alla funzione di accesso della funzione di accesso sottoposta a override, se presente.
Il livello di accessibilità nella funzione di accesso deve essere più restrittivo del livello di accessibilità nella
proprietà o nell'indicizzatore stesso.

Modificatori di accesso per l'override di funzioni di accesso


Quando si esegue l'override di una proprietà o un indicizzatore, le funzioni di accesso sottoposte a override
devono essere accessibili al codice di override. Inoltre, l'accessibilità della proprietà/indicizzatore e delle relative
funzioni di accesso deve corrispondere alla proprietà/indicizzatore e alle funzioni di accesso sottoposti a
override corrispondenti. Ad esempio:

public class Parent


{
public virtual int TestProperty
{
// Notice the accessor accessibility level.
protected set { }

// No access modifier is used here.


get { return 0; }
}
}
public class Kid : Parent
{
public override int TestProperty
{
// Use the same accessibility level as in the overridden accessor.
protected set { }

// Cannot use access modifier here.


get { return 0; }
}
}

Implementazione di interfacce
Quando si usa una funzione di accesso per implementare un'interfaccia, la funzione di accesso potrebbe non
disporre di un modificatore di accesso. Se tuttavia si implementa l'interfaccia usando una sola funzione di
accesso, ad esempio get , l'altra funzione di accesso può avere un modificatore di accesso, come nell'esempio
seguente:

public interface ISomeInterface


{
int TestProperty
{
// No access modifier allowed here
// because this is an interface.
get;
}
}

public class TestClass : ISomeInterface


{
public int TestProperty
{
// Cannot use access modifier here because
// this is an interface implementation.
get { return 10; }

// Interface property does not have set accessor,


// so access modifier is allowed.
protected set { }
}
}

Dominio di accessibilità della funzione di accesso


Se si usa un modificatore di accesso nella funzione di accesso, il dominio di accessibilità della funzione di
accesso è determinato da questo modificatore.
Se non è stato usato un modificatore di accesso nella funzione di accesso, il dominio di accessibilità della
funzione di accesso è determinato dal livello di accessibilità della proprietà o dell'indicizzatore.

Esempio
L'esempio seguente contiene tre classi, BaseClass , DerivedClass e MainClass . Sono disponibili due proprietà
per BaseClass , Name e Id in entrambe le classi. L'esempio illustra come la proprietà Id in DerivedClass può
essere nascosta dalla proprietà Id in BaseClass quando si usa un modificatore di accesso restrittivo, ad
esempio protected o private. Pertanto, quando si assegnano valori a questa proprietà, viene invece chiamata la
proprietà nella classe BaseClass . La sostituzione del modificatore di accesso con public renderà la proprietà
accessibile.
L'esempio illustra anche che un modificatore di accesso restrittivo, quale private o protected , nella funzione
di accesso set della proprietà Name in DerivedClass impedisce l'accesso alla funzione di accesso e genera un
errore quando si esegue l'assegnazione.

public class BaseClass


{
private string _name = "Name-BaseClass";
private string _id = "ID-BaseClass";

public string Name


{
get { return _name; }
set { }
}

public string Id
{
get { return _id; }
set { }
}
}

public class DerivedClass : BaseClass


{
private string _name = "Name-DerivedClass";
private string _id = "ID-DerivedClass";

new public string Name


{
get
{
return _name;
}

// Using "protected" would make the set accessor not accessible.


set
{
_name = value;
}
}

// Using private on the following property hides it in the Main Class.


// Any assignment to the property will use Id in BaseClass.
new private string Id
{
get
{
return _id;
}
set
{
_id = value;
_id = value;
}
}
}

class MainClass
{
static void Main()
{
BaseClass b1 = new BaseClass();
DerivedClass d1 = new DerivedClass();

b1.Name = "Mary";
d1.Name = "John";

b1.Id = "Mary123";
d1.Id = "John123"; // The BaseClass.Id property is called.

System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);


System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
Base: Name-BaseClass, ID-BaseClass
Derived: John, ID-BaseClass
*/

Commenti
Si noti che sostituendo la dichiarazione new private string Id con new public string Id , si otterrà l'output:
Name and ID in the base class: Name-BaseClass, ID-BaseClass

Name and ID in the derived class: John, John123

Vedere anche
Guida per programmatori C#
Proprietà
Indicizzatori
Modificatori di accesso
Come dichiarare e usare le proprietà di
lettura/scrittura (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

Le proprietà offrono i vantaggi dei membri dati pubblici senza i rischi associati all'accesso non protetto, non
controllato e non verificato ai dati di un oggetto. Ciò si ottiene tramite le funzioni di accesso, ovvero metodi
speciali che assegnano e recuperano valori dal membro dati sottostante. La funzione di accesso set consente
l'assegnazione di valori ai membri dati, mentre la funzione di accesso get recupera i valori dei membri dati.
Questo esempio mostra una classe Person con due proprietà: Name (string) e Age (int). Entrambe le proprietà
forniscono le funzioni di accesso get e set , quindi vengono considerate proprietà di lettura/scrittura.

Esempio
class Person
{
private string _name = "N/A";
private int _age = 0;

// Declare a Name property of type string:


public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

// Declare an Age property of type int:


public int Age
{
get
{
return _age;
}

set
{
_age = value;
}
}

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}
}

class TestPerson
{
static void Main()
{
// Create a new Person object:
Person person = new Person();
// Print out the name and the age associated with the person:
Console.WriteLine("Person details - {0}", person);

// Set some values on the person object:


person.Name = "Joe";
person.Age = 99;
Console.WriteLine("Person details - {0}", person);

// Increment the Age property:


person.Age += 1;
Console.WriteLine("Person details - {0}", person);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Person details - Name = N/A, Age = 0
Person details - Name = Joe, Age = 99
Person details - Name = Joe, Age = 100
*/

Programmazione efficiente
Nell'esempio precedente le proprietà Name e Age sono di tipo public e includono entrambe le funzioni di
accesso get e set . Ciò consente a qualsiasi oggetto di leggere e scrivere tali proprietà. A volte è tuttavia
preferibile escludere una delle funzioni di accesso. Se si omette la funzione di accesso set , ad esempio, la
proprietà diventa di sola lettura:

public string Name


{
get
{
return _name;
}
set
{
_name = value;
}
}

In alternativa è possibile esporre pubblicamente una funzione di accesso ma rendere l'altra privata o protetta.
Per altre informazioni, vedere Accessibilità asimmetrica delle funzioni di accesso.
Una volta dichiarate le proprietà, è possibile usarle come se si trattasse di campi della classe. Ciò consente di
usare una sintassi molto semplice quando si vuole ottenere o impostare il valore di una proprietà, come nelle
istruzioni seguenti:

person.Name = "Joe";
person.Age = 99;

Si noti che in un metodo set di una proprietà è disponibile una variabile value speciale. Questa variabile
contiene il valore specificato dall'utente, ad esempio:

_name = value;

Si noti la semplice sintassi per incrementare la proprietà Age di un oggetto Person :


person.Age += 1;

Se per modellare le proprietà venissero usati metodi set e get distinti, il codice equivalente sarebbe simile al
seguente:

person.SetAge(person.GetAge() + 1);

In questo esempio viene eseguito l'override del metodo ToString :

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}

Si noti che il metodo ToString non viene usato in modo esplicito nel programma. Viene richiamato per
impostazione predefinita dalle chiamate a WriteLine .

Vedi anche
Guida per programmatori C#
Proprietà
Classi e struct
Proprietà implementate automaticamente (Guida
per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

In C# 3.0 e versioni successive, le proprietà implementate automaticamente rendono più concisa la


dichiarazione di proprietà quando nelle funzioni di accesso della proprietà non è necessaria alcuna logica
aggiuntiva. Consentono inoltre al codice client di creare oggetti. Quando si dichiara una proprietà come
mostrato nel seguente esempio, il compilatore crea un campo sottostante privato anonimo accessibile solo
tramite le funzioni di accesso get e set della proprietà.

Esempio
L'esempio seguente mostra una classe semplice con alcune proprietà implementate automaticamente:

// This class is mutable. Its data can be modified from


// outside the class.
class Customer
{
// Auto-implemented properties for trivial get and set
public double TotalPurchases { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }

// Constructor
public Customer(double purchases, string name, int id)
{
TotalPurchases = purchases;
Name = name;
CustomerId = id;
}

// Methods
public string GetContactInfo() { return "ContactInfo"; }
public string GetTransactionHistory() { return "History"; }

// .. Additional methods, events, etc.


}

class Program
{
static void Main()
{
// Intialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);

// Modify a property.
cust1.TotalPurchases += 499.99;
}
}

Non è possibile dichiarare proprietà implementate automaticamente nelle interfacce. Le proprietà implementate
automaticamente dichiarano un campo sottostante istanza privata e le interfacce non possono dichiarare campi
di istanza. La dichiarazione di una proprietà in un'interfaccia senza la definizione di un corpo dichiara una
proprietà con funzioni di accesso che devono essere implementate da ogni tipo che implementa tale interfaccia.
In C# 6 e versioni successive, è possibile inizializzare le proprietà implementate automaticamente in modo
simile ai campi:

public string FirstName { get; set; } = "Jane";

La classe mostrata nell'esempio precedente è modificabile. Il codice client può modificare i valori negli oggetti
dopo la creazione. Nelle classi complesse che contengono un comportamento significativo (metodi) e i dati,
spesso è necessario disporre di proprietà pubbliche. Tuttavia, per le classi o gli struct di piccole dimensioni che
incapsulano solo un set di valori (dati) e non hanno comportamenti oppure hanno comportamenti limitati, è
consigliabile rendere gli oggetti non modificabili dichiarando la funzione di accesso set come private (non
modificabile dai consumer) o dichiarando solo una funzione di accesso get (non modificabile, tranne che nel
costruttore). Per ulteriori informazioni, vedere come implementare una classe Lightweight con proprietà
implementate automaticamente.

Vedere anche
Proprietà
Modificatori
Come implementare una classe Lightweight con
proprietà implementate automaticamente (Guida
per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

Questo esempio mostra come creare una classe leggera non modificabile che serve solo a incapsulare un set di
proprietà implementate automaticamente. Usare questo genere di costrutto invece di una struct quando è
necessario usare la semantica del tipo riferimento.
È possibile creare una proprietà non modificabile in due modi:
È possibile dichiarare la funzione di accesso set come privata. La proprietà è impostabile solo all'interno
del tipo è, ma non è modificabile per i consumer.
Quando si dichiara una funzione di accesso set privata, non è possibile usare un inizializzatore di
oggetto per inizializzare la proprietà. È necessario usare un costruttore o un metodo factory.
È possibile dichiarare solo la funzione di accesso Get , che rende la proprietà non modificabile ovunque
tranne che nel costruttore del tipo.
Nell'esempio seguente viene illustrato il modo in cui una proprietà con solo la funzione di accesso get è diversa
da una con Get e private set.

class Contact
{
public string Name { get; }
public string Address { get; private set; }

public Contact(string contactName, string contactAddress)


{
// Both properties are accessible in the constructor.
Name = contactName;
Address = contactAddress;
}

// Name isn't assignable here. This will generate a compile error.


//public void ChangeName(string newName) => Name = newName;

// Address is assignable here.


public void ChangeAddress(string newAddress) => Address = newAddress
}

Esempio
Il seguente esempio mostra due modi per implementare una classe non modificabile con proprietà
implementate automaticamente. Ogni modo dichiara una delle proprietà con una funzione di accesso set
privata e una delle proprietà solo con get . La prima classe usa un costruttore solo per inizializzare le proprietà e
la seconda classe usa un metodo factory statico che chiama un costruttore.

// This class is immutable. After an object is created,


// it cannot be modified from outside the class. It uses a
// constructor to initialize its properties.
class Contact
{
{
// Read-only property.
public string Name { get; }

// Read-write property with a private set accessor.


public string Address { get; private set; }

// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}

// This class is immutable. After an object is created,


// it cannot be modified from outside the class. It uses a
// static method and private constructor to initialize its properties.
public class Contact2
{
// Read-write property with a private set accessor.
public string Name { get; private set; }

// Read-only property.
public string Address { get; }

// Private constructor.
private Contact2(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}

// Public factory method.


public static Contact2 CreateContact(string name, string address)
{
return new Contact2(name, address);
}
}

public class Program


{
static void Main()
{
// Some simple data sources.
string[] names = {"Terry Adams","Fadi Fakhouri", "Hanying Feng",
"Cesar Garcia", "Debra Garcia"};
string[] addresses = {"123 Main St.", "345 Cypress Ave.", "678 1st Ave",
"12 108th St.", "89 E. 42nd St."};

// Simple query to demonstrate object creation in select clause.


// Create Contact objects by using a constructor.
var query1 = from i in Enumerable.Range(0, 5)
select new Contact(names[i], addresses[i]);

// List elements cannot be modified by client code.


var list = query1.ToList();
foreach (var contact in list)
{
Console.WriteLine("{0}, {1}", contact.Name, contact.Address);
}

// Create Contact2 objects by using a static factory method.


var query2 = from i in Enumerable.Range(0, 5)
select Contact2.CreateContact(names[i], addresses[i]);

// Console output is identical to query1.


var list2 = query2.ToList();
// List elements cannot be modified by client code.
// CS0272:
// list2[0].Name = "Eugene Zabokritski";

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

/* Output:
Terry Adams, 123 Main St.
Fadi Fakhouri, 345 Cypress Ave.
Hanying Feng, 678 1st Ave
Cesar Garcia, 12 108th St.
Debra Garcia, 89 E. 42nd St.
*/

Il compilatore crea campi sottostanti per ogni proprietà implementate automaticamente. I campi non sono
accessibili direttamente dal codice sorgente.

Vedi anche
Proprietà
struct
Inizializzatori di oggetto e di raccolta
Metodi (Guida per programmatori C#)
28/01/2021 • 18 minutes to read • Edit Online

Un metodo è un blocco di codice che contiene una serie di istruzioni. Un programma fa in modo che le istruzioni
vengano eseguite chiamando il metodo e specificando gli argomenti del metodo obbligatori. In C#, ogni
istruzione eseguita viene attuata nel contesto di un metodo. Il Main metodo è il punto di ingresso per ogni
applicazione C# e viene chiamato dal Common Language Runtime (CLR) all'avvio del programma.

NOTE
Questo articolo illustra i metodi denominati. Per informazioni sulle funzioni anonime, vedere Funzioni anonime.

Firme del metodo


I metodi vengono dichiarati in una classe, uno structo un' interfaccia specificando il livello di accesso, ad
esempio public o private , i modificatori facoltativi, ad esempio abstract o sealed , il valore restituito, il
nome del metodo e i parametri del metodo. Queste parti costituiscono la firma del metodo.

IMPORTANT
Un tipo restituito di un metodo non fa parte della firma del metodo in caso di overload dei metodi. Fa tuttavia parte della
firma del metodo quando si determina la compatibilità tra un delegato e il metodo a cui fa riferimento.

I parametri del metodo vengono racchiusi tra parentesi e separati da virgole. Le parentesi vuote indicano che il
metodo non richiede parametri. Questa classe contiene quattro metodi:

abstract class Motorcycle


{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }

// Only derived classes can call this.


protected void AddGas(int gallons) { /* Method statements here */ }

// Derived classes can override the base class implementation.


public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }

// Derived classes must implement this.


public abstract double GetTopSpeed();
}

Accesso ai metodi
Chiamare un metodo su un oggetto è come accedere a un campo. Dopo il nome dell'oggetto aggiungere un
punto, il nome del metodo e le parentesi. Gli argomenti vengono elencati tra parentesi e separati da virgole. I
metodi della classe Motorcycle possono quindi essere chiamati come nell'esempio seguente:
class TestMotorcycle : Motorcycle
{

public override double GetTopSpeed()


{
return 108.4;
}

static void Main()


{

TestMotorcycle moto = new TestMotorcycle();

moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}

Parametri di metodo e argomenti


La definizione del metodo specifica i nomi e i tipi di tutti i parametri obbligatori. Quando il codice chiamante
chiama il metodo, fornisce valori concreti, detti argomenti, per ogni parametro. Gli argomenti devono essere
compatibili con il tipo di parametro, ma il nome dell'argomento (se presente) usato nel codice chiamante non
deve essere lo stesso del parametro denominato definito nel metodo. Ad esempio:

public void Caller()


{
int numA = 4;
// Call with an int variable.
int productA = Square(numA);

int numB = 32;


// Call with another int variable.
int productB = Square(numB);

// Call with an integer literal.


int productC = Square(12);

// Call with an expression that evaulates to int.


productC = Square(productA * 3);
}

int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}

Passaggio per riferimento e passaggio per valore


Per impostazione predefinita, quando un'istanza di un tipo di valore viene passata a un metodo, la relativa copia
viene passata al posto dell'istanza stessa. Pertanto, le modifiche apportate all'argomento non hanno alcun
effetto sull'istanza originale nel metodo chiamante. Per passare un'istanza di tipo valore per riferimento, usare la
ref parola chiave. Per altre informazioni, vedere Passaggio di parametri di tipi di valore.

Quando viene passato un oggetto di un tipo riferimento a un metodo, viene passato un riferimento all'oggetto,
ovvero, il metodo riceve un argomento che indica la posizione dell'oggetto, ma non l'oggetto stesso. Se si
modifica un membro dell'oggetto usando questo riferimento, la modifica si riflette nell'argomento nel metodo
chiamante, anche se si passa l'oggetto per valore.
Per creare un tipo riferimento, usare la class parola chiave, come illustrato nell'esempio seguente:

public class SampleRefType


{
public int value;
}

Se ora si passa un oggetto basato su questo tipo a un metodo, viene passato un riferimento all'oggetto.
Nell'esempio seguente viene passato un oggetto di tipo SampleRefType al metodo ModifyObject :

public static void TestRefType()


{
SampleRefType rt = new SampleRefType();
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
}

static void ModifyObject(SampleRefType obj)


{
obj.value = 33;
}

L'esempio è sostanzialmente uguale al precedente in quanto passa un argomento per valore a un metodo, ma,
essendo usato un tipo riferimento, il risultato è diverso. La modifica apportata in ModifyObject al campo value
del parametro, obj , cambia anche il campo value dell'argomento, rt , nel metodo TestRefType . Il metodo
TestRefType visualizza 33 come output.

Per altre informazioni su come passare i tipi di riferimento per riferimento e per valore, vedere Passaggio di
parametri di tipi di riferimento e Tipi di riferimento.

Valori restituiti
I metodi possono restituire un valore al chiamante. Se il tipo restituito, il tipo elencato prima del nome del
metodo, non è void , il metodo può restituire il valore usando la parola chiave return . Un'istruzione con la
parola chiave return seguita da un valore corrispondente al tipo restituito restituirà tale valore al chiamante del
metodo.
Il valore può essere restituito dal chiamante per valore o, a partire dalla versione C# 7.0, per riferimento. I valori
vengono restituiti al chiamante per riferimento se la parola chiave ref viene usata nella firma del metodo e se
segue ogni parola chiave return . La firma del metodo e l'istruzione di restituzione seguenti, ad esempio,
indicano che il metodo restituisce al chiamante i nomi di una variabile estDistance per riferimento.

public ref double GetEstimatedDistance()


{
return ref estDistance;
}

La parola chiave return interrompe anche l'esecuzione del metodo. Se il tipo restituito è void , un'istruzione
return senza un valore è tuttavia utile per interrompere l'esecuzione del metodo. Senza la parola chiave
return , l'esecuzione del metodo verrà interrotta quando verrà raggiunta la fine del blocco di codice. Per usare
la parola chiave return per restituire un valore, sono obbligatori metodi con un tipo restituito non void. Ad
esempio, questi due metodi usano la parola chiave return per restituire numeri interi:

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}

public int SquareANumber(int number)


{
return number * number;
}
}

Per usare un valore restituito da un metodo, il metodo chiamante può usare la chiamata al metodo stessa
ovunque è sufficiente un valore dello stesso tipo. È inoltre possibile assegnare il valore restituito a una variabile.
I due esempi seguenti di codice ottengono lo stesso risultato:

int result = obj.AddTwoNumbers(1, 2);


result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);

result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));


// The result is 9.
Console.WriteLine(result);

L'uso di una variabile locale, in questo caso result , per archiviare un valore è facoltativo. Potrebbe migliorare la
leggibilità del codice o potrebbe essere necessario se si desidera archiviare il valore originale dell'argomento
per l'intero ambito del metodo.
Per usare un valore restituito da un metodo per riferimento, è necessario dichiarare una variabile locale ref se si
vuole modificarne il valore. Se, ad esempio, il metodo Planet.GetEstimatedDistance restituisce un valore Double
per riferimento, è possibile definirlo come variabile locale ref con un codice simile al seguente:

ref int distance = plant

Non è necessario restituire una matrice multidimensionale da un metodo, M , che modifica il contenuto della
matrice, se la funzione chiamante ha passato la matrice a M . Si può restituire la matrice risultante da M per un
flusso di valori corretto o funzionale, ma non è necessario perché C# passa tutti i tipi riferimento per valore e il
valore di un riferimento a una matrice è il puntatore alla matrice. Nel metodo M tutte le modifiche apportate al
contenuto della matrice sono osservabili da qualsiasi codice che contiene un riferimento alla matrice, come
illustrato nell'esempio seguente:
static void Main(string[] args)
{
int[,] matrix = new int[2, 2];
FillMatrix(matrix);
// matrix is now full of -1
}

public static void FillMatrix(int[,] matrix)


{
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
matrix[i, j] = -1;
}
}
}

Per altre informazioni, vedere return.

Metodi asincroni
Tramite la funzionalità async, è possibile richiamare i metodi asincroni senza usare callback espliciti o
suddividere manualmente il codice in più metodi o espressioni lambda.
Se si contrassegna un metodo con il modificatore async, è possibile usare l'operatore await nel metodo. Quando
il controllo raggiunge un'espressione await nel metodo asincrono, il controllo torna al chiamante e
l'avanzamento nel metodo viene sospeso fino al completamento dell'attività attesa. Una volta completata
l'attività, l'esecuzione del metodo può riprendere.

NOTE
Un metodo asincrono restituisce al chiamante quando rileva il primo oggetto atteso che non è ancora completo o
raggiunge la fine del metodo asincrono, a seconda di quale si verifica per primo.

Un metodo asincrono può avere un tipo restituito Task<TResult>, Tasko void. Il tipo restituito void viene usato
principalmente per definire i gestori eventi, dove un tipo restituito void è necessario. Un metodo asincrono che
restituisce void non può essere atteso e il chiamante di un metodo che restituisce void non può intercettare
eccezioni generate dal metodo.
Nel seguente esempio, DelayAsync è un metodo asincrono con un tipo restituito Task<TResult>. DelayAsync ha
un'istruzione return che restituisce un numero intero. La dichiarazione del metodo di DelayAsync deve quindi
avere un tipo restituito Task<int> . Poiché il tipo restituito è Task<int> , la valutazione dell'espressione await in
DoSomethingAsync genera un numero intero come illustra l'istruzione seguente: int result = await delayTask .

Il Main metodo è un esempio di un metodo asincrono che ha un tipo restituito Task . Passa al DoSomethingAsync
metodo e, poiché è espresso con una singola riga, può omettere le async await parole chiave e. Poiché
DoSomethingAsync è un metodo asincrono, l'attività per la chiamata a DoSomethingAsync deve essere attesa, come
mostra l'istruzione seguente: await DoSomethingAsync(); .
using System;
using System.Threading.Tasks;

class Program
{
static Task Main() => DoSomethingAsync();

static async Task DoSomethingAsync()


{
Task<int> delayTask = DelayAsync();
int result = await delayTask;

// The previous two statements may be combined into


// the following statement.
//int result = await DelayAsync();

Console.WriteLine($"Result: {result}");
}

static async Task<int> DelayAsync()


{
await Task.Delay(100);
return 5;
}
}
// Example output:
// Result: 5

Un metodo asincrono non può dichiarare parametri ref o out , ma può chiamare metodi che hanno tali
parametri.
Per altre informazioni sui metodi asincroni, vedere programmazione asincrona con i tipi Async e await e Async
restituiti.

Definizioni del corpo dell'espressione


È comune disporre di definizioni di metodo che semplicemente restituiscono subito il risultato di un'espressione
o che includono una singola istruzione come corpo del metodo. Esiste una sintassi breve per definire tali metodi
usando => :

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);

Se il metodo restituisce void o è un metodo asincrono, il corpo del metodo deve essere un'espressione di
istruzione (come per le espressioni lambda). Per le proprietà e gli indicizzatori, devono essere di sola lettura e
non è necessario usare la parola chiave della funzione di accesso get .

Iterators
Un iteratore esegue un'iterazione personalizzata su una raccolta, ad esempio un elenco o una matrice. Un
iteratore usa l'istruzione yield return per restituire un elemento alla volta. Quando viene raggiunta un'istruzione
yield return , la posizione corrente nel codice viene memorizzata. L'esecuzione viene riavviata a partire da quella
posizione la volta successiva che viene chiamato l'iteratore.
Per chiamare un iteratore dal codice client, usare un'istruzione foreach .
Il tipo restituito di un iteratore può essere IEnumerable, IEnumerable<T>, IEnumeratoro IEnumerator<T>.
Per ulteriori informazioni, vedere iteratori.

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi e struct
Modificatori di accesso
Classi statiche e membri di classi statiche
Ereditarietà
Classi e membri delle classi astratte e sealed
params
ritorno
out
ref
Passaggio di parametri
Funzioni locali (Guida per programmatori C#)
02/11/2020 • 19 minutes to read • Edit Online

A partire dalla versione 7.0, C# supporta le funzioni locali. Le funzioni locali sono metodi privati di un tipo
annidati in un altro membro. Possono essere chiamate solo dal relativo membro contenitore. Le funzioni locali,
in particolare, possono essere dichiarate in e chiamate da:
Metodi, soprattutto metodi iteratori e metodi asincroni
Costruttori
Funzioni di accesso alle proprietà
Funzioni di accesso agli eventi
Metodi anonimi
Espressioni lambda
Finalizzatori
Altre funzioni locali
Le funzioni locali, tuttavia, non possono essere dichiarate all'interno di un membro con corpo di espressione.

NOTE
In alcuni casi, è possibile usare un'espressione lambda per implementare le funzionalità supportate anche da una funzione
locale. Per un confronto, vedere funzioni locali rispetto alle espressioni lambda.

Le funzioni locali rendono chiaro l'obiettivo del codice. Chiunque legga il codice può vedere che il metodo non è
richiamabile, ad eccezione del metodo contenitore. Per i progetti in team, le funzioni locali impediscono anche a
un altro sviluppatore di chiamare per errore il metodo direttamente da un altro punto della classe o dello struct.

Sintassi delle funzioni locali


Una funzione locale viene definita come metodo annidato all'interno di un membro contenitore. La definizione
presenta la sintassi seguente:

<modifiers> <return-type> <method-name> <parameter-list>

Con una funzione locale è possibile usare i modificatori seguenti:


async
unsafe
static (in C# 8,0 e versioni successive). Una funzione locale statica non può acquisire le variabili locali o lo
stato dell'istanza.
extern (in C# 9,0 e versioni successive). Una funzione locale esterna deve essere static .

Tutte le variabili locali definite nel membro contenitore, inclusi i parametri del metodo, sono accessibili in una
funzione locale non statica.
Diversamente da una definizione di metodo, una definizione di funzione locale non può includere il modificatore
di accesso ai membri. Poiché tutte le funzioni locali sono private, l'integrazione di un modificatore di accesso
come la parola chiave private genera l'errore del compilatore CS0106: "Il modificatore 'private' non è valido
per questo elemento".
L'esempio seguente definisce una funzione locale denominata AppendPathSeparator , privata di un metodo
denominato GetText :

private static string GetText(string path, string filename)


{
var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
var text = reader.ReadToEnd();
return text;

string AppendPathSeparator(string filepath)


{
return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
}
}

A partire da C# 9,0, è possibile applicare attributi a una funzione locale, i parametri e i parametri di tipo, come
illustrato nell'esempio seguente:

#nullable enable
private static void Process(string?[] lines, string mark)
{
foreach (var line in lines)
{
if (IsValid(line))
{
// Processing logic...
}
}

bool IsValid([NotNullWhen(true)] string? line)


{
return !string.IsNullOrEmpty(line) && line.Length >= mark.Length;
}
}

Nell'esempio precedente viene usato un attributo speciale per supportare il compilatore nell'analisi statica in un
contesto Nullable.

Funzioni locali ed eccezioni


Le funzioni locali offrono il vantaggio di consentire alle eccezioni di essere rilevate immediatamente. Nel caso
degli iteratori di metodo, le eccezioni vengono rilevate solo nel momento in cui la sequenza restituita viene
enumerata e non quando viene recuperato l'iteratore. Nel caso dei metodi asincroni, qualsiasi eccezione
generata viene rilevata mentre è attesa l'attività restituita.
Nell'esempio seguente viene definito un OddSequence metodo che enumera i numeri dispari in un intervallo
specificato. Poiché al metodo enumeratore OddSequence viene trasmesso un numero maggiore di 100, il metodo
genera una ArgumentOutOfRangeException. Come illustrato dall'output dell'esempio, l'eccezione viene rilevata
solo nel momento in cui vengono iterati i numeri e non quando si recupera l'enumeratore.
using System;
using System.Collections.Generic;

public class IteratorWithoutLocalExample


{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110);
Console.WriteLine("Retrieved enumerator...");

foreach (var x in xs) // line 11


{
Console.Write($"{x} ");
}
}

public static IEnumerable<int> OddSequence(int start, int end)


{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");

for (int i = start; i <= end; i++)


{
if (i % 2 == 1)
yield return i;
}
}
}
// The example displays the output like this:
//
// Retrieved enumerator...
// Unhandled exception. System.ArgumentOutOfRangeException: end must be less than or equal to 100.
(Parameter 'end')
// at IteratorWithoutLocalExample.OddSequence(Int32 start, Int32 end)+MoveNext() in
IteratorWithoutLocal.cs:line 22
// at IteratorWithoutLocalExample.Main() in IteratorWithoutLocal.cs:line 11

Se si inserisce la logica iteratore in una funzione locale, vengono generate eccezioni di convalida degli
argomenti quando si recupera l'enumeratore, come illustrato nell'esempio seguente:
using System;
using System.Collections.Generic;

public class IteratorWithLocalExample


{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110); // line 8
Console.WriteLine("Retrieved enumerator...");

foreach (var x in xs)


{
Console.Write($"{x} ");
}
}

public static IEnumerable<int> OddSequence(int start, int end)


{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");

return GetOddSequenceEnumerator();

IEnumerable<int> GetOddSequenceEnumerator()
{
for (int i = start; i <= end; i++)
{
if (i % 2 == 1)
yield return i;
}
}
}
}
// The example displays the output like this:
//
// Unhandled exception. System.ArgumentOutOfRangeException: end must be less than or equal to 100.
(Parameter 'end')
// at IteratorWithLocalExample.OddSequence(Int32 start, Int32 end) in IteratorWithLocal.cs:line 22
// at IteratorWithLocalExample.Main() in IteratorWithLocal.cs:line 8

È possibile utilizzare le funzioni locali in modo analogo alle operazioni asincrone. Eccezioni generate in un
metodo asincrono quando l'attività corrispondente è attesa. Le funzioni locali consentono al codice di adottare
un approccio "fail fast" e alle eccezioni di essere generate e osservate in modo sincrono.
Nell'esempio seguente viene usato un metodo asincrono denominato GetMultipleAsync per sospendere
l'operazione per un determinato numero di secondi e restituire un valore che sia un multiplo casuale del
numero di secondi specificato. Il ritardo massimo consentito è 5 secondi. Se il valore è superiore a 5, viene
generata una ArgumentOutOfRangeException. Come illustrato nell'esempio seguente, l'eccezione generata
quando viene passato un valore 6 al GetMultipleAsync metodo viene osservata solo quando l'attività è in attesa.
using System;
using System.Threading.Tasks;

public class AsyncWithoutLocalExample


{
public static async Task Main()
{
var t = GetMultipleAsync(6);
Console.WriteLine("Got the task");

var result = await t; // line 11


Console.WriteLine($"The returned value is {result:N0}");
}

static async Task<int> GetMultipleAsync(int delayInSeconds)


{
if (delayInSeconds < 0 || delayInSeconds > 5)
throw new ArgumentOutOfRangeException(nameof(delayInSeconds), "Delay cannot exceed 5 seconds.");

await Task.Delay(delayInSeconds * 1000);


return delayInSeconds * new Random().Next(2,10);
}
}
// The example displays the output like this:
//
// Got the task
// Unhandled exception. System.ArgumentOutOfRangeException: Delay cannot exceed 5 seconds. (Parameter
'delayInSeconds')
// at AsyncWithoutLocalExample.GetMultipleAsync(Int32 delayInSeconds) in AsyncWithoutLocal.cs:line 18
// at AsyncWithoutLocalExample.Main() in AsyncWithoutLocal.cs:line 11

Analogamente all'iteratore del metodo, è possibile effettuare il refactoring dell'esempio precedente e inserire il
codice di un'operazione asincrona in una funzione locale. Come mostra l'output dell'esempio seguente,
l'oggetto ArgumentOutOfRangeException viene generato non appena GetMultiple viene chiamato il metodo.
using System;
using System.Threading.Tasks;

public class AsyncWithLocalExample


{
public static async Task Main()
{
var t = GetMultiple(6); // line 8
Console.WriteLine("Got the task");

var result = await t;


Console.WriteLine($"The returned value is {result:N0}");
}

static Task<int> GetMultiple(int delayInSeconds)


{
if (delayInSeconds < 0 || delayInSeconds > 5)
throw new ArgumentOutOfRangeException(nameof(delayInSeconds), "Delay cannot exceed 5 seconds.");

return GetValueAsync();

async Task<int> GetValueAsync()


{
await Task.Delay(delayInSeconds * 1000);
return delayInSeconds * new Random().Next(2,10);
}
}
}
// The example displays the output like this:
//
// Unhandled exception. System.ArgumentOutOfRangeException: Delay cannot exceed 5 seconds. (Parameter
'delayInSeconds')
// at AsyncWithLocalExample.GetMultiple(Int32 delayInSeconds) in AsyncWithLocal.cs:line 18
// at AsyncWithLocalExample.Main() in AsyncWithLocal.cs:line 8

Funzioni locali ed espressioni lambda


A prima vista, le funzioni locali e le espressioni lambda sono molto simili. In molti casi, la scelta tra l'uso di
funzioni locali ed espressioni lambda è una questione di stile e preferenze personali. Esistono tuttavia differenze
reali di cui è necessario essere consapevoli nei casi in cui è possibile usare le une o le altre.
Si esamineranno ora le differenze tra implementazioni di funzioni locali e implementazioni di espressioni
lambda dell'algoritmo fattoriale. Di seguito è illustrata la versione che usa una funzione locale:

public static int LocalFunctionFactorial(int n)


{
return nthFactorial(n);

int nthFactorial(int number) => number < 2


? 1
: number * nthFactorial(number - 1);
}

Questa versione USA le espressioni lambda:


public static int LambdaFactorial(int n)
{
Func<int, int> nthFactorial = default(Func<int, int>);

nthFactorial = number => number < 2


? 1
: number * nthFactorial(number - 1);

return nthFactorial(n);
}

Denominazione
Le funzioni locali sono denominate in modo esplicito come metodi. Le espressioni lambda sono metodi anonimi
che devono essere assegnate a variabili di un delegate tipo, in genere Action o Func . Quando si dichiara una
funzione locale, il processo è simile alla scrittura di un metodo normale; si dichiara un tipo restituito e una firma
di funzione.
Firme di funzione e tipi di espressione lambda
Le espressioni lambda si basano sul tipo della Action / Func variabile a cui sono assegnati per determinare
l'argomento e i tipi restituiti. Nelle funzioni locali, poiché la sintassi è molto simile alla scrittura di un metodo
normale, i tipi di argomento e il tipo restituito fanno già parte della dichiarazione di funzione.
Assegnazione definita
Le espressioni lambda sono oggetti dichiarati e assegnati in fase di esecuzione. Per poter usare un'espressione
lambda, è necessario assegnarla in modo sicuro: la Action / Func variabile a cui verrà assegnato deve essere
dichiarata e l'espressione lambda assegnata. Si noti che è LambdaFactorial necessario dichiarare e inizializzare
l'espressione lambda nthFactorial prima di definirla. In caso contrario, si verifica un errore di compilazione per
fare riferimento a nthFactorial prima di assegnarla.
Le funzioni locali sono definite in fase di compilazione. Poiché non sono assegnati alle variabili, è possibile farvi
riferimento da qualsiasi posizione del codice in cui si trova nell'ambito . nel primo esempio
LocalFunctionFactorial , è possibile dichiarare la funzione locale al di sopra o al di sotto dell' return istruzione
e non attivare alcun errore del compilatore.
Queste differenze fanno sì che gli algoritmi ricorsivi sino più facili da creare usando funzioni locali. È possibile
dichiarare e definire una funzione locale che chiama se stessa. Le espressioni lambda devono essere dichiarate e
a queste deve essere assegnato un valore predefinito prima che sia possibile riassegnarle a un corpo che fa
riferimento alla stessa espressione lambda.
Implementazione come delegato
Le espressioni lambda vengono convertite in delegati quando vengono dichiarate. Le funzioni locali sono più
flessibili perché possono essere scritte come un metodo tradizionale o come delegato. Le funzioni locali
vengono convertite in delegati solo se utilizzati come delegato.
Se si dichiara una funzione locale e si fa riferimento a questa solo chiamandola come un metodo, non verrà
convertita in delegato.
Acquisizione di variabili
Le regole di assegnazione definita influiscono anche sulle variabili acquisite dalla funzione locale o
dall'espressione lambda. Il compilatore può eseguire un'analisi statica che consente alle funzioni locali di
assegnare definitivamente le variabili acquisite nell'ambito di inclusione. Prendere in considerazione questo
esempio:
int M()
{
int y;
LocalFunction();
return y;

void LocalFunction() => y = 0;


}

Il compilatore può determinare che LocalFunction assegna in modo certo y quando viene chiamata. Poiché
LocalFunction viene chiamata prima dell'istruzione return , y viene assegnata in modo certo in
corrispondenza dell'istruzione return .
Si noti che quando una funzione locale acquisisce le variabili nell'ambito di inclusione, la funzione locale viene
implementata come tipo delegato.
Allocazioni heap
A seconda del loro uso, le funzioni locali possono evitare le allocazioni di heap, che sono sempre necessarie per
le espressioni lambda. Se una funzione locale non viene mai convertita in un delegato e nessuna delle variabili
acquisite dalla funzione locale viene acquisita da altre espressioni lambda o funzioni locali convertite in delegati,
il compilatore può evitare allocazioni di heap.
Si consideri questo esempio asincrono:

public Task<string> PerformLongRunningWorkLambda(string address, int index, string name)


{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-
negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

Func<Task<string>> longRunningWorkImplementation = async () =>


{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
};

return longRunningWorkImplementation();
}

La chiusura per questa espressione lambda contiene le variabili address , index e name . Nel caso delle funzioni
locali, l'oggetto che implementa la chiusura può essere di tipo struct Tale tipo struct verrebbe passato per
riferimento alla funzione locale. Questa differenza di implementazione consentirebbe di risparmiare
un'allocazione.
La creazione di istanze necessaria per le espressioni lambda comporta allocazioni di memoria aggiuntive che
possono ridurre le prestazioni nei percorsi di codice in cui il tempo è un fattore cruciale. Questo sovraccarico
non si verifica per le funzioni locali. Nell'esempio precedente, la versione di funzioni locali dispone di due
allocazioni minori rispetto alla versione dell'espressione lambda.
Se si è certi che la funzione locale non verrà convertita in un delegato e nessuna delle variabili acquisite da essa
viene acquisita da altre espressioni lambda o funzioni locali convertite in delegati, è possibile garantire che la
funzione locale eviti di essere allocata nell'heap dichiarando come static funzione locale. Si noti che questa
funzionalità è disponibile in C# 8,0 e versioni successive.
NOTE
La funzione locale equivalente di questo metodo usa anche una classe per la chiusura. Se la chiusura di una funzione locale
viene implementata come class o struct non ha molta importanza. Una funzione locale può usare struct mentre
un'espressione lambda userà sempre class .

public Task<string> PerformLongRunningWork(string address, int index, string name)


{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-
negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

return longRunningWorkImplementation();

async Task<string> longRunningWorkImplementation()


{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
}
}

Utilizzo della yield parola chiave


Come ultimo vantaggio, non illustrato in questo esempio, le funzioni locali possono essere implementate come
iteratori usando la sintassi yield return per produrre una sequenza di valori.

public IEnumerable<string> SequenceToLowercase(IEnumerable<string> input)


{
if (!input.Any())
{
throw new ArgumentException("There are no items to convert to lowercase.");
}

return LowercaseIterator();

IEnumerable<string> LowercaseIterator()
{
foreach (var output in input.Select(item => item.ToLower()))
{
yield return output;
}
}
}

L' yield return istruzione non è consentita nelle espressioni lambda. vedere errore del compilatore CS1621.
Sebbene le funzioni locali possano apparire ridondanti rispetto alle espressioni lambda, in realtà hanno finalità e
usi diversi. Le funzioni locali sono più efficienti nel caso si voglia scrivere una funzione che viene chiamata solo
dal contesto di un altro metodo.

Vedere anche
Metodi
Valori restituiti e variabili locali ref
18/03/2020 • 12 minutes to read • Edit Online

A partire da C# 7.0, C# supporta i valori restituiti di riferimento (valori restituiti ref). Un valore restituito di
riferimento consente a un metodo di restituire a un chiamante un riferimento a una variabile, invece di un
valore. Il chiamante può quindi scegliere di trattare la variabile come se fosse stata restituita da un valore o un
riferimento. Il chiamante può creare una nuova variabile che è di per sé un riferimento al valore restituito,
chiamata variabile locale ref.

Che cos'è un valore restituito di riferimento?


La maggior parte degli sviluppatori ha familiarità con il passaggio di un argomento a un metodo chiamato
tramite un riferimento. L'elenco di argomenti di un metodo chiamato include una variabile passata per
riferimento. Qualsiasi modifica apportata al relativo valore dal metodo chiamato viene rispettata dal chiamante.
Un valore restituito di riferimento indica che un metodo restituisce un riferimento (o alias) a una variabile.
L'ambito di tale variabile deve includere il metodo. La durata della variabile deve estendersi oltre la restituzione
del controllo del metodo. Le modifiche effettuate dal chiamante al valore restituito del metodo vengono
apportate alla variabile restituita dal metodo.
La dichiarazione che un metodo restituisce un valore restituito di riferimento indica che il metodo restituisce un
alias a una variabile. La finalità della progettazione è spesso quella di fare in modo che il codice chiamante abbia
accesso a tale variabile tramite l'alias, inclusa la possibilità di modificarla. Ne consegue che i metodi restituiti per
riferimento non possono avere il tipo restituito void .
Esistono alcune restrizioni per l'espressione che un metodo può restituire come valore restituito di riferimento.
Tali restrizioni includono:
Il valore restituito deve avere una durata che si estende oltre l'esecuzione del metodo. In altre parole, non
può essere una variabile locale nel metodo che la restituisce. Può essere un'istanza o un campo statico di
una classe oppure un argomento passato al metodo. Se si tenta di restituire una variabile locale, viene
generato l'errore del compilatore CS8168, "Non è possibile restituire la variabile locale 'obj' per
riferimento perché non è una variabile locale ref".
Il valore restituito non può essere il valore letterale null . La restituzione di null genera l'errore del
compilatore CS8156, "Non è possibile usare un'espressione in questo contesto perché non può essere
restituita per riferimento".
Un metodo con un ref restituito può restituire un alias a una variabile il cui valore è attualmente il valore
null (senza istanze) o un tipo di valore nullable per un tipo di valore.
Il valore restituito non può essere una costante, un membro di enumerazione, il valore restituito per
valore da una proprietà o un metodo di un oggetto class o struct . La violazione di questa regola
genera l'errore del compilatore CS8156, "Non è possibile usare un'espressione in questo contesto perché
non può essere restituita per riferimento".
Inoltre, i valori restituiti di riferimento non sono consentiti per i metodi asincroni. Un metodo asincrono può
restituire il controllo prima del termine dell'esecuzione, quando il valore restituito è ancora sconosciuto.

Definizione di un valore restituito ref


Un metodo che restituisce un valore restituito di riferimento deve soddisfare le due condizioni seguenti:
La firma del metodo include la parola chiave ref davanti al tipo restituito.
Ogni istruzione return nel corpo del metodo include la parola chiave ref davanti al nome dell'istanza
restituita.
L'esempio seguente illustra un metodo che soddisfa le condizioni e restituisce un riferimento a un oggetto
Person denominato p :

public ref Person GetContactInformation(string fname, string lname)


{
// ...method implementation...
return ref p;
}

Uso di un valore restituito ref


Il valore restituito ref è un alias per un'altra variabile nell'ambito del metodo chiamato. È possibile interpretare
qualsiasi uso del valore restituito ref come uso della variabile di cui effettua l'aliasing:
Quando si assegna il valore, si assegna un valore alla variabile di cui effettua l'aliasing.
Quando si legge il valore, si legge il valore della variabile di cui effettua l'aliasing.
Se lo si restituisce per riferimento, si restituisce un alias alla stessa variabile.
Se lo si passa a un altro metodo per riferimento, si passa un riferimento alla variabile di cui effettua l'aliasing.
Quando si crea un alias locale ref, si crea un nuovo alias per la stessa variabile.

Variabili locali ref


Si supponga che il metodo GetContactInformation venga dichiarato come valore restituito ref:

public ref Person GetContactInformation(string fname, string lname)

Un'assegnazione per valore legge il valore di una variabile e la assegna a una nuova variabile:

Person p = contacts.GetContactInformation("Brandie", "Best");

L'assegnazione precedente dichiara p come variabile locale. Il valore iniziale viene copiato dalla lettura del
valore restituito da GetContactInformation . Eventuali assegnazioni future a p non modificheranno il valore
della variabile restituita da GetContactInformation . La variabile p non è più un alias per la variabile restituita.
Si dichiara una variabile locale ref per copiare l'alias nel valore originale. Nell'assegnazione seguente p è un
alias per la variabile restituita da GetContactInformation .

ref Person p = ref contacts.GetContactInformation("Brandie", "Best");

L'utilizzo successivo di p è uguale all'utilizzo della variabile restituita da GetContactInformation perché p è un


alias per tale variabile. Le modifiche apportate a p modificano anche la variabile restituita da
GetContactInformation .

La parola chiave ref viene usata sia prima della dichiarazione di variabile locale che prima della chiamata al
metodo.
È possibile accedere a un valore per riferimento nello stesso modo. In alcuni casi, l'accesso a un valore per
riferimento migliora le prestazioni evitando un'operazione di copia potenzialmente dispendiosa. L'istruzione
seguente, ad esempio, spiega come sia possibile definire un valore locale di riferimento usato per fare
riferimento a un valore.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

La parola chiave ref viene usata sia prima della dichiarazione di variabile locale che prima del valore nel
secondo esempio. Se non si includono entrambe le parole chiave ref nella dichiarazione di variabile e
nell'assegnazione nei due esempi, viene generato l'errore del compilatore CS8172, "Non è possibile inizializzare
una variabile per riferimento con un valore".
Prima di C# 7.3, le variabili locali di riferimento non potevano essere riassegnate per fare riferimento a una
risorsa di archiviazione diversa dopo l'inizializzazione. Questa restrizione è stata rimossa. L'esempio seguente
mostra una riassegnazione:

ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization


refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

Le variabili locali di riferimento devono ancora essere inizializzate quando vengono dichiarate.

Valori restituiti e variabili locali ref: un esempio


L'esempio seguente definisce una classe NumberStore che archivia una matrice di valori integer. Il metodo
FindNumber restituisce per riferimento il primo numero maggiore o uguale al numero passato come argomento.
Se nessun numero è maggiore o uguale all'argomento, il metodo restituisce il numero nell'indice 0.

using System;

class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

public ref int FindNumber(int target)


{
for (int ctr = 0; ctr < numbers.Length; ctr++)
{
if (numbers[ctr] >= target)
return ref numbers[ctr];
}
return ref numbers[0];
}

public override string ToString() => string.Join(" ", numbers);


}

L'esempio seguente chiama il metodo NumberStore.FindNumber per recuperare il primo valore maggiore o
uguale a 16. Il chiamante raddoppia quindi il valore restituito dal metodo. L'output dell'esempio mostra la
modifica riflessa nel valore degli elementi della matrice dell'istanza di NumberStore .
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence: {store.ToString()}");
// The example displays the following output:
// Original sequence: 1 3 7 15 31 63 127 255 511 1023
// New sequence: 1 3 7 15 62 63 127 255 511 1023

Senza il supporto per i valori restituiti di riferimento, tale operazione viene eseguita restituendo l'indice
dell'elemento di matrice insieme al relativo valore. Il chiamante può quindi usare questo indice per modificare il
valore in una chiamata al metodo distinta. Il chiamante può tuttavia anche modificare l'indice per accedere ed
eventualmente modificare altri valori della matrice.
L'esempio seguente illustra il modo in cui è possibile riscrivere il metodo FindNumber dopo C# 7.3 per usare la
riassegnazione locale del riferimento:

using System;

class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

public ref int FindNumber(int target)


{
ref int returnVal = ref numbers[0];
var ctr = numbers.Length - 1;
while ((ctr >= 0) && numbers[ctr] >= target)
{
returnVal = ref numbers[ctr];
ctr--;
}
return ref returnVal;
}

public override string ToString() => string.Join(" ", numbers);


}

Questa seconda versione è più efficiente con sequenze più lunghe negli scenari in cui il numero cercato è più
vicino alla fine della matrice.

Vedere anche
ref (parola chiave)
Scrivere codice efficiente e sicuro
Passaggio di parametri (Guida per programmatori
C#)
02/11/2020 • 2 minutes to read • Edit Online

In C# è possibile passare gli argomenti ai parametri per valore o per riferimento. Il passaggio per riferimento
consente a membri di funzioni, metodi, proprietà, indicizzatori, operatori e costruttori di modificare il valore dei
parametri e rendere permanenti le modifiche nell'ambiente chiamante. Per passare un parametro per
riferimento con l'intenzione di modificare il valore, usare la parola chiave ref o out . Per passare per
riferimento con l'intenzione di evitare la copia, ma non la modifica del valore, usare il modificatore in . Per
semplicità, negli esempi riportati in questo argomento verrà utilizzata soltanto la parola chiave ref . Per altre
informazioni sulla differenza tra in , ref e out , vedere in, ref e out.
L'esempio seguente illustra la differenza fra parametri di valore e di riferimento.

class Program
{
static void Main(string[] args)
{
int arg;

// Passing by value.
// The value of arg in Main is not changed.
arg = 4;
squareVal(arg);
Console.WriteLine(arg);
// Output: 4

// Passing by reference.
// The value of arg in Main is changed.
arg = 4;
squareRef(ref arg);
Console.WriteLine(arg);
// Output: 16
}

static void squareVal(int valParameter)


{
valParameter *= valParameter;
}

// Passing by reference
static void squareRef(ref int refParameter)
{
refParameter *= refParameter;
}
}

Per altre informazioni, vedere gli argomenti seguenti:


Passaggio di parametri di tipi di valore
Passaggio di parametri di tipo di riferimento

Specifiche del linguaggio C#


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

Vedere anche
Guida per programmatori C#
Metodi
Passaggio di parametri di tipo di valore (Guida per
programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

Una variabile di tipo valore contiene direttamente i dati, mentre una variabile di tipo riferimento contiene un
riferimento ai dati. Quando si passa una variabile di tipo valore a un metodo per valore, si passa una copia della
variabile al metodo. Tutte le modifiche apportate al parametro che si verificano all'interno del metodo non
hanno alcun effetto sui dati originali archiviati nella variabile dell'argomento. Se si vuole che il metodo chiamato
modifichi il valore dell'argomento, è necessario passarlo per riferimento usando la parola chiave ref o out. È
inoltre possibile usare la parola chiave in per passare un parametro di valore per riferimento per evitare la copia,
garantendo che il valore non verrà modificato. Per semplicità, negli esempi seguenti viene usato ref .

Passaggio di tipi di valore per valore


Nell'esempio seguente viene illustrato il passaggio di parametri di tipo valore per valore. La variabile n viene
passata al metodo SquareIt per valore. Tutte le modifiche apportate all'interno del metodo non hanno effetto
sul valore originale della variabile.

class PassingValByVal
{
static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(n); // Passing the variable by value.


System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 5
*/

La variabile n è un tipo valore. Contiene i propri dati, il valore 5 . Quando si richiama SquareIt , il contenuto di
n viene copiato nel parametro x , che viene elevato al quadrato all'interno del metodo. In Main , tuttavia, il
valore di n dopo la chiamata del metodo SquareIt rimane invariato. La modifica apportata all'interno del
metodo ha effetto soltanto sulla variabile locale x .

Passaggio di tipi di valore per riferimento


L'esempio seguente è identico a quello precedente, tranne per il fatto che l'argomento viene passato come
parametro ref . Il valore dell'argomento sottostante, n , viene modificato quando si modifica x nel metodo.

class PassingValByRef
{
static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(ref n); // Passing the variable by reference.


System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 25
*/

Nell'esempio seguente non viene passato il valore di n , ma un riferimento a n . Il parametro x non è di tipo
int ma è un riferimento a un valore int , in questo caso un riferimento a n . Di conseguenza, quando x viene
elevato al quadrato all'interno del metodo, il valore effettivamente elevato al quadrato è quello a cui x fa
riferimento, ovvero n .

Scambio di tipi di valore


Un esempio comune di modifica dei valori degli argomenti è un metodo swap, dove si passano due variabili al
metodo e il metodo scambia i relativi contenuti. È necessario passare argomenti al metodo swap per
riferimento. In caso contrario, vengono scambiate le copie locali dei parametri all'interno del metodo e al
metodo che esegue la chiamata non viene apportata alcuna modifica. L'esempio seguente scambia i valori
integer.

static void SwapByRef(ref int x, ref int y)


{
int temp = x;
x = y;
y = temp;
}

Quando si chiama il metodo SwapByRef , usare nella chiamata la parole chiave ref , come mostrato
nell'esempio seguente.
static void Main()
{
int i = 2, j = 3;
System.Console.WriteLine("i = {0} j = {1}" , i, j);

SwapByRef (ref i, ref j);

System.Console.WriteLine("i = {0} j = {1}" , i, j);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
/* Output:
i = 2 j = 3
i = 3 j = 2
*/

Vedere anche
Guida per programmatori C#
Passaggio di parametri
Passaggio di parametri di tipo di riferimento
Passaggio di parametri di tipo di riferimento (Guida
per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

Una variabile di un tipo riferimento non contiene direttamente i dati, ma solo un riferimento a essi. Quando si
passa un parametro di tipo riferimento per valore, è possibile modificare i dati appartenenti all'oggetto di
riferimento, ad esempio il valore del membro di una classe. Non è tuttavia possibile modificare il valore del
riferimento stesso. Ad esempio, non è possibile usare lo stesso riferimento per allocare memoria per un nuovo
oggetto e per renderlo persistente all'esterno del metodo. In questo caso, è necessario passare il parametro
usando la parola chiave ref o out. Per semplicità, negli esempi seguenti viene usato ref .

Passaggio di tipi riferimento per valore


Nell'esempio seguente viene illustrato il passaggio per valore di un parametro di tipo riferimento, arr , a un
metodo, Change . Poiché il parametro è un riferimento a arr , è possibile modificare i valori degli elementi della
matrice. Il tentativo di riassegnare il parametro a una diversa posizione in memoria, tuttavia, è efficace solo
all'interno del metodo e non ha alcun effetto sulla variabile originale arr .

class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[0] = 888; // This change affects the original element.
pArray = new int[5] {-3, -1, -2, -3, -4}; // This change is local.
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}

static void Main()


{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr
[0]);

Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr
[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: 888
*/

Nell'esempio precedente, la matrice arr , che rappresenta un tipo riferimento, viene passata al metodo senza il
parametro ref . In questo caso, al metodo viene passata una copia del riferimento che punta a arr . L'output
indica che il metodo ha la possibilità di modificare il contenuto di un elemento della matrice (da 1 a 888 ).
L'allocazione di una nuova porzione di memoria usando l'operatore new nel metodo Change , tuttavia, fa sì che
la variabile pArray faccia riferimento a una nuova matrice. In questo modo, eventuali modifiche successive non
avranno effetto sulla matrice originale arr , creata in Main . In realtà, in questo esempio vengono create due
matrici: una in Main e una nel metodo Change .
Passaggio di tipi riferimento per riferimento
L'esempio seguente è identico a quello precedente, tranne per il fatto che la parola chiave ref viene aggiunta
all'intestazione e alla chiamata del metodo. Tutte le modifiche apportate nel metodo si ripercuotono sulla
variabile originale nel programma chiamante.

class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// Both of the following changes will affect the original variables:
pArray[0] = 888;
pArray = new int[5] {-3, -1, -2, -3, -4};
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}

static void Main()


{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}",
arr[0]);

Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}",
arr[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: -3
*/

Tutte le modifiche apportate all'interno del metodo si ripercuotono sulla variabile originale in Main . In realtà, la
matrice originale viene riallocata mediante l'operatore new . Dopo la chiamata al metodo Change , quindi, i
riferimenti a arr puntano alla matrice a cinque elementi creata nel metodo Change .

Scambio di due stringhe


Lo scambio di stringhe è un buon esempio per illustrare il passaggio per riferimento di parametri di tipo
riferimento. In questo esempio, due stringhe, str1 e str2 , vengono inizializzate in Main e passate al metodo
SwapStrings come parametri modificati dalla parola chiave ref . Le due stringhe vengono scambiate all'interno
del metodo e all'interno di Main .
class SwappingStrings
{
static void SwapStrings(ref string s1, ref string s2)
// The string parameter is passed by reference.
// Any changes on parameters will affect the original variables.
{
string temp = s1;
s1 = s2;
s2 = temp;
System.Console.WriteLine("Inside the method: {0} {1}", s1, s2);
}

static void Main()


{
string str1 = "John";
string str2 = "Smith";
System.Console.WriteLine("Inside Main, before swapping: {0} {1}", str1, str2);

SwapStrings(ref str1, ref str2); // Passing strings by reference


System.Console.WriteLine("Inside Main, after swapping: {0} {1}", str1, str2);
}
}
/* Output:
Inside Main, before swapping: John Smith
Inside the method: Smith John
Inside Main, after swapping: Smith John
*/

In questo esempio è necessario passare i parametri per riferimento perché abbiano effetto sulle variabili del
programma chiamante. Se si rimuove la parola chiave ref sia dall'intestazione del metodo che dalla chiamata
al metodo, nel programma chiamante non avrà luogo alcuna modifica.
Per altre informazioni sulle stringhe, vedere Stringhe.

Vedere anche
Guida per programmatori C#
Passaggio di parametri
ref
in
out
Tipi di riferimento
Come distinguere la differenza tra il passaggio di
uno struct e il passaggio di un riferimento a una
classe a un metodo (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

L'esempio seguente mostra la differenza tra passare uno struct a un metodo e passare un'istanza di una classe a
un metodo. Nell'esempio entrambi gli argomenti (struct e istanza di classe) vengono passati in base al valore ed
entrambi i metodi modificano il valore di un campo dell'argomento. I risultati dei due metodi non sono tuttavia
uguali perché ciò che viene passato quando si passa uno struct è diverso da ciò che viene passato quando si
passa un'istanza di una classe.
Poiché uno struct è un tipo valore, quando si passa uno struct in base al valore a un metodo, il metodo riceve
una copia dell'argomento dello struct, su cui opera. Il metodo non ha accesso allo struct originale nella chiamata
e quindi non può modificarlo in alcun modo. Il metodo può modificare solo la copia.
Un'istanza di classe è un tipo riferimento, non un tipo valore. Quando si passa un tipo riferimento in base al
valore a un metodo, il metodo riceve una copia del riferimento all'istanza di classe. Ovvero, il metodo chiamato
riceve una copia dell'indirizzo dell'istanza e il metodo chiamante mantiene l'indirizzo originale dell'istanza.
L'istanza di classe nel metodo chiamante ha un indirizzo, il parametro nel metodo chiamato ha una copia
dell'indirizzo ed entrambi gli indirizzi fanno riferimento allo stesso oggetto. Poiché il parametro contiene solo
una copia dell'indirizzo, il metodo chiamato non può modificare l'indirizzo dell'istanza di classe nel metodo
chiamante. Tuttavia, il metodo chiamato può utilizzare la copia dell'indirizzo per accedere ai membri della classe
che sia l'indirizzo originale che la copia del riferimento all'indirizzo. Se il metodo chiamato modifica un membro
della classe, viene modificata anche l'istanza di classe originale nel metodo chiamante.
L'output dell'esempio seguente illustra la differenza. Il valore del campo willIChange dell'istanza di classe viene
modificato dalla chiamata al metodo ClassTaker perché il metodo usa l'indirizzo nel parametro per trovare il
campo specificato dell'istanza di classe. Il campo willIChange dello struct nel metodo chiamante non viene
modificato dalla chiamata al metodo StructTaker perché il valore dell'argomento è una copia dello struct, non
una copia del relativo indirizzo. StructTaker modifica la copia e la copia viene persa quando la chiamata a
StructTaker viene completata.

Esempio
using System;

class TheClass
{
public string willIChange;
}

struct TheStruct
{
public string willIChange;
}

class TestClassAndStruct
{
static void ClassTaker(TheClass c)
{
c.willIChange = "Changed";
}

static void StructTaker(TheStruct s)


{
s.willIChange = "Changed";
}

static void Main()


{
TheClass testClass = new TheClass();
TheStruct testStruct = new TheStruct();

testClass.willIChange = "Not Changed";


testStruct.willIChange = "Not Changed";

ClassTaker(testClass);
StructTaker(testStruct);

Console.WriteLine("Class field = {0}", testClass.willIChange);


Console.WriteLine("Struct field = {0}", testStruct.willIChange);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/

Vedere anche
Guida per programmatori C#
Classi
Tipi di struttura
Passaggio di parametri
Variabili locali tipizzate in modo implicito - Guida
per programmatori C#
02/11/2020 • 9 minutes to read • Edit Online

Le variabili locali possono essere dichiarate senza specificare un tipo esplicito. La parola chiave var indica al
compilatore di dedurre il tipo della variabile dall'espressione sul lato destro dell'istruzione di inizializzazione. Il
tipo derivato può essere un tipo incorporato, un tipo anonimo, un tipo definito dall'utente o un tipo definito
nella libreria di classi .NET. Per altre informazioni su come inizializzare matrici con var , vedere Matrici tipizzate
in modo implicito.
Gli esempi seguenti illustrano svariati modi in cui le variabili locali possono essere dichiarate con var :

// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>


// or perhaps IQueryable<Customer>
var expr =
from c in customers
where c.City == "London"
select c;

// anon is compiled as an anonymous type


var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>


var list = new List<int>();

È importante comprendere che la parola chiave var non significa "variant" e che non indica che la variabile è
debolmente tipizzata o ad associazione tardiva. Significa semplicemente che il compilatore determina e assegna
il tipo più adatto.
È possibile usare la parola chiave var nei contesti seguenti:
Per variabili locali (variabili dichiarate nell'ambito del metodo), come illustrato nell'esempio precedente.
In un'istruzione di inizializzazione for.

for (var x = 1; x < 10; x++)

In un'istruzione di inizializzazione foreach.

foreach (var item in list) {...}

In un'istruzione using.
using (var file = new StreamReader("C:\\myfile.txt")) {...}

Per ulteriori informazioni, vedere come utilizzare variabili e matrici locali tipizzate in modo implicito in
un'espressione di query.

var e tipi anonimi


In molti casi l'uso di var è facoltativo ed è solo una convenzione sintattica. Se però una variabile viene
inizializzata con un tipo anonimo e si deve accedere alle proprietà dell'oggetto in un momento successivo, è
necessario dichiarare la variabile come var . Si tratta di uno scenario comune nelle espressioni di query LINQ.
Per ulteriori informazioni, vedere tipi anonimi.
Dal punto di vista del codice sorgente, un tipo anonimo non ha nome. Se una variabile di query è stata
inizializzata con var , l'unico modo per accedere alle proprietà nella sequenza di oggetti restituita è usare var
come tipo della variabile di iterazione nell'istruzione foreach .

class ImplicitlyTypedLocals2
{
static void Main()
{
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };

// If a query produces a sequence of anonymous types,


// then use var in the foreach statement to access the properties.
var upperLowerWords =
from w in words
select new { Upper = w.ToUpper(), Lower = w.ToLower() };

// Execute the query


foreach (var ul in upperLowerWords)
{
Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
}
}
}
/* Outputs:
Uppercase: APPLE, Lowercase: apple
Uppercase: BLUEBERRY, Lowercase: blueberry
Uppercase: CHERRY, Lowercase: cherry
*/

Commenti
Alle dichiarazioni di variabili tipizzate in modo implicito si applicano le restrizioni seguenti:
La parola chiave var può essere usata solo quando una variabile locale viene dichiarata e inizializzata
nella stessa istruzione. La variabile non può essere inizializzata su null, su un gruppo di metodi o su una
funzione anonima.
Non è possibile usare var nei campi nell'ambito della classe.
Le variabili dichiarate tramite var non possono essere usate nell'espressione di inizializzazione. In altre
parole, questa espressione è valida: int i = (i = 20); ma questa espressione genera un errore in fase di
compilazione: var i = (i = 20);
Non è possibile inizializzare più variabili tipizzate in modo implicito nella stessa istruzione.
Se un tipo denominato var rientra nell'ambito, la parola chiave var si risolverà in tale nome di tipo e
non verrà trattata come parte di una dichiarazione di variabile locale tipizzata in modo implicito.
La tipizzazione implicita con la parola chiave var può essere applicata solo alle variabili nell'ambito del metodo
locale. La tipizzazione implicita non è disponibile per i campi di classe perché il compilatore C# riscontrerebbe
un paradosso logico durante l'elaborazione del codice: il compilatore deve conoscere il tipo del campo, ma è in
grado di determinare il tipo solo dopo che l'espressione di assegnazione è stata analizzata e l'espressione non
può essere valutata senza conoscere il tipo. Osservare il codice seguente:

private var bookTitles;

bookTitles è un campo di classe a cui è stato assegnato il tipo var . Poiché il campo non ha alcuna espressione
da valutare, è impossibile per il compilatore dedurre quale tipo dovrebbe essere bookTitles . Inoltre, anche
l'aggiunta di un'espressione al campo, come si farebbe per una variabile locale, risulta insufficiente:

private var bookTitles = new List<string>();

Quando il compilatore rileva i campi durante la compilazione del codice, registra il tipo di ogni campo prima di
elaborare le espressioni a esso associate. Durante il tentativo di analisi di bookTitles , il compilatore rileva lo
stesso paradosso: ha necessità di conoscere il tipo del campo, ma il compilatore normalmente determina il tipo
di var tramite l'analisi dell'espressione, operazione che non è possibile eseguire senza conoscere prima il tipo.
var può anche risultare utile con le espressioni di query in cui è difficile determinare con esattezza il tipo
costruito della variabile della query, ad esempio con il raggruppamento e l'ordinamento di operazioni.
La parola chiave var può essere utile anche quando il tipo specifico della variabile risulta difficile da digitare
con la tastiera, è ovvio o non migliora la leggibilità del codice. var è utile in tal senso ad esempio con i tipi
generici annidati, quali quelli usati con le operazioni di gruppo. Nella query seguente il tipo della variabile di
query è IEnumerable<IGrouping<string, Student>> . Purché ciò sia chiaro a tutti coloro che devono gestire il
codice, l'uso della tipizzazione implicita per ragioni di praticità e brevità non costituisce un problema.

// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;

L'uso di var consente di semplificare il codice, ma il suo uso dovrebbe essere limitato ai casi in cui è necessario
o quando rende il codice più facile da leggere. Per altre informazioni su quando usare var correttamente,
vedere la sezione variabili locali tipizzate in modo implicito nell'articolo linee guida per il codice C#.

Vedi anche
Riferimenti per C#
Matrici tipizzate in modo implicito
Come usare variabili e matrici locali tipizzate in modo implicito in un'espressione di query
Tipi anonimi
Inizializzatori di oggetto e di raccolta
var
LINQ in C#
LINQ (Language-Integrated Query)
for
foreach, in
Istruzione using
Come usare le matrici e le variabili locali tipizzate in
modo implicito in un'espressione di query (Guida
per programmatori C#)
28/01/2021 • 3 minutes to read • Edit Online

È possibile usare le variabili locali tipizzate in modo implicito ogni volta che si vuole che il compilatore determini
il tipo di una variabile locale. È necessario usare le variabili locali tipizzate in modo implicito per archiviare i tipi
anonimi, che vengono spesso usati nelle espressioni di query. Gli esempi seguenti illustrano tipi d'uso facoltativi
e obbligatori delle variabili locali tipizzate in modo implicito nelle query.
Le variabili locali tipizzate in modo implicito vengono dichiarate usando la parola chiave contestuale var. Per
altre informazioni, vedere Variabili locali tipizzate in modo implicito e Matrici tipizzate in modo implicito.

Esempio
L'esempio seguente mostra uno scenario comune in cui la parola chiave var è obbligatoria: un'espressione di
query che produce una sequenza di tipi anonimi. In questo scenario sia la variabile di query che la variabile di
iterazione nell'istruzione foreach devono essere tipizzate in modo implicito usando var , perché non si ha
accesso a un nome di tipo per il tipo anonimo. Per altre informazioni sui tipi anonimi, vedere Tipi anonimi.

private static void QueryNames(char firstLetter)


{
// Create the query. Use of var is required because
// the query produces a sequence of anonymous types:
// System.Collections.Generic.IEnumerable<????>.
var studentQuery =
from student in students
where student.FirstName[0] == firstLetter
select new { student.FirstName, student.LastName };

// Execute the query and display the results.


foreach (var anonType in studentQuery)
{
Console.WriteLine("First = {0}, Last = {1}", anonType.FirstName, anonType.LastName);
}
}

Esempio
L'esempio seguente usa la parola chiave var in una situazione simile, ma in cui l'uso di var è facoltativo.
Poiché student.LastName è una stringa, l'esecuzione della query restituisce una sequenza di stringhe. Il tipo di
queryID può quindi essere dichiarato come System.Collections.Generic.IEnumerable<string> invece di var . La
parola chiave var viene usata per praticità. Nell'esempio la variabile di iterazione nell'istruzione foreach è
tipizzata in modo esplicito come stringa, ma potrebbe invece essere dichiarata usando var . Poiché il tipo della
variabile di iterazione non è un tipo anonimo, l'uso di var è facoltativo e non obbligatorio. Tenere presente che
var non è un tipo, ma un'istruzione che indica al compilatore di derivare tramite inferenza e assegnare il tipo.
// Variable queryId could be declared by using
// System.Collections.Generic.IEnumerable<string>
// instead of var.
var queryId =
from student in students
where student.Id > 111
select student.LastName;

// Variable str could be declared by using var instead of string.


foreach (string str in queryId)
{
Console.WriteLine("Last name: {0}", str);
}

Vedi anche
Guida per programmatori C#
Metodi di estensione
LINQ (Language-Integrated Query)
var
LINQ in C#
Metodi di estensione (Guida per programmatori
C#)
02/11/2020 • 17 minutes to read • Edit Online

I metodi di estensione consentono di "aggiungere" metodi ai tipi esistenti senza creare un nuovo tipo derivato,
ricompilare o modificare in altro modo il tipo originale. I metodi di estensione sono metodi statici, ma vengono
chiamati come se fossero metodi di istanza sul 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.
I metodi di estensione più comuni sono gli operatori di query standard LINQ che aggiungono la funzionalità di
query ai System.Collections.IEnumerable tipi e esistenti System.Collections.Generic.IEnumerable<T> . Per
utilizzare gli operatori query standard, inserirli innanzitutto nell'ambito con una direttiva using System.Linq . In
questo modo qualsiasi tipo che implementa IEnumerable<T> avrà apparentemente metodi di istanza quali
GroupBy, OrderBy, Averagee così via. È possibile visualizzare questi metodi aggiuntivi con la funzionalità di
completamento istruzioni di IntelliSense quando si digita "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 OrderBy dell'operatore query standard su una
matrice di Integer. L'espressione tra parentesi è un'espressione lambda. Molti operatori di query standard
accettano espressioni lambda come parametri, ma questo non è un requisito per i metodi di estensione. Per altre
informazioni, vedere espressioni lambda.

class ExtensionMethods2
{

static void Main()


{
int[] ints = { 10, 45, 15, 39, 21, 26 };
var result = ints.OrderBy(g => g);
foreach (var i in result)
{
System.Console.Write(i + " ");
}
}
}
//Output: 10 15 21 26 39 45

I metodi di estensione sono definiti come metodi statici, ma vengono chiamati utilizzando la sintassi del metodo
di istanza. Il primo parametro specifica il tipo su cui opera il metodo. Il parametro è preceduto dal modificatore
this . I metodi di estensione si trovano nell'ambito solo quando si importa in modo esplicito lo spazio dei nomi
nel codice sorgente con una direttiva using .
Nell'esempio riportato di seguito viene illustrato un metodo di estensione definito per la classe System.String.
Viene definito all'interno di una classe statica non annidata e non generica:
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

Il metodo di estensione WordCount può essere inserito nell'ambito con questa direttiva using :

using ExtensionMethods;

Può inoltre essere chiamato da un'applicazione utilizzando questa sintassi:

string s = "Hello Extension Methods";


int i = s.WordCount();

Il metodo di estensione viene richiamato nel codice con la sintassi del metodo di istanza. Il linguaggio
intermedio (IL) generato dal compilatore converte il codice in una chiamata sul metodo statico. Il principio di
incapsulamento non viene effettivamente violato. I metodi di estensione non possono accedere a variabili
private nel tipo che stanno estendendo.
Per ulteriori informazioni, vedere come implementare e chiamare un metodo di estensione personalizzato.
In generale, probabilmente si chiamerà metodi di estensione molto più spesso rispetto all'implementazione di
un proprio. Perché i metodi di estensione vengono chiamati utilizzando la sintassi del metodo di istanza, non è
necessaria alcuna particolare conoscenza per utilizzarli dal codice client. Per abilitare i metodi di estensione per
un particolare tipo, aggiungere una direttiva using per lo spazio dei nomi nel quale sono definiti i metodi. Per
utilizzare ad esempio gli operatori query standard, aggiungere questa direttiva using al codice:

using System.Linq;

Potrebbe anche essere necessario aggiungere un riferimento a System.Core.dll. Si noterà che gli operatori di
query standard vengono ora visualizzati in IntelliSense come metodi aggiuntivi disponibili per la maggior parte
dei IEnumerable<T> tipi.

Associazione di metodi di estensione in fase di compilazione


È possibile utilizzare metodi di estensione per estendere una classe o un'interfaccia, ma non per eseguirne
l'override. Un metodo di estensione con lo stesso nome e la stessa firma di un metodo di interfaccia o di classe
non verrà mai chiamato. In fase di compilazione, i metodi di estensione hanno sempre una priorità più bassa dei
metodi di istanza definiti nel tipo stesso. In altre parole, se un tipo dispone di un metodo denominato
Process(int i) e si dispone di un metodo di estensione con la stessa firma, il compilatore eseguirà sempre
l'associazione al metodo di istanza. Quando il compilatore rileva una chiamata al metodo, cerca innanzitutto una
corrispondenza nei metodi di istanza del tipo. Se non viene trovata alcuna corrispondenza, cercherà eventuali
metodi di estensione definiti per il tipo ed eseguirà l'associazione al primo metodo di estensione trovato.
Nell'esempio seguente viene dimostrato come il compilatore determina a quale metodo di estensione o metodo
di istanza eseguire l'associazione.
Esempio
Nell'esempio seguente vengono illustrate le regole che il compilatore C# segue nel determinare se associare
una chiamata al metodo a un metodo di istanza sul tipo o a un metodo di estensione. La classe Extensions
statica contiene metodi di estensione definiti per qualsiasi tipo che implementa IMyInterface . Le classi A , B e
C implementano tutte l'interfaccia.

Il metodo di estensione MethodB non viene mai chiamato perché il nome e la firma corrispondono esattamente
a metodi già implementati dalle classi.
Quando il compilatore non riesce a trovare un metodo di istanza con una firma corrispondente, eseguirà
l'associazione a un metodo di estensione corrispondente se ne esiste uno.

// Define an interface named IMyInterface.


namespace DefineIMyInterface
{
using System;

public interface IMyInterface


{
// Any class that implements IMyInterface must define a method
// that matches the following signature.
void MethodB();
}
}

// Define extension methods for IMyInterface.


namespace Extensions
{
using System;
using DefineIMyInterface;

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

// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
using System;
using Extensions;
using DefineIMyInterface;
using DefineIMyInterface;

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

class ExtMethodDemo
{
static void Main(string[] args)
{
// 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.MethodA(object obj)
C.MethodB()
*/

Modelli di utilizzo comuni


Funzionalità di raccolta
In passato, era comune creare "classi di raccolta" che implementavano l'
System.Collections.Generic.IEnumerable<T> interfaccia per un determinato tipo e conteneva la funzionalità che
agiva sulle raccolte di quel tipo. Anche se non c'è niente di sbagliato nella creazione di questo tipo di oggetto
Collection, è possibile ottenere la stessa funzionalità usando un'estensione in
System.Collections.Generic.IEnumerable<T> . Le estensioni offrono il vantaggio di consentire la chiamata della
funzionalità da qualsiasi raccolta, ad esempio System.Array o System.Collections.Generic.List<T> che
implementi System.Collections.Generic.IEnumerable<T> in tale tipo. Un esempio di questo esempio di utilizzo di
una matrice di Int32 è disponibile in precedenza in questo articolo.
Funzionalità specifiche del livello
Quando si usa un'architettura a cipolla o un'altra progettazione di applicazioni a più livelli, è comune avere un
set di entità di dominio o oggetti Trasferimento dati che possono essere usati per comunicare tra 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 per ogni livello dell'applicazione senza caricare l'oggetto con metodi non necessari o
desiderati in altri livelli.

public class DomainEntity


{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

static class DomainEntityExtensions


{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}

Estensione di tipi predefiniti


Anziché 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 i metodi di estensione, è possibile
creare Engine una Query classe o per eseguire il lavoro di esecuzione di una query su un SQL Server che può
essere chiamato da più posizioni nel codice. Tuttavia, è possibile estendere la
System.Data.SqlClient.SqlConnection classe usando i metodi di estensione per eseguire la query da qualsiasi
luogo in cui è presente una connessione a una SQL Server. Altri esempi potrebbero consistere nell'aggiungere
funzionalità comuni alla System.String classe, estendere le funzionalità di elaborazione dei dati System.IO.File
degli System.IO.Stream oggetti e e System.Exception gli oggetti per una specifica funzionalità di gestione degli
errori. Questi tipi di casi d'uso sono limitati solo dalla propria immaginazione e dal buon senso.
L'estensione dei tipi predefiniti può essere difficile con i struct tipi perché vengono passati per valore ai
metodi. Ciò significa che vengono apportate modifiche allo struct a una copia dello struct. Queste modifiche non
sono visibili quando il metodo di estensione viene chiuso. A partire da C# 7,2, è possibile aggiungere il ref
modificatore al primo argomento di un metodo di estensione. L'aggiunta del ref modificatore indica che il
primo argomento viene passato per riferimento. In questo modo è possibile scrivere metodi di estensione che
modificano lo stato dello struct esteso.
Linee guida generali
Sebbene sia ancora considerato preferibile aggiungere funzionalità modificando il codice di un oggetto o
derivando un nuovo tipo ogni volta che è ragionevole ed è possibile farlo, i metodi di estensione sono diventati
un'opzione cruciale per la creazione di funzionalità riutilizzabili nell'ecosistema .NET. Per le situazioni in cui
l'origine originale non è sotto il controllo, quando un oggetto derivato è inappropriato o impossibile oppure
quando la funzionalità non deve essere esposta oltre l'ambito applicabile, i metodi di estensione rappresentano
una scelta ottimale.
Per ulteriori informazioni sui tipi derivati, vedere ereditarietà.
Quando si usa un metodo di estensione per estendere un tipo il cui codice sorgente non è in grado di
controllare, si corre il rischio che una modifica nell'implementazione del tipo provochi l'interruzione del metodo
di estensione.
Se si implementano metodi di estensione per un determinato tipo, è importante tenere presente quanto segue:
Un metodo di estensione non verrà mai chiamato se dispone della stessa firma di un metodo definito nel
tipo.
I metodi di estensione vengono inseriti nell'ambito al livello dello spazio dei nomi. Se, ad esempio, sono
presenti più classi statiche che contengono metodi di estensione in un singolo spazio dei nomi denominato
Extensions , verranno tutti inseriti nell'ambito using Extensions; dalla direttiva.

Per una libreria di classi implementata, non è necessario utilizzare i metodi di estensione per evitare
l'incremento del numero di versione di un assembly. Se si desidera aggiungere funzionalità significative a una
libreria per la quale si è proprietari del codice sorgente, attenersi alle linee guida di .NET per il controllo delle
versioni degli assembly. Per altre informazioni, vedere Controllo delle versioni degli assembly.

Vedere anche
Guida per programmatori C#
Esempi di programmazione parallela (sono inclusi molti metodi di estensione di esempio)
Espressioni lambda
Cenni preliminari sugli operatori di query standard
Regole di conversione per parametri Instance e relativo impatto
Interoperabilità dei metodi di estensione tra linguaggi
Metodi di estensione e delegati sottoposti a currying
Associazione di metodi di estensione e segnalazione errori
Come implementare e chiamare un metodo di
estensione personalizzato (Guida per
programmatori C#)
28/01/2021 • 3 minutes to read • Edit Online

Questo argomento illustra come implementare metodi di estensione personali per qualsiasi tipo .NET. Il codice
client può usare i metodi di estensione aggiungendo un riferimento alla DLL che li contiene, e aggiungendo una
direttiva using che specifica lo spazio dei nomi in cui vengono definiti i metodi di estensione.

Per definire e chiamare il metodo di estensione


1. Definire una classe statica per contenere il metodo di estensione.
La classe deve essere visibile al codice client. Per altre informazioni sulle regole di accessibilità, vedere
Modificatori di accesso.
2. Implementare il metodo di estensione come metodo statico con almeno la stessa visibilità della classe
che lo contiene.
3. Il primo parametro del metodo specifica il tipo su cui il metodo opera e deve essere preceduto dal
modificatore this.
4. Nel codice chiamante, aggiungere una direttiva using per specificare lo spazio dei nomi che contiene la
classe del metodo di estensione.
5. Chiamare i metodi come se fossero metodi di istanza sul tipo.
Si noti che il primo parametro non viene specificato tramite la chiamata di codice, poiché rappresenta il
tipo su cui viene applicato l'operatore e il compilatore conosce già il tipo dell'oggetto. È sufficiente
specificare gli argomenti per i parametri da 2 a n .

Esempio
Nell'esempio seguente un metodo di estensione denominato WordCount viene implementato nella classe
CustomExtensions.StringExtension . Il metodo opera sulla classe String, che viene specificata come primo
parametro del metodo. Lo spazio dei nomi CustomExtensions viene importato nello spazio dei nomi
dell'applicazione e il metodo viene chiamato all'interno del metodo Main .
using System.Linq;
using System.Text;
using System;

namespace CustomExtensions
{
// Extension methods must be defined in a static class.
public static class StringExtension
{
// This is the extension method.
// The first parameter takes the "this" modifier
// and specifies the type for which the method is defined.
public static int WordCount(this String str)
{
return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
namespace Extension_Methods_Simple
{
// Import the extension method namespace.
using CustomExtensions;
class Program
{
static void Main(string[] args)
{
string s = "The quick brown fox jumped over the lazy dog.";
// Call the method as if it were an
// instance method on the type. Note that the first
// parameter is not specified by the calling code.
int i = s.WordCount();
System.Console.WriteLine("Word count of s is {0}", i);
}
}
}

Sicurezza .NET
I metodi di estensione non presentano vulnerabilità di protezione specifiche. Non possono mai essere usati per
rappresentare metodi esistenti su un tipo, perché tutti i conflitti di nomi vengono risolti a favore dell'istanza o
del metodo statico definito dal tipo stesso. I metodi di estensione non possono accedere ai dati privati nella
classe estesa.

Vedi anche
Guida per programmatori C#
Metodi di estensione
LINQ (Language-Integrated Query)
Classi statiche e membri di classi statiche
protected
internal
public
this
namespace
Come creare un nuovo metodo per
un'enumerazione (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

È possibile usare metodi di estensione per aggiungere la funzionalità specifica di un particolare tipo enum.

Esempio
Nell'esempio seguente, l'enumerazione Grades rappresenta il voto che uno studente potrebbe ricevere in un
corso. Il metodo di estensione denominato Passing viene aggiunto al tipo Grades in modo che ogni istanza di
tale tipo ora "sa" se rappresenta un voto sufficiente oppure no.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace EnumExtension
{
// Define an extension method in a non-nested static class.
public static class Extensions
{
public static Grades minPassing = Grades.D;
public static bool Passing(this Grades grade)
{
return grade >= minPassing;
}
}

public enum Grades { F = 0, D=1, C=2, B=3, A=4 };


class Program
{
static void Main(string[] args)
{
Grades g1 = Grades.D;
Grades g2 = Grades.F;
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");

Extensions.minPassing = Grades.C;
Console.WriteLine("\r\nRaising the bar!\r\n");
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
}
}
}
/* Output:
First is a passing grade.
Second is not a passing grade.

Raising the bar!

First is not a passing grade.


Second is not a passing grade.
*/

Si noti che la classe Extensions contiene anche una variabile statica aggiornata in modo dinamico e che il
valore restituito dal metodo di estensione riflette il valore corrente di tale variabile. Ciò dimostra che dietro le
quinte i metodi di estensione vengono chiamati direttamente per la classe statica nella quale sono definiti.

Vedi anche
Guida per programmatori C#
Metodi di estensione
Argomenti denominati e facoltativi (Guida per
programmatori C#)
28/01/2021 • 12 minutes to read • Edit Online

In C# 4 sono stati introdotti gli argomenti denominati e facoltativi. Gli argomenti denominati consentono di
specificare un argomento per un parametro eseguendo la corrispondenza dell'argomento con il relativo nome
invece che con la relativa posizione nell'elenco di parametri. Gli argomenti facoltativi consentono di omettere gli
argomenti per alcuni parametri. Entrambe le tecniche possono essere usate con i metodi, gli indicizzatori, i
costruttori e i delegati.
Quando si usano gli argomenti denominati e facoltativi, gli argomenti vengono valutati nell'ordine nel quale
sono visualizzati nell'elenco di argomenti, non nell'elenco di parametri.
I parametri denominati e facoltativi consentono di fornire argomenti per i parametri selezionati. Questa
funzionalità semplifica significativamente le chiamate a interfacce COM, ad esempio le API di automazione del
Microsoft Office.

Argomenti denominati
Gli argomenti denominati non consentono di abbinare l'ordine dei parametri negli elenchi di parametri dei
metodi chiamati. Il parametro per ogni argomento può essere specificato dal nome del parametro. Ad esempio,
una funzione che stampa i dettagli dell'ordine, ad esempio il nome del venditore, il numero dell'ordine & il
nome del prodotto, può essere chiamato inviando argomenti in base alla posizione, nell'ordine definito dalla
funzione.

PrintOrderDetails("Gift Shop", 31, "Red Mug");

Se non si ricorda l'ordine dei parametri ma si conoscono i nomi, è possibile inviare gli argomenti in qualsiasi
ordine.

PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");


PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

Gli argomenti denominati migliorano anche la leggibilità del codice identificando che cosa rappresenta ogni
argomento. Nel metodo di esempio riportato di seguito, sellerName non può essere null o uno spazio vuoto.
sellerName e productName sono di tipi stringa, quindi, anziché inviare argomenti in base alla posizione, è
opportuno usare argomenti denominati per evitare ambiguità tra i due e semplificare la lettura del codice.
Se usati con argomenti posizionali, gli argomenti denominati sono validi se
non sono seguiti da argomenti posizionali, o

PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");

iniziando con C# 7.2, vengono usati nella posizione corretta. Nell'esempio seguente il parametro
orderNum è nella posizione corretta, ma non è denominato in modo esplicito.
PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");

Gli argomenti posizionali che seguono gli argomenti denominati non ordinati non sono validi.

// This generates CS1738: Named argument specifications must appear after all fixed arguments have been
specified.
PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");

Esempio
Il codice seguente implementa gli esempi di questa e altre sezioni.

class NamedExample
{
static void Main(string[] args)
{
// The method can be called in the normal way, by using positional arguments.
PrintOrderDetails("Gift Shop", 31, "Red Mug");

// Named arguments can be supplied for the parameters in any order.


PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

// Named arguments mixed with positional arguments are valid


// as long as they are used in their correct position.
PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug"); // C# 7.2 onwards
PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug"); // C# 7.2 onwards

// However, mixed arguments are invalid if used out-of-order.


// The following statements will cause a compiler error.
// PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");
// PrintOrderDetails(31, sellerName: "Gift Shop", "Red Mug");
// PrintOrderDetails(31, "Red Mug", sellerName: "Gift Shop");
}

static void PrintOrderDetails(string sellerName, int orderNum, string productName)


{
if (string.IsNullOrWhiteSpace(sellerName))
{
throw new ArgumentException(message: "Seller name cannot be null or empty.", paramName:
nameof(sellerName));
}

Console.WriteLine($"Seller: {sellerName}, Order #: {orderNum}, Product: {productName}");


}
}

Argomenti facoltativi
La definizione di un metodo, un costruttore, un indicizzatore o un delegato può specificare che i parametri sono
obbligatori o facoltativi. Tutte le chiamate devono specificare gli argomenti per tutti i parametri obbligatori, ma
possono omettere gli argomenti per i parametri facoltativi.
Ogni parametro facoltativo ha un valore predefinito incluso nella definizione. Se per il parametro non viene
inviato alcun argomento, viene usato il valore predefinito. Il valore predefinito deve essere uno dei tipi di
espressioni seguenti:
un'espressione costante;
un'espressione del form new ValType() , dove ValType è un tipo di valore, ad esempio enum o struct;
un'espressione del form default(ValType), dove ValType è un tipo di valore.
I parametri facoltativi sono definiti alla fine dell'elenco di parametri, dopo eventuali parametri obbligatori. Se il
chiamante specifica un argomento per un parametro di una successione di parametri facoltativi, deve specificare
gli argomenti per tutti i parametri facoltativi precedenti. Non sono supportati i gap delimitati da virgole
nell'elenco di argomenti. Nel codice seguente, ad esempio, il metodo di istanza ExampleMethod viene definito
con un parametro obbligatorio e due parametri facoltativi.

public void ExampleMethod(int required, string optionalstr = "default string",


int optionalint = 10)

La chiamata seguente a ExampleMethod genera un errore del compilatore, poiché viene specificato un
argomento per il terzo parametro ma non per il secondo.

//anExample.ExampleMethod(3, ,4);

Se, tuttavia, si conosce il nome del terzo parametro, è possibile usare un argomento denominato per eseguire
l'attività.

anExample.ExampleMethod(3, optionalint: 4);

IntelliSense usa le parentesi per indicare parametri facoltativi, come illustrato nell'immagine seguente:

NOTE
È possibile anche dichiarare parametri facoltativi usando la classe .NET OptionalAttribute. I parametri OptionalAttribute
non richiedono un valore predefinito.

Esempio
Nell'esempio seguente il costruttore per ExampleClass ha un solo parametro facoltativo. Il metodo di istanza
ExampleMethod ha un solo parametro obbligatorio, required , e due parametri facoltativi, optionalstr e
optionalint . Il codice in Main illustra i diversi modi in cui è possibile richiamare il costruttore e il metodo.

namespace OptionalNamespace
{
class OptionalExample
{
static void Main(string[] args)
{
// Instance anExample does not send an argument for the constructor's
// optional parameter.
ExampleClass anExample = new ExampleClass();
anExample.ExampleMethod(1, "One", 1);
anExample.ExampleMethod(2, "Two");
anExample.ExampleMethod(3);

// Instance anotherExample sends an argument for the constructor's


// optional parameter.
ExampleClass anotherExample = new ExampleClass("Provided name");
ExampleClass anotherExample = new ExampleClass("Provided name");
anotherExample.ExampleMethod(1, "One", 1);
anotherExample.ExampleMethod(2, "Two");
anotherExample.ExampleMethod(3);

// The following statements produce compiler errors.

// An argument must be supplied for the first parameter, and it


// must be an integer.
//anExample.ExampleMethod("One", 1);
//anExample.ExampleMethod();

// You cannot leave a gap in the provided arguments.


//anExample.ExampleMethod(3, ,4);
//anExample.ExampleMethod(3, 4);

// You can use a named parameter to make the previous


// statement work.
anExample.ExampleMethod(3, optionalint: 4);
}
}

class ExampleClass
{
private string _name;

// Because the parameter for the constructor, name, has a default


// value assigned to it, it is optional.
public ExampleClass(string name = "Default name")
{
_name = name;
}

// The first parameter, required, has no default value assigned


// to it. Therefore, it is not optional. Both optionalstr and
// optionalint have default values assigned to them. They are optional.
public void ExampleMethod(int required, string optionalstr = "default string",
int optionalint = 10)
{
Console.WriteLine(
$"{_name}: {required}, {optionalstr}, and {optionalint}.");
}
}

// The output from this example is the following:


// Default name: 1, One, and 1.
// Default name: 2, Two, and 10.
// Default name: 3, default string, and 10.
// Provided name: 1, One, and 1.
// Provided name: 2, Two, and 10.
// Provided name: 3, default string, and 10.
// Default name: 3, default string, and 4.
}

Il codice precedente mostra alcuni esempi in cui i parametri facoltativi non vengono applicati correttamente. Il
primo illustra che è necessario specificare un argomento per il primo parametro, che è obbligatorio.

Interfacce COM
Gli argomenti denominati e facoltativi, oltre al supporto per oggetti dinamici, migliorano significativamente
l'interoperabilità con le API COM, ad esempio le API di automazione di Office.
Ad esempio, il metodo AutoFormat nell'interfaccia Range di Microsoft Office Excel ha sette parametri facoltativi.
Questi parametri sono illustrati nell'immagine seguente:
In C# 3.0 e versioni precedenti è necessario un argomento per ogni parametro, come illustrato nell'esempio
seguente.

// In C# 3.0 and earlier versions, you need to supply an argument for


// every parameter. The following call specifies a value for the first
// parameter, and sends a placeholder value for the other six. The
// default values are used for those parameters.
var excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Workbooks.Add();
excelApp.Visible = true;

var myFormat =
Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting1;

excelApp.get_Range("A1", "B4").AutoFormat(myFormat, Type.Missing,


Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

È tuttavia possibile semplificare in modo sostanziale la chiamata a AutoFormat mediante argomenti denominati
e facoltativi, introdotti in C# 4.0. Gli argomenti denominati e facoltativi consentono di omettere l'argomento per
un parametro facoltativo se non si vuole modificare il valore predefinito del parametro. Nella chiamata seguente
viene specificato un valore per uno solo dei sette parametri.

// The following code shows the same call to AutoFormat in C# 4.0. Only
// the argument for which you want to provide a specific value is listed.
excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );

Per altre informazioni ed esempi, vedere come usare gli argomenti denominati e facoltativi nella
programmazione di Office e come accedere agli oggetti di interoperabilità di Office usando le funzionalità di C#.

Overload Resolution
L'uso di argomenti denominati e facoltativi influisce sulla risoluzione dell'overload nei modi seguenti:
Un metodo, un indicizzatore o un costruttore è un candidato per l'esecuzione se ogni parametro è facoltativo
o corrisponde, per nome o per posizione, a un solo argomento nell'istruzione chiamante e tale argomento
può essere convertito nel tipo del parametro.
Se è disponibile più di un candidato, agli argomenti specificati in modo esplicito vengono applicate le regole
di risoluzione dell'overload per le conversioni preferite. Gli argomenti omessi per i parametri facoltativi
vengono ignorati.
Se due candidati vengono giudicati ugualmente validi, la preferenza passa a un candidato che non dispone di
parametri facoltativi per i quali gli argomenti sono stati omessi nella chiamata. La risoluzione dell'overload
preferisce in genere i candidati con un minor numero di parametri.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Come usare argomenti denominati e facoltativi nella
programmazione di Office (Guida per
programmatori C#)
28/01/2021 • 8 minutes to read • Edit Online

Gli argomenti denominati e gli argomenti facoltativi, introdotti in C# 4, migliorano la praticità, la flessibilità e la
leggibilità nella programmazione C#. Queste funzionalità, poi, semplificano notevolmente l'accesso alle
interfacce COM, quali le API di automazione di Microsoft Office.
Nell'esempio seguente, il metodo ConvertToTable dispone di sedici parametri che rappresentano le
caratteristiche di una tabella, ad esempio il numero di colonne e di righe, la formattazione, i bordi, i tipi di
carattere e i colori. I sedici parametri sono tutti facoltativi, perché nella maggior parte dei casi non si vogliono
specificare particolari valori per tutti. Senza gli argomenti denominati e facoltativi, tuttavia, è necessario
specificare un valore o un valore di segnaposto per ogni parametro. Con gli argomenti denominati e facoltativi
si specificano valori solo per i parametri obbligatori per il progetto.
Per eseguire queste procedure, Microsoft Office Word deve essere installato.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Per creare un nuovo progetto di applicazione console


1. Avviare Visual Studio.
2. Scegliere Nuovo dal menu File e quindi fare clic su Progetto .
3. Nel riquadro Categorie di modelli espandere Visual C# e quindi fare clic su Windows .
4. Verificare che nella parte superiore del riquadro Modelli sia visualizzato .NET Framework 4 nella
casella Framework di destinazione .
5. Nel riquadro Modelli fare clic su Applicazione console .
6. Digitare un nome per il progetto nel campo Nome .
7. Fare clic su OK .
Il nuovo progetto verrà visualizzato in Esplora soluzioni .

Per aggiungere un riferimento


1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e quindi scegliere
Aggiungi riferimento . Verrà visualizzata la finestra di dialogo Aggiungi riferimento .
2. Nella pagina .NET selezionare Microsoft.Office.Interop.Word nell'elenco Nome componente .
3. Fare clic su OK .
Per aggiungere le direttive using necessarie
1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul file Program.cs e quindi fare clic su
Visualizza codice .
2. Aggiungere le seguenti using direttive all'inizio del file di codice:

using Word = Microsoft.Office.Interop.Word;

Per visualizzare il testo in un documento di Word


1. Nella Program classe in Program.cs aggiungere il metodo seguente per creare un'applicazione di Word e
un documento di Word. Il metodo Add dispone di quattro parametri facoltativi. Questo esempio usa i
relativi valori predefiniti. Non sono pertanto necessari argomenti nell'istruzione di chiamata.

static void DisplayInWord()


{
var wordApp = new Word.Application();
wordApp.Visible = true;
// docs is a collection of all the Document objects currently
// open in Word.
Word.Documents docs = wordApp.Documents;

// Add a document to the collection and name it doc.


Word.Document doc = docs.Add();
}

2. Aggiungere il codice seguente alla fine del metodo per definire dove visualizzare il testo nel documento e
il testo da visualizzare:

// Define a range, a contiguous area in the document, by specifying


// a starting and ending character position. Currently, the document
// is empty.
Word.Range range = doc.Range(0, 0);

// Use the InsertAfter method to insert a string at the end of the


// current range.
range.InsertAfter("Testing, testing, testing. . .");

Per eseguire l'applicazione


1. Aggiungere la seguente istruzione a Main:

DisplayInWord();

2. Premere CTRL + F5 per eseguire il progetto. Verrà visualizzato un documento di Word contenente il testo
specificato.

Per convertire il testo in una tabella


1. Usare il metodo ConvertToTable per racchiudere il testo in una tabella. Il metodo ha 16 parametri
facoltativi. IntelliSense racchiude tra parentesi quadre i parametri facoltativi, come mostrato
nell'immagine seguente.
Gli argomenti denominati e facoltativi consentono di specificare valori esclusivamente per i parametri che
si vogliono modificare. Aggiungere il codice seguente alla fine del metodo DisplayInWord per creare una
tabella semplice. L'argomento indica che le virgole nella stringa di testo in range separano le celle della
tabella.

// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");

Nelle versioni precedenti di C# la chiamata a ConvertToTable richiede un argomento di riferimento per


ogni parametro, come illustrato nel codice seguente:

// Call to ConvertToTable in Visual C# 2008 or earlier. This code


// is not part of the solution.
var missing = Type.Missing;
object separator = ",";
range.ConvertToTable(ref separator, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing);

2. Premere CTRL + F5 per eseguire il progetto.

Per provare a usare altri parametri


1. Per modificare la tabella in modo che contenga una colonna e tre righe, sostituire l'ultima riga in
DisplayInWord con l'istruzione seguente, quindi premere CTRL + F5.

range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1);

2. Per specificare un formato predefinito per la tabella, sostituire l'ultima riga in DisplayInWord con
l'istruzione seguente, quindi premere CTRL + F5. Il formato può corrispondere a qualsiasi costante
WdTableFormat.

range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1,


Format: Word.WdTableFormat.wdTableFormatElegant);

Esempio
Il codice seguente include l'esempio completo:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeHowTo
{
class WordProgram
{
static void Main(string[] args)
{
DisplayInWord();
}

static void DisplayInWord()


{
var wordApp = new Word.Application();
wordApp.Visible = true;
// docs is a collection of all the Document objects currently
// open in Word.
Word.Documents docs = wordApp.Documents;

// Add a document to the collection and name it doc.


Word.Document doc = docs.Add();

// Define a range, a contiguous area in the document, by specifying


// a starting and ending character position. Currently, the document
// is empty.
Word.Range range = doc.Range(0, 0);

// Use the InsertAfter method to insert a string at the end of the


// current range.
range.InsertAfter("Testing, testing, testing. . .");

// You can comment out any or all of the following statements to


// see the effect of each one in the Word document.

// Next, use the ConvertToTable method to put the text into a table.
// The method has 16 optional parameters. You only have to specify
// values for those you want to change.

// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");

// Change to a single column with three rows..


range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1);

// Format the table.


range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1,
Format: Word.WdTableFormat.wdTableFormatElegant);
}
}
}

Vedi anche
Argomenti denominati e facoltativi
Costruttori (Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Quando si crea una classe o uno struct, viene chiamato il relativo costruttore. Una classe o uno struct può avere
più costruttori che accettano argomenti diversi. I costruttori consentono al programmatore di impostare i valori
predefiniti, limitare la creazione di istanze e scrivere codice flessibile e facile da leggere. Per altre informazioni ed
esempi, vedere Utilizzo di costruttori e Costruttori di istanze.

Costruttori senza parametri


Se non si specifica un costruttore per la classe, C# ne crea uno per impostazione predefinita che crea un'istanza
dell'oggetto e imposta le variabili membro sui valori predefiniti elencati nell'articolo valori predefiniti di tipi C# .
Se non si specifica un costruttore per lo struct, C# si basa su un costruttore senza parametri implicito per
inizializzare automaticamente ogni campo sul valore predefinito. Per ulteriori informazioni ed esempi, vedere
costruttori di istanze.

Sintassi del costruttore


Un costruttore è un metodo il cui nome è identico al nome del tipo relativo. La firma di questo metodo include
solo il nome metodo e l'elenco dei parametri, ma non include un tipo restituito. L'esempio seguente illustra il
costruttore di una classe denominata Person .

public class Person


{
private string last;
private string first;

public Person(string lastName, string firstName)


{
last = lastName;
first = firstName;
}

// Remaining implementation of Person class.


}

Se un costruttore può essere implementato come istruzione unica, è possibile usare una definizione del corpo
dell'espressione. L'esempio seguente definisce una classe Location il cui costruttore ha un solo parametro di
stringa denominato name. La definizione del corpo dell'espressione assegna l'argomento al campo
locationName .

public class Location


{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}
Costruttori statici
Tutti gli esempi precedenti hanno illustrato costruttori di istanza, che creano un nuovo oggetto. Una classe o uno
struct può avere anche un costruttore statico, che inizializza i membri statici del tipo. I costruttori statici non
hanno parametri. Se non si fornisce un costruttore statico per inizializzare i campi statici, il compilatore C#
Inizializza i campi statici sul valore predefinito come elencato nell'articolo valori predefiniti di tipi C# .
L'esempio seguente usa un costruttore statico per inizializzare un campo statico.

public class Adult : Person


{
private static int minimumAge;

public Adult(string lastName, string firstName) : base(lastName, firstName)


{ }

static Adult()
{
minimumAge = 18;
}

// Remaining implementation of Adult class.


}

È anche possibile definire un costruttore statico con una definizione di corpo dell'espressione, come illustrato
nell'esempio seguente.

public class Child : Person


{
private static int maximumAge;

public Child(string lastName, string firstName) : base(lastName, firstName)


{ }

static Child() => maximumAge = 18;

// Remaining implementation of Child class.


}

Per altre informazioni, vedere Costruttori statici.

Contenuto della sezione


Utilizzo di costruttori
Costruttori di istanza
Costruttori privati
Costruttori statici
Come scrivere un costruttore di copia

Vedere anche
Guida per programmatori C#
Classi e struct
Finalizzatori
static
Perché gli inizializzatori vengono eseguiti nell'ordine opposto come costruttori? Parte 1
Utilizzo di costruttori (Guida per programmatori
C#)
28/01/2021 • 7 minutes to read • Edit Online

Quando si crea una classe o uno struct viene chiamato il relativo costruttore. I costruttori hanno lo stesso nome
della classe o dello struct e in genere inizializzano i membri dati del nuovo oggetto.
Nell'esempio seguente viene definita una classe denominata Taxi usando un costruttore semplice. Viene
quindi creata un'istanza per la classe con l'operatore new. Il costruttore Taxi viene richiamato dall'operatore
new immediatamente dopo l'allocazione della memoria per il nuovo oggetto.

public class Taxi


{
public bool IsInitialized;

public Taxi()
{
IsInitialized = true;
}
}

class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
Console.WriteLine(t.IsInitialized);
}
}

Un costruttore che non accetta parametri è detto costruttore senza parametri. I costruttori senza parametri
vengono richiamati ogni volta che si crea un'istanza per un oggetto usando l'operatore new e non vengono
specificati argomenti per new . Per altre informazioni, vedere Costruttori di istanze.
A meno che la classe non sia statica, le classi senza costruttori ricevono un costruttore senza parametri pubblico
dal compilatore C# perché possano creare istanze di classi. Per altre informazioni, vedere classi statiche e
membri di classi statiche.
È possibile impedire che venga creata un'istanza per una classe rendendo il costruttore privato, come indicato di
seguito:

class NLog
{
// Private Constructor:
private NLog() { }

public static double e = Math.E; //2.71828...


}

Per altre informazioni, vedere Costruttori privati.


I costruttori per i tipi struct sono simili ai costruttori di classi, ma gli structs non possono contenere un
costruttore senza parametri esplicito poiché ne viene specificato automaticamente uno dal compilatore. Questo
costruttore inizializza ogni campo nell'oggetto sul struct valore predefinito. Il costruttore senza parametri
viene tuttavia chiamato solo se si crea un'istanza dello struct con new . Questo codice, ad esempio, usa il
costruttore senza parametri per Int32, in modo da garantire che venga inizializzato l'Integer:

int i = new int();


Console.WriteLine(i);

Il codice seguente, tuttavia, provoca un errore del compilatore perché non usa new e poiché tenta di usare un
oggetto che non è stato inizializzato:

int i;
Console.WriteLine(i);

In alternativa, gli oggetti basati su structs , inclusi tutti i tipi numerici incorporati, possono essere inizializzati o
assegnati e quindi usati come nell'esempio seguente:

int a = 44; // Initialize the value type...


int b;
b = 33; // Or assign it before using it.
Console.WriteLine("{0}, {1}", a, b);

La chiamata al costruttore senza parametri per un tipo di valore non è quindi necessaria.
Sia le classi che gli structs possono definire costruttori che accettano parametri. I costruttori che accettano
parametri devono essere chiamati con un'istruzione new o un'istruzione di base. Le classi e gli structs
possono anche definire più costruttori. Né le une né gli altri devono necessariamente definire un costruttore
senza parametri. Ad esempio:

public class Employee


{
public int Salary;

public Employee() { }

public Employee(int annualSalary)


{
Salary = annualSalary;
}

public Employee(int weeklySalary, int numberOfWeeks)


{
Salary = weeklySalary * numberOfWeeks;
}
}

Questa classe può essere creata usando una delle istruzioni seguenti:

Employee e1 = new Employee(30000);


Employee e2 = new Employee(500, 52);

Un costruttore può usare la parola chiave base per chiamare il costruttore di una classe di base. Ad esempio:
public class Manager : Employee
{
public Manager(int annualSalary)
: base(annualSalary)
{
//Add further instructions here.
}
}

In questo esempio il costruttore per la classe di base viene chiamato prima che venga eseguito il blocco per il
costruttore. La parola chiave base può essere usata con o senza parametri. Tutti i parametri per il costruttore
possono essere usati come parametri per base o come parte di un'espressione. Per altre informazioni, vedere
base.
In una classe derivata, se un costruttore di classe base non viene chiamato in modo esplicito usando la parola
chiave base , il costruttore senza parametri, se ne esiste uno, viene chiamato in modo implicito. Ciò significa
quindi che le seguenti dichiarazioni del costruttore si equivalgono:

public Manager(int initialData)


{
//Add further instructions here.
}

public Manager(int initialData)


: base()
{
//Add further instructions here.
}

Se una classe di base non offre un costruttore senza parametri, la classe derivata deve effettuare una chiamata
esplicita a un costruttore di base usando base .
Un costruttore può richiamare un altro costruttore nello stesso oggetto usando la parola chiave this. Come
base , this può essere utilizzata con o senza parametri e gli eventuali parametri nel costruttore sono
disponibili come parametri per this o come parte di un'espressione. Ad esempio, il secondo costruttore
nell'esempio precedente può essere riscritto usando this :

public Employee(int weeklySalary, int numberOfWeeks)


: this(weeklySalary * numberOfWeeks)
{
}

L'uso della parola chiave this nell'esempio precedente causa la chiamata di questo costruttore:

public Employee(int annualSalary)


{
Salary = annualSalary;
}

I costruttori possono essere contrassegnati come public, private, protected, internal, protected internal o private
protected. Questi modificatori di accesso definiscono il modo in cui gli utenti della classe possono costruire la
classe. Per altre informazioni, vedere Modificatori di accesso.
Un costruttore può essere dichiarato statico usando la parola chiave static. I costruttori statici vengono chiamati
automaticamente subito prima dell'accesso ai campi statici e in genere vengono usati per inizializzare i membri
delle classi statiche. Per altre informazioni, vedere Costruttori statici.

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi e struct
Costruttori
Finalizzatori
Costruttori di istanze (Guida per programmatori
C#)
28/01/2021 • 6 minutes to read • Edit Online

I costruttori di istanze vengono usati per creare e inizializzare qualsiasi variabile membro di istanza quando si
usa l'espressione new per creare un oggetto di una classe. Per inizializzare una classe statica o variabili statiche
in una classe non statica, definire un costruttore statico. Per altre informazioni, vedere Costruttori statici.
Nell'esempio seguente viene illustrato un costruttore di istanze:

class Coords
{
public int x, y;

// constructor
public Coords()
{
x = 0;
y = 0;
}
}

NOTE
Per maggior chiarezza, questa classe contiene campi pubblici. L'uso di campi pubblici non è una procedura di
programmazione consigliata, perché consente a qualsiasi metodo in qualsiasi punto di un programma di accedere senza
restrizioni e senza verifiche ai componenti interni di un oggetto. I membri dati in genere devono essere privati e l'accesso
ad essi deve essere consentito solo tramite metodi e proprietà della classe.

Questo costruttore di istanze viene chiamato ogni volta che viene creato un oggetto basato sulla classe Coords .
Un costruttore come questo, che non accetta argomenti, viene chiamato costruttore senza parametri. È spesso
utile, tuttavia, per fornire costruttori aggiuntivi. È ad esempio possibile aggiungere un costruttore alla classe
Coords che consente di specificare i valori iniziali dei membri dati:

// A constructor with two arguments.


public Coords(int x, int y)
{
this.x = x;
this.y = y;
}

Ciò consente di creare oggetti Coords con valori iniziali predefiniti o specifici, come illustrato di seguito:

var p1 = new Coords();


var p2 = new Coords(5, 3);

Se una classe non ha un costruttore, viene generato automaticamente un costruttore senza parametri e per
inizializzare i campi dell'oggetto vengono usati valori predefiniti. Ad esempio, un valore int viene inizializzato su
0. Per informazioni sui valori predefiniti del tipo, vedere valori predefiniti dei tipi C#. Poiché il costruttore senza
parametri della classe Coords inizializza tutti i membri dati su zero, può pertanto essere rimosso
completamente senza che ciò modifichi il funzionamento della classe. Per un esempio completo sull'uso di più
costruttori, vedere l'esempio 1 più avanti in questo argomento. Per un esempio di costruttore generato
automaticamente, vedere l'esempio 2.
I costruttori di istanze possono anche essere usati per chiamare i costruttori di istanze delle classi di base. Il
costruttore di classi può chiamare il costruttore della classe di base mediante l'inizializzatore, ad esempio:

class Circle : Shape


{
public Circle(double radius)
: base(radius, 0)
{
}
}

In questo esempio la classe Circle passa i valori che rappresentano il raggio e l'altezza al costruttore fornito da
Shape da cui deriva Circle . Per un esempio completo sull'uso di Shape e Circle vedere l'esempio 3 di
questo argomento.

Esempio 1
L'esempio riportato di seguito illustra una classe con due costruttori di classi, uno senza argomenti e uno con
due argomenti.
class Coords
{
public int x, y;

// Default constructor.
public Coords()
{
x = 0;
y = 0;
}

// A constructor with two arguments.


public Coords(int x, int y)
{
this.x = x;
this.y = y;
}

// Override the ToString method.


public override string ToString()
{
return $"({x},{y})";
}
}

class MainClass
{
static void Main()
{
var p1 = new Coords();
var p2 = new Coords(5, 3);

// Display the results using the overriden ToString method.


Console.WriteLine($"Coords #1 at {p1}");
Console.WriteLine($"Coords #2 at {p2}");
Console.ReadKey();
}
}
/* Output:
Coords #1 at (0,0)
Coords #2 at (5,3)
*/

Esempio 2
In questo esempio, la classe Person non ha alcun costruttore. In questo caso viene fornito automaticamente un
costruttore senza parametri e i campi vengono inizializzati sui rispettivi valori predefiniti.
using System;

public class Person


{
public int age;
public string name;
}

class TestPerson
{
static void Main()
{
var person = new Person();

Console.WriteLine($"Name: {person.name}, Age: {person.age}");


// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: Name: , Age: 0

Si noti che il valore predefinito di age è 0 e il valore predefinito di name è null .

Esempio 3
Nell'esempio riportato di seguito viene illustrato l'uso dell'inizializzatore della classe di base. La classe Circle è
derivata dalla classe generale Shape e la classe Cylinder è derivata dalla classe Circle . Il costruttore di ogni
classe derivata usa il relativo inizializzatore della classe di base.
using System;

abstract class Shape


{
public const double pi = Math.PI;
protected double x, y;

public Shape(double x, double y)


{
this.x = x;
this.y = y;
}

public abstract double Area();


}

class Circle : Shape


{
public Circle(double radius)
: base(radius, 0)
{
}

public override double Area()


{
return pi * x * x;
}
}

class Cylinder : Circle


{
public Cylinder(double radius, double height)
: base(radius)
{
y = height;
}

public override double Area()


{
return (2 * base.Area()) + (2 * pi * x * y);
}
}

class TestShapes
{
static void Main()
{
double radius = 2.5;
double height = 3.0;

Circle ring = new Circle(radius);


Cylinder tube = new Cylinder(radius, height);

Console.WriteLine("Area of the circle = {0:F2}", ring.Area());


Console.WriteLine("Area of the cylinder = {0:F2}", tube.Area());

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Area of the circle = 19.63
Area of the cylinder = 86.39
*/
Per altri esempi su come chiamare i costruttori della classe di base, vedere virtual, override e base.

Vedi anche
Guida per programmatori C#
Classi e struct
Costruttori
Finalizzatori
static
Costruttori privati (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Un costruttore privato è un costruttore di istanza speciale. Viene generalmente usato in classi contenenti solo
membri statici. Se una classe ha uno o più costruttori privati ma non include alcun costruttore pubblico, le altre
classi, ad eccezione di quelle annidate, non potranno creare istanze della classe. Ad esempio:

class NLog
{
// Private Constructor:
private NLog() { }

public static double e = Math.E; //2.71828...


}

La dichiarazione del costruttore vuoto impedisce la generazione automatica di un costruttore senza parametri.
Tenere presente che se non si usa un modificatore di accesso con il costruttore, questo rimarrà di tipo privato
per impostazione predefinita. Tuttavia, il modificatore private viene solitamente usato in modo esplicito per
specificare che non è possibile creare un'istanza della classe.
I costruttori privati vengono usati per impedire la creazione di istanze di una classe in assenza di metodi e campi
di istanza, ad esempio la classe Math, oppure quando viene chiamato un metodo per ottenere un'istanza di una
classe. Se tutti i metodi della classe sono statici, è consigliabile rendere statica l'intera classe. Per altre
informazioni, vedere Classi statiche e membri di classi statiche.

Esempio
Di seguito è riportato l'esempio di una classe che usa un costruttore privato.

Se si rimuove il commento dall'istruzione dell'esempio riportata di seguito, verrà generato un errore poiché non
è possibile accedere al costruttore a causa del livello di protezione:

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi e struct
Costruttori
Finalizzatori
privata
public
Costruttori statici (Guida per programmatori C#)
28/01/2021 • 7 minutes to read • Edit Online

Un costruttore statico consente di inizializzare gli eventuali dati static oppure di eseguire un'operazione specifica
che deve essere effettuata una sola volta. Viene chiamato automaticamente prima che ne venga creata la prima
istanza o venga fatto riferimento a qualsiasi membro statico.

class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;

// Static constructor is called at most one time, before any


// instance constructor is invoked or member is accessed.
static SimpleClass()
{
baseline = DateTime.Now.Ticks;
}
}

Commenti
I costruttori statici hanno le proprietà seguenti:
Un costruttore statico non accetta modificatori di accesso e non ha parametri.
Una classe o struct può avere solo un costruttore statico.
I costruttori statici non possono essere ereditati e non è possibile eseguirne l'overload.
Un costruttore statico non può essere chiamato direttamente ed è progettato esclusivamente per essere
chiamato da Common Language Runtime (CLR). Viene richiamato automaticamente.
L'utente non può controllare in alcun modo il momento in cui il costruttore statico viene eseguito nel
programma.
Un costruttore statico viene chiamato automaticamente per inizializzare la classe prima che ne venga
creata la prima istanza o venga fatto riferimento a qualsiasi membro statico. Un costruttore statico verrà
eseguito prima di un costruttore di istanza. Il costruttore statico di un tipo viene chiamato quando viene
richiamato un metodo statico assegnato a un evento o a un delegato e non al momento
dell'assegnazione. Gli eventuali inizializzatori variabili di campo statico presenti nella classe del
costruttore statico verranno eseguiti nell'ordine testuale in cui appaiono nella dichiarazione della classe
subito prima dell'esecuzione del costruttore statico.
Se non si specifica un costruttore statico per inizializzare i campi statici, tutti i campi statici vengono
inizializzati sul valore predefinito elencato in valori predefiniti di tipi C#.
Se un costruttore statico genera un'eccezione, il runtime non lo chiamerà una seconda volta e il tipo
rimarrà non inizializzato per la durata del dominio dell'applicazione in cui il programma è in esecuzione.
Viene più comunemente generata un'eccezione TypeInitializationException quando un costruttore statico
non è in grado di creare un'istanza di un tipo o per un'eccezione non gestita che si verifica all'interno di
un costruttore statico. Per i costruttori statici impliciti che non sono definiti in modo esplicito nel codice
sorgente, la risoluzione dei problemi può richiedere l'ispezione del codice IL (Intermediate Language).
La presenza di un costruttore statico impedisce l'aggiunta dell'attributo di tipo BeforeFieldInit. Ciò limita
l'ottimizzazione in fase di esecuzione.
Un campo dichiarato come static readonly può essere assegnato solo come parte della relativa
dichiarazione o in un costruttore statico. Quando non è richiesto un costruttore statico esplicito,
inizializzare i campi statici in fase di dichiarazione, anziché tramite un costruttore statico per una migliore
ottimizzazione in fase di esecuzione.

NOTE
Anche se non è direttamente accessibile, la presenza di un costruttore statico esplicito deve essere documentata per
facilitare la risoluzione dei problemi relativi alle eccezioni di inizializzazione.

Utilizzo
In genere, i costruttori statici sono usati per scrivere voci nel file di log, quando alla classe è associato un
file di log.
I costruttori statici risultano utili anche durante la creazione di classi wrapper per il codice non gestito,
quando il costruttore può chiamare il metodo LoadLibrary .
I costruttori statici rappresentano anche una comoda posizione per applicare i controlli in fase di
esecuzione sul parametro di tipo che non può essere controllato in fase di compilazione tramite vincoli
(vincoli di parametro di tipo).

Esempio
In questo esempio la classe Bus ha un costruttore statico. Quando viene creata la prima istanza di Bus ( bus1 ),
il costruttore statico viene chiamato per inizializzare la classe. L'output dell'esempio verifica che il costruttore
statico venga eseguito una sola volta, anche se vengono create due istanze di Bus , e che venga eseguito prima
del costruttore di istanze.

public class Bus


{
// Static variable used by all Bus instances.
// Represents the time the first bus of the day starts its route.
protected static readonly DateTime globalStartTime;

// Property for the number of each bus.


protected int RouteNumber { get; set; }

// Static constructor to initialize the static variable.


// It is invoked before the first instance constructor is run.
static Bus()
{
globalStartTime = DateTime.Now;

// The following statement produces the first line of output,


// and the line occurs only once.
Console.WriteLine("Static constructor sets global start time to {0}",
globalStartTime.ToLongTimeString());
}

// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}

// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;

// For demonstration purposes we treat milliseconds as minutes to simulate


// actual bus times. Do not do this in your actual bus schedule program!
Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
}
}

class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);

// Create a second bus.


Bus bus2 = new Bus(72);

// Send bus1 on its way.


bus1.Drive();

// Wait for bus2 to warm up.


System.Threading.Thread.Sleep(25);

// Send bus2 on its way.


bus2.Drive();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Sample output:
Static constructor sets global start time to 3:57:08 PM.
Bus #71 is created.
Bus #72 is created.
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Costruttori statici della specifica del linguaggio C#.

Vedi anche
Guida per programmatori C#
Classi e struct
Costruttori
Classi statiche e membri di classi statiche
Finalizzatori
Linee guida per la progettazione di costruttori
Avviso di sicurezza-CA2121: i costruttori statici devono essere privati
Come scrivere un costruttore di copia (Guida per
programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

C# non include un costruttore di copia per gli oggetti. È tuttavia possibile scriverne uno manualmente.

Esempio
Nell'esempio seguente, nella Person classe viene definito un costruttore di copia che accetta come argomento
un'istanza di Person . I valori delle proprietà dell'argomento vengono assegnati alle proprietà della nuova
istanza di Person . Il codice contiene un costruttore di copia alternativo che invia le proprietà Name e Age
dell'istanza che si vuole copiare nel costruttore di istanza della classe.
using System;

class Person
{
// Copy constructor.
public Person(Person previousPerson)
{
Name = previousPerson.Name;
Age = previousPerson.Age;
}

//// Alternate copy constructor calls the instance constructor.


//public Person(Person previousPerson)
// : this(previousPerson.Name, previousPerson.Age)
//{
//}

// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}

public int Age { get; set; }

public string Name { get; set; }

public string Details()


{
return Name + " is " + Age.ToString();
}
}

class TestPerson
{
static void Main()
{
// Create a Person object by using the instance constructor.
Person person1 = new Person("George", 40);

// Create another Person object, copying person1.


Person person2 = new Person(person1);

// Change each person's age.


person1.Age = 39;
person2.Age = 41;

// Change person2's name.


person2.Name = "Charles";

// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output:
// George is 39
// Charles is 41

Vedi anche
ICloneable
Guida per programmatori C#
Classi e struct
Costruttori
Finalizzatori
Finalizzatori (Guida per programmatori C#)
28/01/2021 • 6 minutes to read • Edit Online

I finalizzatori (detti anche distruttori ) vengono usati per eseguire operazioni di pulizia finale eventualmente
necessarie quando un'istanza di classe viene raccolta da Garbage Collector.

Commenti
I finalizzatori non possono essere definiti negli struct. Vengono usati solo con le classi.
Una classe può avere un solo finalizzatore.
I finalizzatori non possono essere ereditati e non è possibile eseguirne l'overload.
I finalizzatori non possono essere chiamati. Vengono richiamati automaticamente.
Un finalizzatore non accetta modificatori e non ha parametri.
Ad esempio, di seguito è riportata la dichiarazione di un finalizzatore per la classe Car .

class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}

Un finalizzatore può anche essere implementato come definizione di corpo dell'espressione, come illustrato
nell'esempio seguente.

using System;

public class Destroyer


{
public override string ToString() => GetType().Name;

~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");


}

Il finalizzatore chiama implicitamente Finalize per la classe di base dell'oggetto. Di conseguenza, una chiamata a
un finalizzatore viene convertita implicitamente nel codice seguente:

protected override void Finalize()


{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}
Questo progetto significa che il Finalize metodo viene chiamato in modo ricorsivo per tutte le istanze nella
catena di ereditarietà, dal più derivato al meno derivato.

NOTE
I finalizzatori vuoti non devono essere usati. Quando una classe contiene un finalizzatore, viene creata una voce nella coda
Finalize . Quando si chiama il finalizzatore, viene richiamato Garbage Collector per elaborare la coda. Se il finalizzatore è
vuoto, si verifica semplicemente un calo di prestazioni.

Il programmatore non ha alcun controllo sul momento in cui viene chiamato il finalizzatore; il Garbage Collector
decide quando chiamarlo. Il Garbage Collector controlla gli oggetti che non vengono più usati dall'applicazione
e, se considera un oggetto idoneo per la finalizzazione, chiama il finalizzatore (se presente) e recupera la
memoria usata per archiviare l'oggetto.
Nelle applicazioni .NET Framework (ma non nelle applicazioni .NET Core) i finalizzatori vengono chiamati anche
alla chiusura del programma.
È possibile forzare la Garbage Collection chiamando Collect , ma nella maggior parte dei casi, questa chiamata
deve essere evitata perché potrebbe creare problemi di prestazioni.

Uso di finalizzatori per liberare risorse


In generale, in C# non è richiesto il maggior livello di gestione della memoria da parte dello sviluppatore come
linguaggi che non hanno come destinazione un runtime con Garbage Collection. Ciò è dovuto al fatto che .NET
Garbage Collector gestisce in modo implicito l'allocazione e il rilascio di memoria per gli oggetti. Tuttavia,
quando l'applicazione incapsula risorse non gestite, ad esempio Windows, file e connessioni di rete, è necessario
usare i finalizzatori per liberare tali risorse. Quando l'oggetto è idoneo per la finalizzazione, il Garbage Collector
esegue il metodo Finalize dell'oggetto.

Rilascio esplicito di risorse


Se l'applicazione usa una risorsa esterna che consuma molta memoria, è consigliabile specificare un modo per
rilasciare la risorsa in modo esplicito prima che il Garbage Collector renda disponibile l'oggetto. Per rilasciare la
risorsa, implementare un Dispose metodo dall' IDisposable interfaccia che esegue la pulizia necessaria per
l'oggetto. Questo consente di migliorare notevolmente le prestazioni dell'applicazione. Anche con questo
controllo esplicito sulle risorse, il finalizzatore diventa una protezione per la pulizia delle risorse se la chiamata al
Dispose metodo ha esito negativo.

Per ulteriori informazioni sulla pulizia delle risorse, vedere gli articoli seguenti:
Pulizia delle risorse non gestite
Implementazione di un metodo Dispose
Istruzione using

Esempio
L'esempio seguente crea tre classi che costituiscono una catena di ereditarietà. La classe First è la classe base,
Second è derivata da First e Third è derivata da Second . Tutte e tre hanno finalizzatori. In Main viene creata
un'istanza della classe più derivata. Durante l'esecuzione del programma, si noti che i finalizzatori delle tre classi
vengono chiamati automaticamente e in ordine, dalla classe più derivata alla meno derivata.
class First
{
~First()
{
System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
}
}

class Second : First


{
~Second()
{
System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
}
}

class Third : Second


{
~Third()
{
System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
}
}

class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
/* Output (to VS Output Window):
Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Distruttori della specifica del linguaggio C#.

Vedi anche
IDisposable
Guida per programmatori C#
Costruttori
Garbage Collection
Inizializzatori di oggetto e di raccolte (Guida per
programmatori C#)
02/11/2020 • 12 minutes to read • Edit Online

C# consente di creare un'istanza di un oggetto o di una raccolta e di eseguire le assegnazioni di membri in


un'unica istruzione.

Inizializzatori di oggetto
Gli inizializzatori di oggetto consentono di assegnare valori a qualsiasi proprietà o campo accessibile di un
oggetto in fase di creazione senza dover richiamare un costruttore seguito da righe di istruzioni di assegnazione.
La sintassi dell'inizializzatore di oggetto consente di specificare gli argomenti per un costruttore o di omettere
gli argomenti (e la sintassi di parentesi). Nell'esempio seguente viene illustrato come usare un inizializzatore di
oggetto con un tipo denominato, Cat e come richiamare il costruttore senza parametri. Si noti l'uso di
proprietà implementate automaticamente nella classe Cat . Per altre informazioni, vedere Proprietà
implementate automaticamente.

public class Cat


{
// Auto-implemented properties.
public int Age { get; set; }
public string Name { get; set; }

public Cat()
{
}

public Cat(string name)


{
this.Name = name;
}
}

Cat cat = new Cat { Age = 10, Name = "Fluffy" };


Cat sameCat = new Cat("Fluffy"){ Age = 10 };

La sintassi degli inizializzatori di oggetto consente di creare un'istanza e dopo assegna l'oggetto appena creato,
con le relative proprietà assegnate, alla variabile nell'assegnazione.
A partire da C# 6, gli inizializzatori di oggetto possono impostare gli indicizzatori, oltre ad assegnare campi e
proprietà. Si consideri questa classe Matrix di base:
public class Matrix
{
private double[,] storage = new double[3, 3];

public double this[int row, int column]


{
// The embedded array will throw out of range exceptions as appropriate.
get { return storage[row, column]; }
set { storage[row, column] = value; }
}
}

È possibile inizializzare la matrice di identità con il codice seguente:

var identity = new Matrix


{
[0, 0] = 1.0,
[0, 1] = 0.0,
[0, 2] = 0.0,

[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,

[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};

Qualsiasi indicizzatore accessibile che contiene un setter accessibile è utilizzabile come espressione in un
inizializzatore di oggetto, indipendentemente dal numero o dai tipi degli argomenti. Gli argomenti di indice
formano il lato sinistro dell'assegnazione e il valore corrisponde al lato destro dell'espressione. Questi, ad
esempio sono tutti validi se IndexersExample ha gli indicizzatori appropriati:

var thing = new IndexersExample {


name = "object one",
[1] = '1',
[2] = '4',
[3] = '9',
Size = Math.PI,
['C',4] = "Middle C"
}

Perché la compilazione del codice precedente riesca, il tipo IndexersExample deve avere i membri seguenti:

public string name;


public double Size { set { ... }; }
public char this[int i] { set { ... }; }
public string this[char c, int i] { set { ... }; }

Inizializzatori di oggetto con tipi anonimi


Anche se gli inizializzatori di oggetto possono essere usati in qualsiasi contesto, sono particolarmente utili nelle
espressioni di query LINQ. Le espressioni di query si avvalgono di frequente di tipi anonimi che possono essere
inizializzati solo con un inizializzatore di oggetto, come illustrato nella dichiarazione seguente.
var pet = new { Age = 10, Name = "Fluffy" };

I tipi anonimi consentono alla select clausola in un'espressione di query LINQ di trasformare gli oggetti della
sequenza originale in oggetti i cui valori e la cui forma potrebbero essere diversi dall'originale. Questo è utile se
si desidera archiviare solo una parte delle informazioni di ogni oggetto di una sequenza. Nell'esempio seguente,
si supponga che un oggetto prodotto ( p ) contenga diversi campi e metodi e che si sia interessati a creare solo
una sequenza di oggetti che contengono il nome e il prezzo unitario del prodotto.

var productInfos =
from p in products
select new { p.ProductName, p.UnitPrice };

Quando questa query verrà eseguita, la variabile productInfos conterrà una sequenza di oggetti a cui sarà
possibile accedere in un'istruzione foreach come illustrato in questo esempio:

foreach(var p in productInfos){...}

Ogni oggetto nel nuovo tipo anonimo dispone di due proprietà pubbliche che ricevono gli stessi nomi delle
proprietà o dei campi nell'oggetto originale. È inoltre possibile rinominare un campo mentre si crea un tipo
anonimo. Nell'esempio seguente il campo UnitPrice viene rinominato in Price .

select new {p.ProductName, Price = p.UnitPrice};

Inizializzatori di raccolta
Gli inizializzatori di raccolta consentono di specificare uno o più inizializzatori di elemento quando si inizializza
un tipo di raccolta che implementa IEnumerable e ha un metodo Add con una firma appropriata come metodo
di istanza o metodo di estensione. Gli inizializzatori di elemento possono essere valori semplici, espressioni o
inizializzatori di oggetto. Se si usa un inizializzatore di insieme, non è necessario specificare più chiamate, in
quanto le chiamate vengono aggiunte dal compilatore automaticamente.
L'esempio seguente illustrati due inizializzatori di insieme semplici:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };


List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };

Nell'inizializzatore di raccolta riportato di seguito vengono utilizzati inizializzatori di oggetto per inizializzare
oggetti della classe Cat definiti in un esempio precedente. Si noti che i singoli inizializzatori di oggetto sono
racchiusi tra parentesi e separati da virgole.

List<Cat> cats = new List<Cat>


{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
};

È possibile specificare null come elemento in un inizializzatore di insieme se il metodo Add della raccolta lo
consente.
List<Cat> moreCats = new List<Cat>
{
new Cat{ Name = "Furrytail", Age=5 },
new Cat{ Name = "Peaches", Age=4 },
null
};

È possibile specificare elementi indicizzati se la raccolta supporta l'indicizzazione in lettura/scrittura.

var numbers = new Dictionary<int, string>


{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};

L'esempio precedente genera codice che chiama Item[TKey] per impostare i valori. Prima di C# 6, era possibile
inizializzare dizionari e altri contenitori associativi usando la sintassi seguente. Si noti che anziché la sintassi
degli indicizzatori, con le parentesi e un'assegnazione, viene usato un oggetto con più valori:

var moreNumbers = new Dictionary<int, string>


{
{19, "nineteen" },
{23, "twenty-three" },
{42, "forty-two" }
};

Questo esempio di inizializzatore chiama Add(TKey, TValue) per aggiungere i tre elementi nel dizionario. Questi
due modi diversi di inizializzazione delle raccolte associative hanno un comportamento leggermente diverso a
causa delle chiamate di metodo generate dal compilatore. Con la classe Dictionary funzionano entrambe le
varianti. È possibile che altri tipi supportino l'uno o l'altro in base all'API pubblica usata.

Esempi
L'esempio seguente unisce i concetti di inizializzatori di oggetto e di insieme.
public class InitializationSample
{
public class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string Name { get; set; }

public Cat() { }

public Cat(string name)


{
Name = name;
}
}

public static void Main()


{
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat sameCat = new Cat("Fluffy"){ Age = 10 };

List<Cat> cats = new List<Cat>


{
new Cat { Name = "Sylvester", Age = 8 },
new Cat { Name = "Whiskers", Age = 2 },
new Cat { Name = "Sasha", Age = 14 }
};

List<Cat> moreCats = new List<Cat>


{
new Cat { Name = "Furrytail", Age = 5 },
new Cat { Name = "Peaches", Age = 4 },
null
};

// Display results.
System.Console.WriteLine(cat.Name);

foreach (Cat c in cats)


System.Console.WriteLine(c.Name);

foreach (Cat c in moreCats)


if (c != null)
System.Console.WriteLine(c.Name);
else
System.Console.WriteLine("List element has null value.");
}
// Output:
//Fluffy
//Sylvester
//Whiskers
//Sasha
//Furrytail
//Peaches
//List element has null value.
}

L'esempio seguente illustra un oggetto che implementa IEnumerable e contiene un metodo Add con più
parametri. Usa un inizializzatore di insieme con più elementi per ogni voce nell'elenco corrispondente alla firma
del metodo Add .
public class FullExample
{
class FormattedAddresses : IEnumerable<string>
{
private List<string> internalList = new List<string>();
public IEnumerator<string> GetEnumerator() => internalList.GetEnumerator();

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>


internalList.GetEnumerator();

public void Add(string firstname, string lastname,


string street, string city,
string state, string zipcode) => internalList.Add(
$@"{firstname} {lastname}
{street}
{city}, {state} {zipcode}"
);
}

public static void Main()


{
FormattedAddresses addresses = new FormattedAddresses()
{
{"John", "Doe", "123 Street", "Topeka", "KS", "00000" },
{"Jane", "Smith", "456 Street", "Topeka", "KS", "00000" }
};

Console.WriteLine("Address Entries:");

foreach (string addressEntry in addresses)


{
Console.WriteLine("\r\n" + addressEntry);
}
}

/*
* Prints:

Address Entries:

John Doe
123 Street
Topeka, KS 00000

Jane Smith
456 Street
Topeka, KS 00000
*/
}

I metodi Add possono usare la parola chiave params per accettare un numero variabile di argomenti, come
illustrato nell'esempio seguente. Questo esempio illustra anche l'implementazione personalizzata di un
indicizzatore per l'inizializzazione di un insieme tramite indici.

public class DictionaryExample


{
class RudimentaryMultiValuedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, List<TValue>>>
{
private Dictionary<TKey, List<TValue>> internalDictionary = new Dictionary<TKey, List<TValue>>();

public IEnumerator<KeyValuePair<TKey, List<TValue>>> GetEnumerator() =>


internalDictionary.GetEnumerator();

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>


internalDictionary.GetEnumerator();
public List<TValue> this[TKey key]
{
get => internalDictionary[key];
set => Add(key, value);
}

public void Add(TKey key, params TValue[] values) => Add(key, (IEnumerable<TValue>)values);

public void Add(TKey key, IEnumerable<TValue> values)


{
if (!internalDictionary.TryGetValue(key, out List<TValue> storedValues))
internalDictionary.Add(key, storedValues = new List<TValue>());

storedValues.AddRange(values);
}
}

public static void Main()


{
RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary1
= new RudimentaryMultiValuedDictionary<string, string>()
{
{"Group1", "Bob", "John", "Mary" },
{"Group2", "Eric", "Emily", "Debbie", "Jesse" }
};
RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary2
= new RudimentaryMultiValuedDictionary<string, string>()
{
["Group1"] = new List<string>() { "Bob", "John", "Mary" },
["Group2"] = new List<string>() { "Eric", "Emily", "Debbie", "Jesse" }
};
RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary3
= new RudimentaryMultiValuedDictionary<string, string>()
{
{"Group1", new string []{ "Bob", "John", "Mary" } },
{ "Group2", new string[]{ "Eric", "Emily", "Debbie", "Jesse" } }
};

Console.WriteLine("Using first multi-valued dictionary created with a collection initializer:");

foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary1)


{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");

foreach (string member in group.Value)


{
Console.WriteLine(member);
}
}

Console.WriteLine("\r\nUsing second multi-valued dictionary created with a collection initializer


using indexing:");

foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary2)


{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");

foreach (string member in group.Value)


{
Console.WriteLine(member);
}
}
Console.WriteLine("\r\nUsing third multi-valued dictionary created with a collection initializer
using indexing:");

foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary3)


{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");
foreach (string member in group.Value)
{
Console.WriteLine(member);
}
}
}

/*
* Prints:

Using first multi-valued dictionary created with a collection initializer:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse

Using second multi-valued dictionary created with a collection initializer using indexing:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse

Using third multi-valued dictionary created with a collection initializer using indexing:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse
*/
}

Vedi anche
Guida per programmatori C#
LINQ in C#
Tipi anonimi
Come inizializzare gli oggetti usando un
inizializzatore di oggetto (Guida per programmatori
C#)
28/01/2021 • 3 minutes to read • Edit Online

È possibile usare gli inizializzatori di oggetto per inizializzare gli oggetti tipo in modo dichiarativo, senza
richiamare in modo esplicito un costruttore per il tipo.
Nell'esempio seguente viene illustrato come usare gli inizializzatori di oggetto con gli oggetti denominati. Il
compilatore elabora gli inizializzatori di oggetto accedendo prima al costruttore di istanza senza parametri e
quindi elaborando le inizializzazioni dei membri. Pertanto, se il costruttore senza parametri viene dichiarato
come private nella classe, gli inizializzatori di oggetto che richiedono l'accesso pubblico avranno esito
negativo.
Se si definisce un tipo anonimo, è necessario usare un inizializzatore di oggetto. Per ulteriori informazioni,
vedere come restituire subset di proprietà degli elementi in una query.

Esempio
Nell'esempio seguente viene illustrato come inizializzare un nuovo tipo StudentName usando inizializzatori di
oggetto. Questo esempio imposta le proprietà nel tipo StudentName :

public class HowToObjectInitializers


{
public static void Main()
{
// Declare a StudentName by using the constructor that has two parameters.
StudentName student1 = new StudentName("Craig", "Playstead");

// Make the same declaration by using an object initializer and sending


// arguments for the first and last names. The parameterless constructor is
// invoked in processing this declaration, not the constructor that has
// two parameters.
StudentName student2 = new StudentName
{
FirstName = "Craig",
LastName = "Playstead"
};

// Declare a StudentName by using an object initializer and sending


// an argument for only the ID property. No corresponding constructor is
// necessary. Only the parameterless constructor is used to process object
// initializers.
StudentName student3 = new StudentName
{
ID = 183
};

// Declare a StudentName by using an object initializer and sending


// arguments for all three properties. No corresponding constructor is
// defined in the class.
StudentName student4 = new StudentName
{
FirstName = "Craig",
LastName = "Playstead",
ID = 116
};

Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
}
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116

public class StudentName


{
// This constructor has no parameters. The parameterless constructor
// is invoked in the processing of object initializers.
// You can test this by changing the access modifier from public to
// private. The declarations in Main that use object initializers will
// fail.
public StudentName() { }

// The following constructor has parameters for two of the three


// properties.
public StudentName(string first, string last)
{
FirstName = first;
LastName = last;
}

// Properties.
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }

public override string ToString() => FirstName + " " + ID;


}
}

Gli inizializzatori di oggetto possono essere usati per impostare gli indicizzatori in un oggetto. L'esempio
seguente definisce una classe BaseballTeam che usa un indicizzatore per ottenere e impostare i lettori in
posizioni diverse. L'inizializzatore può assegnare lettori, in base all'abbreviazione per la posizione o al numero
usato per gli scorecard di baseball di ogni posizione:
public class HowToIndexInitializer
{
public class BaseballTeam
{
private string[] players = new string[9];
private readonly List<string> positionAbbreviations = new List<string>
{
"P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"
};

public string this[int position]


{
// Baseball positions are 1 - 9.
get { return players[position-1]; }
set { players[position-1] = value; }
}
public string this[string position]
{
get { return players[positionAbbreviations.IndexOf(position)]; }
set { players[positionAbbreviations.IndexOf(position)] = value; }
}
}

public static void Main()


{
var team = new BaseballTeam
{
["RF"] = "Mookie Betts",
[4] = "Jose Altuve",
["CF"] = "Mike Trout"
};

Console.WriteLine(team["2B"]);
}
}

Vedi anche
Guida per programmatori C#
Inizializzatori di oggetto e di raccolta
Come inizializzare un dizionario con un
inizializzatore di insieme (Guida per programmatori
C#)
28/01/2021 • 2 minutes to read • Edit Online

Un oggetto Dictionary<TKey,TValue> contiene una raccolta di coppie chiave-valore. Il relativo metodo Add
accetta due parametri, uno per la chiave e uno per il valore. Un modo per inizializzare un oggetto
Dictionary<TKey,TValue> o qualsiasi raccolta il cui metodo Add accetta più parametri, consiste nel racchiudere
ogni set di parametri tra parentesi graffe, come illustrato nell'esempio seguente. Un'altra opzione consiste
nell'usare un inizializzatore di indice, come mostrato nell'esempio seguente.

Esempio
Nell'esempio di codice seguente, viene inizializzato un oggetto Dictionary<TKey,TValue> con istanze di tipo
StudentName . La prima inizializzazione usa il metodo Add con due argomenti. Il compilatore genera una
chiamata a Add per ognuna delle coppie di chiavi int e valori StudentName . Il secondo usa un metodo di
lettura pubblica / indicizzatore di scrittura della classe Dictionary :
public class HowToDictionaryInitializer
{
class StudentName
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
}

public static void Main()


{
var students = new Dictionary<int, StudentName>()
{
{ 111, new StudentName { FirstName="Sachin", LastName="Karnik", ID=211 } },
{ 112, new StudentName { FirstName="Dina", LastName="Salimzianova", ID=317 } },
{ 113, new StudentName { FirstName="Andy", LastName="Ruth", ID=198 } }
};

foreach(var index in Enumerable.Range(111, 3))


{
Console.WriteLine($"Student {index} is {students[index].FirstName} {students[index].LastName}");
}
Console.WriteLine();

var students2 = new Dictionary<int, StudentName>()


{
[111] = new StudentName { FirstName="Sachin", LastName="Karnik", ID=211 },
[112] = new StudentName { FirstName="Dina", LastName="Salimzianova", ID=317 } ,
[113] = new StudentName { FirstName="Andy", LastName="Ruth", ID=198 }
};

foreach (var index in Enumerable.Range(111, 3))


{
Console.WriteLine($"Student {index} is {students2[index].FirstName}
{students2[index].LastName}");
}
}
}

Si noti che vi sono due coppie di parentesi graffe in ogni elemento della raccolta nella prima dichiarazione. Le
parentesi graffe più interne racchiudono l'inizializzatore di oggetto per StudentName e le parentesi graffe più
esterne racchiudono l'inizializzatore per la coppia chiave/valore che verrà aggiunta a students
Dictionary<TKey,TValue> . Infine, tutto l'inizializzatore di insieme per il dizionario è racchiuso tra parentesi
graffe. Durante la seconda inizializzazione, il lato sinistro dell'assegnazione è la chiave e il lato destro è il valore,
usando un inizializzatore di oggetto per StudentName .

Vedi anche
Guida per programmatori C#
Inizializzatori di oggetto e di raccolta
Tipi annidati (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Un tipo definito all'interno di una classe, uno struct, un delegato o un' interfaccia viene definito tipo annidato. Ad
esempio:

public class Container


{
class Nested
{
Nested() { }
}
}

Indipendentemente dal fatto che il tipo esterno sia una classe, un'interfaccia o uno struct, i tipi annidati per
impostazione predefinita sono privati; sono accessibili solo dal tipo che lo contiene. Nell'esempio precedente, la
classe Nested non è accessibile a tipi esterni.
È possibile anche specificare un modificatore di accesso per definire l'accessibilità di un tipo annidato, come
indicato di seguito:
I tipi annidati di una classe possono essere public, protected, internal, protected internal, private o private
protected.
La definizione di una classe annidata protected , protected internal o private protected all'interno di
un classe sealed, tuttavia, genera l'avviso del compilatore CS0628: "Il nuovo membro protected è stato
dichiarato nella classe sealed".
I tipi annidati di uno struct possono essere public, internal o private.
Nell'esempio seguente, la classe Nested viene resa public:

public class Container


{
public class Nested
{
Nested() { }
}
}

Il tipo annidato, o interno, può accedere al tipo contenitore, o esterno. Per accedere al tipo contenitore, passarlo
come argomento al costruttore del tipo annidato. Ad esempio:
public class Container
{
public class Nested
{
private Container parent;

public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}

Un tipo annidato può accedere a tutti i membri accessibili al tipo contenitore. Può accedere a membri privati e
protetti del tipo che li contengono, inclusi tutti i membri protetti ereditati.
Nella dichiarazione precedente il nome completo della classe Nested è Container.Nested . Questo nome viene
utilizzato per creare una nuova istanza della classe annidata, come illustrato di seguito:

Container.Nested nest = new Container.Nested();

Vedi anche
Guida per programmatori C#
Classi e struct
Modificatori di accesso
Costruttori
Classi e metodi parziali (Guida per programmatori
C#)
28/01/2021 • 12 minutes to read • Edit Online

È possibile suddividere la definizione di una classe di uno struct, di un'interfaccia o di un metodo tra due o più
file di origine. Ogni file di origine contiene una sezione della definizione di tipo o metodo e tutte le parti
vengono combinate al momento della compilazione dell'applicazione.

Classi parziali
La suddivisione della definizione di una classe è consigliabile in diverse situazioni:
Quando si lavora su progetti di grandi dimensioni, la distribuzione di una classe in file distinti ne
consente l'uso simultaneo da parte di più programmatori.
Quando si usa un'origine generata automaticamente, è possibile aggiungere codice alla classe senza
dover ricreare il file di origine. Visual Studio usa questo approccio per la creazione di Windows Form,
codice wrapper di servizi Web e così via. È possibile creare codice che usa queste classi senza dover
modificare il file creato da Visual Studio.
Per suddividere la definizione di una classe, usare il modificatore della parola chiave partial, come
illustrato di seguito:

public partial class Employee


{
public void DoWork()
{
}
}

public partial class Employee


{
public void GoToLunch()
{
}
}

La parola chiave partial indica che è possibile definire altre parti della classe, dello struct o dell'interfaccia
nello spazio dei nomi. Tutte le parti devono usare la parola chiave partial ed essere disponibili in fase di
compilazione in modo da formare il tipo finale. Tutte le parti devono anche avere lo stesso livello di accessibilità,
ad esempio public , private e così via.
Se una parte viene dichiarata come astratta, l'intero tipo verrà considerato astratto. Se una parte viene
dichiarata come sealed, l'intero tipo verrà considerato sealed. Se una parte dichiara un tipo base, l'intero tipo
eredita la classe.
Tutte le parti che specificano una classe base devono concordare, tuttavia le parti che omettono una classe base
ereditano comunque il tipo base. Le parti possono specificare interfacce di base differenti e il tipo finale
implementa tutte le interfacce elencate da tutte le dichiarazioni parziali. Tutti i membri di classe, struttura o
interfaccia dichiarati in una definizione parziale sono disponibili per tutte le altre parti. Il tipo finale rappresenta
la combinazione di tutte le parti in fase di compilazione.
NOTE
Il modificatore partial non è disponibile per le dichiarazioni di delegato o di enumerazione.

L'esempio seguente illustra che i tipi annidati possono essere parziali, anche se non lo è il tipo all'interno del
quale sono annidati.

class Container
{
partial class Nested
{
void Test() { }
}

partial class Nested


{
void Test2() { }
}
}

In fase di compilazione gli attributi delle definizioni di tipi parziali vengono uniti. Si considerino ad esempio le
dichiarazioni seguenti:

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Sono equivalenti alle dichiarazioni seguenti:

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

Gli elementi seguenti vengono uniti da tutte le definizioni di tipi parziali:


Commenti in XML
interfaces
attributi di parametri di tipo generico
attributi class
Membri di
Si considerino ad esempio le dichiarazioni seguenti:

partial class Earth : Planet, IRotate { }


partial class Earth : IRevolve { }

Sono equivalenti alle dichiarazioni seguenti:

class Earth : Planet, IRotate, IRevolve { }


Restrizioni
Quando si usano le definizioni parziali di classi è necessario rispettare diverse regole:
Tutte le definizioni di tipi parziali destinate a essere parti dello stesso tipo devono essere modificate con
partial . Ad esempio, le dichiarazioni di classe seguenti generano un errore:

public partial class A { }


//public class A { } // Error, must also be marked partial

Il modificatore partial può essere specificato solo prima delle parole chiave class , struct o
interface .

I tipi parziali annidati sono consentiti nelle definizioni di tipi parziali, come illustrato nell'esempio
seguente:

partial class ClassWithNestedClass


{
partial class NestedClass { }
}

partial class ClassWithNestedClass


{
partial class NestedClass { }
}

Tutte le definizioni di tipi parziali destinate a essere parti dello stesso tipo devono essere definite nello
stesso assembly e nello stesso modulo (file con estensione exe o dll). Le definizioni parziali non possono
estendersi su più moduli.
Il nome della classe e i parametri di tipo generico devono corrispondere in tutte le definizioni di tipi
parziali. I tipi generici possono essere parziali. In ogni dichiarazione parziale è necessario usare gli stessi
nomi di parametri nello stesso ordine.
Le parole chiave riportate di seguito sono facoltative in una definizione di tipi parziali. Tuttavia, se presenti
in una definizione, tali parole chiave non possono essere in conflitto con quelle specificate in un'altra
definizione parziale per lo stesso tipo:
public
privata
protetto
interno
astratta
sealed
classi base
modificatore new (parti annidate)
vincoli generici
Per altre informazioni, vedere Vincoli sui parametri di tipo.

Esempio 1
Descrizione
Nell'esempio seguente i campi e il costruttore della classe Coords vengono dichiarati in una definizione parziale
di classe, mentre il membro PrintCoords viene dichiarato in un'altra definizione parziale di classe.
Codice

public partial class Coords


{
private int x;
private int y;

public Coords(int x, int y)


{
this.x = x;
this.y = y;
}
}

public partial class Coords


{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}

class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: Coords: 10,15

Esempio 2
Descrizione
L'esempio seguente dimostra che è anche possibile sviluppare struct e interfacce parziali.
Codice
partial interface ITest
{
void Interface_Test();
}

partial interface ITest


{
void Interface_Test2();
}

partial struct S1
{
void Struct_Test() { }
}

partial struct S1
{
void Struct_Test2() { }
}

Metodi parziali
Una classe o uno struct parziale può contenere un metodo parziale. Una parte della classe contiene la firma del
metodo. Un'implementazione facoltativa può essere definita nella stessa parte o in un'altra parte. Se
l'implementazione non viene specificata, il metodo e tutte le chiamate al metodo vengono rimosse in fase di
compilazione.
I metodi parziali consentono all'implementatore di una parte di una classe di definire un metodo, simile a un
evento. L'implementatore dell'altra parte della classe può decidere se implementare il metodo o meno. Se il
metodo non viene implementato, il compilatore rimuove la firma del metodo e tutte le chiamate al metodo. Le
chiamate al metodo, inclusi eventuali risultati che derivassero dalla valutazione di argomenti nelle chiamate, non
hanno alcun effetto in fase di esecuzione. Pertanto, il codice nella classe parziale può usare liberamente un
metodo parziale, anche se non viene specificata l'implementazione. Non verranno generati errori in fase di
compilazione o errori di runtime se il metodo viene chiamato ma non implementato.
I metodi parziali sono particolarmente utili per personalizzare il codice generato. Consentono di riservare un
nome e una firma del metodo, in modo che il codice generato possa chiamare il metodo ma spetta allo
sviluppatore decidere se implementare il metodo. Analogamente alle classi parziali, i metodi parziali consentono
di usare il codice creato da un generatore di codice con il codice creato da un sviluppatore senza alcun costo in
fase di esecuzione.
Una dichiarazione di metodo parziale è costituita da due parti: la definizione e l'implementazione, che possono
trovarsi in parti separate di una classe parziale o nella stessa parte. Se non è presente la dichiarazione di
implementazione, il compilatore ottimizza la dichiarazione di definizione e tutte le chiamate al metodo.

// Definition in file1.cs
partial void onNameChanged();

// Implementation in file2.cs
partial void onNameChanged()
{
// method body
}

Le dichiarazioni di metodi parziali devono iniziare con la parola chiave contestuale partial e il metodo
deve restituire void.
I metodi parziali possono contenere il parametro in o ref ma non il parametro out.
I metodi parziali sono implicitamente private e pertanto non possono essere virtual.
I metodi parziali non possono essere extern perché la presenza del corpo determina se è in corso una
definizione o un'implementazione.
I metodi parziali possono contenere modificatori static e unsafe.
I metodi parziali possono essere generici. I vincoli vengono inseriti nella dichiarazione di definizione del
metodo parziale e possono essere ripetuti facoltativamente nella dichiarazione di implementazione. I
nomi dei parametri e dei parametri di tipo non devono essere uguali nella dichiarazione di
implementazione e in quella di definizione.
È possibile creare un delegato di un metodo parziale che è stato definito e implementato, ma non di un
metodo parziale che è stato solo definito.

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Classi
Tipi di struttura
Interfacce
partial (Tipo)
Tipi anonimi (Guida per programmatori C#)
02/11/2020 • 7 minutes to read • Edit Online

I tipi anonimi offrono un modo pratico per incapsulare un set di proprietà di sola lettura in un singolo oggetto,
senza dover definire prima un tipo in modo esplicito. Il nome del tipo viene generato dal compilatore e non è
disponibile a livello di codice sorgente. Il tipo di ogni proprietà è dedotto dal compilatore.
Per creare tipi anonimi, si usa l'operatore new insieme a un inizializzatore di oggetto. Per altre informazioni sugli
inizializzatori di oggetto, vedere Inizializzatori di oggetto e di raccolte.
L'esempio seguente mostra un tipo anonimo inizializzato con due proprietà denominate Amount e Message .

var v = new { Amount = 108, Message = "Hello" };

// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);

I tipi anonimi vengono in genere usati nella clausola select di un'espressione di query per restituire un subset
delle proprietà da ogni oggetto della sequenza di origine. Per ulteriori informazioni sulle query, vedere LINQ in
C#.
I tipi anonimi contengono una o più proprietà pubbliche di sola lettura. Non sono validi altri tipi di membri della
classe, ad esempio metodi o eventi. L'espressione usata per inizializzare una proprietà non può essere null ,
una funzione anonima o un tipo di puntatore.
Lo scenario più comune consiste nell'inizializzare un tipo anonimo con proprietà di un altro tipo. Nell'esempio
seguente si presuppone l'esistenza di una classe denominata Product . La classe Product include le proprietà
Color e Price , insieme ad altre proprietà non pertinenti. La variabile products è una raccolta di oggetti
Product . La dichiarazione di tipo anonimo inizia con la parola chiave new . La dichiarazione inizializza un nuovo
tipo che usa solo due proprietà della classe Product . In questo modo la query restituisce una quantità di dati
inferiore.
Se nel tipo anonimo non si specificano i nomi dei membri, il compilatore assegna ai membri di tipo anonimo lo
stesso nome della proprietà usata per inizializzarli. Per una proprietà inizializzata con un'espressione è
necessario fornire un nome, come illustrato nell'esempio seguente. Nell'esempio seguente i nomi delle
proprietà del tipo anonimo sono Color e Price .

var productQuery =
from prod in products
select new { prod.Color, prod.Price };

foreach (var v in productQuery)


{
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

In genere, quando si usa un tipo anonimo per inizializzare una variabile, è necessario dichiarare la variabile
come locale tipizzata in modo implicito tramite var. Il nome del tipo non può essere specificato nella
dichiarazione di variabile, perché solo il compilatore ha accesso al nome sottostante del tipo anonimo. Per altre
informazioni su var , vedere Variabili locali tipizzate in modo implicito.
È possibile creare una matrice di elementi tipizzati in modo anonimo combinando una variabile locale tipizzata
in modo implicito e una matrice tipizzata in modo implicito, come illustrato nell'esempio seguente.

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

Osservazioni
I tipi anonimi sono tipi class che derivano da un tipo object e dei quali non può essere eseguito il cast ad alcun
tipo, eccetto al tipo object. Il compilatore fornisce un nome per qualsiasi tipo anonimo, anche se l'applicazione
non può accedervi. Dal punto di vista di Common Language Runtime, un tipo anonimo non è diverso da
qualsiasi altro tipo di riferimento.
Se due o più inizializzatori di oggetti anonimi in un assembly specificano una sequenza di proprietà nello stesso
ordine e con gli stessi nomi e tipi, il compilatore considera gli oggetti come istanze dello stesso tipo.
Condividono le stesse informazioni sul tipo generate dal compilatore.
Non è possibile dichiarare un campo, una proprietà, un evento o il tipo restituito di un metodo specificando un
tipo anonimo. In modo analogo, non è possibile dichiarare un parametro formale di un metodo, una proprietà,
un costruttore o un indicizzatore specificando un tipo anonimo. Per passare un tipo anonimo o una raccolta
contenente tipi anonimi come argomento a un metodo, è possibile dichiarare il parametro come oggetto di tipo.
In questo modo si annulla tuttavia lo scopo della tipizzazione forte. Se è necessario archiviare i risultati delle
query o passarli oltre i limiti del metodo, si consideri l'uso di uno struct o una classe con nome normale invece
di un tipo anonimo.
I metodi Equals e GetHashCode nei tipi anonimi sono definiti in termini di metodi delle proprietà Equals e
GetHashCode , di conseguenza due istanze dello stesso tipo anonimo sono uguali solo se tutte le relative
proprietà sono uguali.

Vedere anche
Guida per programmatori C#
Inizializzatori di oggetto e di raccolta
Nozioni di base su LINQ in C#
LINQ in C#
Come restituire subset di proprietà degli elementi in
una query (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Usare un tipo anonimo in un'espressione di query quando si verificano entrambe le condizioni seguenti:
Si vogliono restituire solo alcune delle proprietà di ogni elemento di origine.
Non è necessario archiviare la query all'esterno dell'ambito del metodo in cui è stata eseguita.
Se si vuole restituire solo una proprietà o un campo da ogni elemento di origine, è sufficiente usare l'operatore
punto nella clausola select . Per restituire, ad esempio, solo l' ID di ogni student , scrivere la clausola select
come segue:

select student.ID;

Esempio
Nell'esempio seguente viene illustrato come usare un tipo anonimo per restituire solo un sottoinsieme delle
proprietà di ogni elemento di origine che corrisponde alla condizione specificata.

private static void QueryByScore()


{
// Create the query. var is required because
// the query produces a sequence of anonymous types.
var queryHighScores =
from student in students
where student.ExamScores[0] > 95
select new { student.FirstName, student.LastName };

// Execute the query.


foreach (var obj in queryHighScores)
{
// The anonymous type's properties were not named. Therefore
// they have the same names as the Student properties.
Console.WriteLine(obj.FirstName + ", " + obj.LastName);
}
}
/* Output:
Adams, Terry
Fakhouri, Fadi
Garcia, Cesar
Omelchenko, Svetlana
Zabokritski, Eugene
*/

Si noti che se per le proprietà non è specificato alcun nome, il tipo anonimo usa i nomi dell'elemento di origine.
Per assegnare nuovi nomi alle proprietà nel tipo anonimo, scrivere l'istruzione select come segue:

select new { First = student.FirstName, Last = student.LastName };

Se si tenta questa operazione nell'esempio precedente, è necessario modificare anche l'istruzione


Console.WriteLine :
Console.WriteLine(student.First + " " + student.Last);

Compilazione del codice


Per eseguire questo codice, copiare e incollare la classe in un'applicazione console C# con una direttiva using
per System.Linq.

Vedi anche
Guida per programmatori C#
Tipi anonimi
LINQ in C#
Interfacce (Guida per programmatori C#)
02/11/2020 • 8 minutes to read • Edit Online

Un'interfaccia contiene le definizioni per un gruppo di funzionalità correlate che devono essere implementate da
una classe non astratta o da uno struct . Un'interfaccia può definire static metodi, che devono disporre di
un'implementazione di. A partire da C# 8,0, un'interfaccia può definire un'implementazione predefinita per i
membri. Un'interfaccia non può dichiarare i dati dell'istanza, ad esempio i campi, le proprietà implementate
automaticamente o gli eventi di tipo proprietà.
Usando le interfacce, è possibile, ad esempio, includere il comportamento di più origini in una classe. Tale
funzionalità è importante in C# perché il linguaggio non supporta l'ereditarietà multipla delle classi. Inoltre è
necessario usare un'interfaccia se si vuole simulare l'ereditarietà per le struct, perché non possono
effettivamente ereditare da un'altra struct o classe.
Per definire un'interfaccia, è possibile usare la parola chiave Interface , come illustrato nell'esempio seguente.

interface IEquatable<T>
{
bool Equals(T obj);
}

Il nome di un'interfaccia deve essere un nome di identificatoreC# valido. Per convenzione, i nomi di interfaccia
iniziano con una lettera I maiuscola.
Qualsiasi classe o struct che implementa l'interfaccia IEquatable<T> deve contenere una definizione per un
metodo Equals che corrisponde alla firma specificata dall'interfaccia. Di conseguenza, è possibile affidarsi a una
classe che implementa IEquatable<T> per contenere un metodo Equals con cui un'istanza della classe può
determinare se sia uguale a un'altra istanza della stessa classe.
La definizione di IEquatable<T> non fornisce un'implementazione per Equals . Una classe o uno struct può
implementare più interfacce, ma una classe può ereditare solo da una singola classe.
Per altre informazioni sulle classi astratte, vedere Classi e membri delle classi astratte e sealed.
Le interfacce possono contenere metodi di istanza, proprietà, eventi, indicizzatori o qualsiasi combinazione di
questi quattro tipi di membri. Le interfacce possono contenere costruttori statici, campi, costanti o operatori. Per
collegamenti a esempi, vedere Sezioni correlate. Un'interfaccia non può contenere campi di istanza, costruttori di
istanza o finalizzatori. I membri di interfaccia sono public per impostazione predefinita.
Per implementare un membro di interfaccia, il corrispondente membro della classe di implementazione deve
essere pubblico e non statico e avere lo stesso nome e la stessa firma del membro di interfaccia.
Quando una classe o uno struct implementa un'interfaccia, la classe o lo struct deve fornire un'implementazione
per tutti i membri dichiarati dall'interfaccia, ma non fornisce un'implementazione predefinita per. Tuttavia, se una
classe base implementa un'interfaccia, qualsiasi classe derivata dalla classe base eredita tale implementazione.
Nell'esempio seguente viene illustrata un'implementazione dell'interfaccia IEquatable<T>. La classe di
implementazione, Car , deve fornire un'implementazione del metodo Equals.
public class Car : IEquatable<Car>
{
public string Make {get; set;}
public string Model { get; set; }
public string Year { get; set; }

// Implementation of IEquatable<T> interface


public bool Equals(Car car)
{
return (this.Make, this.Model, this.Year) ==
(car.Make, car.Model, car.Year);
}
}

Le proprietà e gli indicizzatori di una classe possono definire altre funzioni di accesso per una proprietà o un
indicizzatore definito in un'interfaccia. Ad esempio, un'interfaccia può dichiarare una proprietà con una funzione
di accesso get. La classe che implementa l'interfaccia può dichiarare la stessa proprietà con una funzione di
accesso get o set. Tuttavia, se la proprietà o l'indicizzatore usa l'implementazione esplicita, le funzioni di
accesso devono corrispondere. Per altre informazioni sull'implementazione esplicita, vedere Implementazione
esplicita dell'interfaccia e Proprietà dell'interfaccia.
Le interfacce possono ereditare da una o più interfacce. L'interfaccia derivata eredita i membri dalle relative
interfacce di base. Una classe che implementa un'interfaccia derivata deve implementare tutti i membri
nell'interfaccia derivata, inclusi tutti i membri delle interfacce di base dell'interfaccia derivata. Tale classe può
essere convertita in modo implicito nell'interfaccia derivata o in una delle interfacce di base. Una classe può
includere un'interfaccia più volte tramite le classi di base ereditate o tramite le interfacce ereditate da altre
interfacce. Tuttavia, la classe può fornire un'implementazione di un'interfaccia solo una volta e solo se la classe
dichiara l'interfaccia durante la definizione della classe ( class ClassName : InterfaceName ). Se l'interfaccia viene
ereditata perché è stata ereditata una classe base che implementa l'interfaccia, la classe base fornisce
l'implementazione dei membri dell'interfaccia. Tuttavia, la classe derivata può reimplementare qualsiasi membro
dell'interfaccia invece di usare l'implementazione ereditata. Quando le interfacce dichiarano
un'implementazione predefinita di un metodo, qualsiasi classe che implementa tale interfaccia eredita tale
implementazione. Le implementazioni definite nelle interfacce sono virtuali e la classe di implementazione può
eseguire l'override dell'implementazione.
Una classe base può implementare anche i membri di interfaccia usando membri virtuali. In tal caso, una classe
derivata può modificare il comportamento dell'interfaccia eseguendo l'override dei membri virtuali. Per altre
informazioni su membri virtuali, vedere Polimorfismo.

Riepilogo delle interfacce


Un'interfaccia presenta le proprietà seguenti:
Un'interfaccia è in genere simile a una classe di base astratta con solo membri astratti. Qualsiasi classe o
struct che implementa l'interfaccia deve implementarne tutti i membri. Facoltativamente, un'interfaccia può
definire implementazioni predefinite per alcuni o tutti i relativi membri. Per altre informazioni, vedere metodi
di interfaccia predefiniti.
Non è possibile creare direttamente un'istanza di un'interfaccia. I membri vengono implementati da qualsiasi
classe o struct che implementa l'interfaccia.
Una classe o struct può implementare più interfacce. Una classe può ereditare una classe base e anche
implementare una o più interfacce.

Sezioni correlate
Proprietà dell'interfaccia
Indicizzatori nelle interfacce
Come implementare eventi di interfaccia
Classi e struct
Ereditarietà
Interfacce
Metodi
Polimorfismo
Classi e membri delle classi astratte e sealed
Proprietà
Eventi
Indicizzatori

Vedere anche
Guida per programmatori C#
Ereditarietà
Nomi di identificatore
Implementazione esplicita dell'interfaccia (Guida per
programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Se class implementa due interfacce che contengono un membro con la stessa firma e quest'ultimo viene
implementato nella classe, entrambe le interfacce useranno il membro come propria implementazione.
Nell'esempio seguente tutte le chiamate a Paint richiamano lo stesso metodo. Il primo esempio definisce i tipi:

public interface IControl


{
void Paint();
}
public interface ISurface
{
void Paint();
}
public class SampleClass : IControl, ISurface
{
// Both ISurface.Paint and IControl.Paint call this method.
public void Paint()
{
Console.WriteLine("Paint method in SampleClass");
}
}

Nell'esempio seguente vengono chiamati i metodi:

SampleClass sample = new SampleClass();


IControl control = sample;
ISurface surface = sample;

// The following lines all call the same method.


sample.Paint();
control.Paint();
surface.Paint();
// Output:
// Paint method in SampleClass
// Paint method in SampleClass
// Paint method in SampleClass

Quando due membri di interfaccia non eseguono la stessa funzione, comporta un'implementazione non corretta
di una o entrambe le interfacce. È possibile implementare un membro di interfaccia in modo esplicito, creando
un membro della classe che viene chiamato solo tramite l'interfaccia ed è specifico di tale interfaccia.
Denominare il membro della classe con il nome dell'interfaccia e un punto. Ad esempio:
public class SampleClass : IControl, ISurface
{
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}

Il membro di classe IControl.Paint è disponibile solo tramite l'interfaccia IControl e ISurface.Paint è


disponibile solo tramite ISurface . Entrambe le implementazioni dei metodi sono separate e nessuna delle due
è disponibile direttamente nella classe. Ad esempio:

// Call the Paint methods from Main.

SampleClass obj = new SampleClass();


//obj.Paint(); // Compiler error.

IControl c = obj;
c.Paint(); // Calls IControl.Paint on SampleClass.

ISurface s = obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.

// Output:
// IControl.Paint
// ISurface.Paint

L'implementazione esplicita viene usata anche per risolvere i casi in cui due interfacce dichiarano membri
diversi con lo stesso nome, ad esempio una proprietà e un metodo. Per implementare entrambe le interfacce,
una classe deve usare l'implementazione esplicita per la proprietà P o il metodo P , o entrambi, per evitare un
errore del compilatore. Ad esempio:

interface ILeft
{
int P { get;}
}
interface IRight
{
int P();
}

class Middle : ILeft, IRight


{
public int P() { return 0; }
int ILeft.P { get { return 0; } }
}

A partire da C# 8,0, è possibile definire un'implementazione per i membri dichiarati in un'interfaccia. Se una
classe eredita un'implementazione del metodo da un'interfaccia, il metodo è accessibile solo tramite un
riferimento del tipo di interfaccia. Il membro ereditato non viene visualizzato come parte dell'interfaccia
pubblica. Nell'esempio seguente viene definita un'implementazione predefinita per un metodo di interfaccia:
public interface IControl
{
void Paint() => Console.WriteLine("Default Paint method");
}
public class SampleClass : IControl
{
// Paint() is inherited from IControl.
}

Nell'esempio seguente viene richiamata l'implementazione predefinita:

var sample = new SampleClass();


//sample.Paint();// "Paint" isn't accessible.
var control = sample as IControl;
control.Paint();

Qualsiasi classe che implementa l' IControl interfaccia può eseguire l'override del Paint metodo predefinito,
sia come metodo pubblico, che come implementazione esplicita dell'interfaccia.

Vedere anche
Guida per programmatori C#
Classi e struct
Interfacce
Ereditarietà
Come implementare in modo esplicito i membri di
interfaccia (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

In questo esempio vengono dichiarate un' interfaccia, IDimensions , e una classe, Box , che implementa in
modo esplicito i membri GetLength di interfaccia e GetWidth . L'accesso ai membri avviene tramite l'istanza di
interfaccia dimensions .

Esempio
interface IDimensions
{
float GetLength();
float GetWidth();
}

class Box : IDimensions


{
float lengthInches;
float widthInches;

Box(float length, float width)


{
lengthInches = length;
widthInches = width;
}
// Explicit interface member implementation:
float IDimensions.GetLength()
{
return lengthInches;
}
// Explicit interface member implementation:
float IDimensions.GetWidth()
{
return widthInches;
}

static void Main()


{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an interface instance dimensions:


IDimensions dimensions = box1;

// The following commented lines would produce compilation


// errors because they try to access an explicitly implemented
// interface member from a class instance:
//System.Console.WriteLine("Length: {0}", box1.GetLength());
//System.Console.WriteLine("Width: {0}", box1.GetWidth());

// Print out the dimensions of the box by calling the methods


// from an instance of the interface:
System.Console.WriteLine("Length: {0}", dimensions.GetLength());
System.Console.WriteLine("Width: {0}", dimensions.GetWidth());
}
}
/* Output:
Length: 30
Width: 20
*/

Programmazione efficiente
Si noti che le righe seguenti, nel metodo Main , sono impostate come commento perché produrrebbero
errori di compilazione. Non è possibile accedere a un membro di interfaccia implementato in modo
esplicito da un'istanza di una classe:

//System.Console.WriteLine("Length: {0}", box1.GetLength());


//System.Console.WriteLine("Width: {0}", box1.GetWidth());

Si noti anche che le righe seguenti, nel metodo Main , stampano le dimensioni di una casella, perché i
metodi vengono chiamati da un'istanza dell'interfaccia:
System.Console.WriteLine("Length: {0}", dimensions.GetLength());
System.Console.WriteLine("Width: {0}", dimensions.GetWidth());

Vedere anche
Guida per programmatori C#
Classi e struct
Interfacce
Come implementare in modo esplicito i membri di due interfacce
Come implementare in modo esplicito i membri di
due interfacce (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

L'implementazione di interfaccia esplicita consente inoltre al programmatore di implementare due interfacce


con gli stessi nomi di membro e assegnare a ogni membro dell'interfaccia un'implementazione separata. Questo
esempio mostra le dimensioni di una casella sia in unità metriche che anglosassoni. La classe Box implementa
due interfacce, IEnglishDimensions e IMetricDimensions, che rappresentano i diversi sistemi di misura.
Entrambe le interfacce hanno nomi di membri identici, Length e Width.

Esempio
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}

// Declare the metric units interface:


interface IMetricDimensions
{
float Length();
float Width();
}

// Declare the Box class that implements the two interfaces:


// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
float lengthInches;
float widthInches;

public Box(float lengthInches, float widthInches)


{
this.lengthInches = lengthInches;
this.widthInches = widthInches;
}

// Explicitly implement the members of IEnglishDimensions:


float IEnglishDimensions.Length() => lengthInches;

float IEnglishDimensions.Width() => widthInches;

// Explicitly implement the members of IMetricDimensions:


float IMetricDimensions.Length() => lengthInches * 2.54f;

float IMetricDimensions.Width() => widthInches * 2.54f;

static void Main()


{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an instance of the English units interface:


IEnglishDimensions eDimensions = box1;

// Declare an instance of the metric units interface:


IMetricDimensions mDimensions = box1;

// Print dimensions in English units:


System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
System.Console.WriteLine("Width (in): {0}", eDimensions.Width());

// Print dimensions in metric units:


System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
}
/* Output:
Length(in): 30
Width (in): 20
Length(cm): 76.2
Width (cm): 50.8
*/

Programmazione efficiente
Se si vogliono impostare come predefinite le misure in unità anglosassoni, implementare i metodi Length e
Width normalmente e implementare in modo esplicito i metodi Length e Width dall'interfaccia
IMetricDimensions:

// Normal implementation:
public float Length() => lengthInches;
public float Width() => widthInches;

// Explicit implementation:
float IMetricDimensions.Length() => lengthInches * 2.54f;
float IMetricDimensions.Width() => widthInches * 2.54f;

In questo caso, è possibile accedere alle unità anglosassoni dall'istanza della classe e alle unità metriche
dall'istanza dell'interfaccia:

public static void Test()


{
Box box1 = new Box(30.0f, 20.0f);
IMetricDimensions mDimensions = box1;

System.Console.WriteLine("Length(in): {0}", box1.Length());


System.Console.WriteLine("Width (in): {0}", box1.Width());
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}

Vedere anche
Guida per programmatori C#
Classi e struct
Interfacce
Come implementare in modo esplicito i membri di interfaccia
Delegati (Guida per programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

Un delegate è un tipo che rappresenta riferimenti ai metodi con un elenco di parametri e un tipo restituito
particolari. Quando si crea un'istanza di un delegato, è possibile associare l'istanza a qualsiasi metodo con una
firma compatibile e un tipo restituito. Tramite l'istanza di delegato è possibile richiamare (o chiamare) il metodo.
I delegati vengono utilizzati per passare metodi come argomenti ad altri metodi. I gestori di evento non sono
altro che metodi richiamati tramite delegati. Creare un metodo personalizzato e una classe, ad esempio un
controllo Windows, che può chiamare tale metodo quando si verifica un determinato evento. Nell'esempio che
segue viene illustrata la dichiarazione di un delegato:

public delegate int PerformCalculation(int x, int y);

Qualsiasi metodo di qualsiasi classe o struct accessibile che corrisponde al tipo di delegato può essere
assegnato al delegato. Il metodo può essere un metodo statico o di istanza. In questo modo è possibile
modificare le chiamate ai metodi a livello di codice, nonché inserire nuovo codice nelle classi esistenti.

NOTE
Nel contesto di overload dei metodi, la firma di un metodo non include il valore restituito, mentre nel contesto dei
delegati, la firma include il valore restituito. In altre parole, un metodo deve restituire lo stesso tipo del delegato.

La possibilità di fare riferimento a un metodo come parametro rende i delegati ideali per la definizione di
metodi di callback. È ad esempio possibile passare un riferimento a un metodo per il confronto di due oggetti
passati come argomento a un algoritmo di ordinamento. Poiché il codice di confronto è in una routine separata,
l'algoritmo di ordinamento può essere scritto in modo più generale.

Panoramica dei delegati


Di seguito sono riportate le proprietà dei delegati:
I delegati sono simili ai puntatori a funzione del linguaggio C++, ma sono interamente orientati agli
oggetti e, a differenza dei puntatori C++ a funzioni membro, incapsulano sia un'istanza che un metodo
dell'oggetto.
Consentono di passare metodi come parametri.
Possono essere utilizzati per definire metodi di callback.
Possono essere concatenati, ad esempio per chiamare più metodi su un singolo evento.
Non devono corrispondere necessariamente al tipo del delegato. Per altre informazioni, vedere Uso della
varianza nei delegati.
In C# versione 2.0 è stato introdotto il concetto di metodi anonimi, ovvero metodi che consentono di
passare blocchi di codice come parametri in alternativa a un metodo definito separatamente. In C# 3.0
sono state introdotte le espressioni lambda per scrivere in modo più conciso i blocchi di codice in linea. I
metodi anonimi e le espressioni lambda vengono compilati, in determinati contesti, in tipi delegati.
Queste funzionalità sono ora note complessivamente come funzioni anonime. Per altre informazioni sulle
espressioni lambda, vedere Espressioni lambda.
Contenuto della sezione
Utilizzo di delegati
Quando usare i delegati anziché le interfacce (Guida per programmatori C#)
Delegati con metodi denominati Metodi anonimi
Uso della varianza nei delegati
Come combinare delegati (delegati multicast)
Come dichiarare un delegato, crearne un'istanza e usarlo

Specifiche del linguaggio C#


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

Capitoli del libro rappresentati


Delegates, Events, and Lambda Expressions (Delegati, eventi ed espressioni lambda) in C# 3.0 Cookbook, Third
Edition: More than 250 solutions for C# 3.0 programmers
Delegati ed eventi (Delegati, eventi ed espressioni lambda) in Learning C# 3.0: Master the fundamentals of C#
3.0

Vedere anche
Delegate
Guida per programmatori C#
Eventi
Utilizzo di delegati (Guida per programmatori C#)
02/11/2020 • 10 minutes to read • Edit Online

Un delegato è un tipo che incapsula in modo sicuro un metodo, simile a un puntatore a funzione in C e C++. A
differenza dei puntatori a funzione, tuttavia, i delegati sono orientati a oggetti, indipendenti dai tipi e sicuri. Il
tipo delegato è definito dal nome del delegato. Nell'esempio seguente viene dichiarato un delegato denominato
Del che può incapsulare un metodo che accetta una stringa come argomento e restituisce void:

public delegate void Del(string message);

Un oggetto delegato viene normalmente creato fornendo il nome del metodo di cui il delegato eseguirà il
wrapping o con una funzione anonima. Una volta che viene creata un'istanza di un delegato, una chiamata al
metodo effettuata al delegato verrà passata dal delegato a tale metodo. I parametri passati al delegato dal
chiamante vengono passati al metodo e il valore restituito, se presente, dal metodo viene restituito al chiamante
dal delegato. Questa operazione è nota come richiamare il delegato. È possibile richiamare un delegato per cui è
stata creata un'istanza come se fosse il metodo di wrapping stesso. Ad esempio:

// Create a method for a delegate.


public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}

// Instantiate the delegate.


Del handler = DelegateMethod;

// Call the delegate.


handler("Hello World");

I tipi delegati sono derivati dalla Delegate classe in .NET. I tipi delegati sono sealed, ovvero non possono essere
usati per la derivazione, e non è possibile derivare classi personalizzate da Delegate. Poiché l'istanza del delegato
è un oggetto, può essere passata come parametro o assegnata a una proprietà. In questo modo un metodo può
accettare un delegato come parametro e chiamare il delegato in un secondo momento. Questa operazione è
nota come callback asincrono ed è un metodo comune per notificare un chiamante al termine di un processo
lungo. Quando un delegato viene usato in questo modo, per il codice che usa il delegato non è richiesta alcuna
conoscenza dell'implementazione del metodo in uso. La funzionalità è simile all'incapsulamento fornito dalle
interfacce.
Un altro utilizzo comune dei callback è la definizione di un metodo di confronto personalizzato e il passaggio di
tale delegato a un metodo di ordinamento. Consente al codice del chiamante di entrare a far parte
dell'algoritmo di ordinamento. Nell'esempio di metodo seguente viene usato il tipo Del come parametro:

public static void MethodWithCallback(int param1, int param2, Del callback)


{
callback("The number is: " + (param1 + param2).ToString());
}

È quindi possibile passare il delegato creato in precedenza a tale metodo:


MethodWithCallback(1, 2, handler);

e visualizzare il seguente output sulla console:

The number is: 3

Usando il delegato come astrazione, non è necessario chiamare direttamente la console da MethodWithCallback ,
ovvero questo non deve essere progettato tenendo presente una console. MethodWithCallback si limita a
preparare una stringa e a passarla a un altro metodo. Questa operazione è particolarmente efficace perché un
metodo delegato può usare qualsiasi numero di parametri.
Quando viene creato un delegato per eseguire il wrapping di un metodo di istanza, il delegato fa riferimento sia
all'istanza sia al metodo. Un delegato non ha alcuna conoscenza del tipo di istanza a parte il metodo di cui
esegue il wrapping, perciò un delegato può fare riferimento a qualsiasi tipo di oggetto a condizione che vi sia un
metodo su tale oggetto che corrisponda alla firma del delegato. Quando viene creato un delegato per eseguire il
wrapping di un metodo statico, fa riferimento solo al metodo. Si considerino le dichiarazioni seguenti:

public class MethodClass


{
public void Method1(string message) { }
public void Method2(string message) { }
}

Insieme al metodo statico DelegateMethod illustrato in precedenza, ci sono tre metodi di cui è possibile eseguire
il wrapping in un'istanza di Del .
Un delegato può chiamare più di un metodo, quando viene richiamato. Questo processo viene definito
multicasting. Per aggiungere un ulteriore metodo all'elenco dei metodi del delegato (l'elenco chiamate), è
necessario semplicemente aggiungere due delegati usando gli operatori addizione o di assegnazione di
addizione ("+" o "+="). Ad esempio:

var obj = new MethodClass();


Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.


Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

A questo punto allMethodsDelegate contiene tre metodi nel relativo elenco chiamate: Method1 , Method2 e
DelegateMethod . I tre delegati originali, d1 , d2 e d3 , rimangono invariati. Quando si richiama
allMethodsDelegate , tutti e tre i metodi vengono chiamati nell'ordine. Se il delegato usa parametri per
riferimento, il riferimento viene passato in sequenza a ciascuno dei tre metodi a turno e le eventuali modifiche
apportate da un solo metodo saranno visibili al metodo successivo. Quando uno dei metodi genera
un'eccezione non rilevata all'interno del metodo, tale eccezione viene passata al chiamante del delegato e non
verrà chiamato nessun metodo successivo nell'elenco chiamate. Se il delegato ha un valore restituito e/o i
parametri out, restituisce il valore restituito e i parametri dell'ultimo metodo richiamato. Per rimuovere un
metodo dall'elenco chiamate, utilizzare gli operatori di assegnazione di sottrazione o sottrazione ( - o -= ). Ad
esempio:
//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2


Del oneMethodDelegate = allMethodsDelegate - d2;

Poiché i tipi delegati vengono derivati da System.Delegate , i metodi e le proprietà definiti da tale classe possono
essere chiamati sul delegato. Ad esempio, per trovare il numero di metodi nell'elenco chiamate di un delegato, è
possibile scrivere:

int invocationCount = d1.GetInvocationList().GetLength(0);

I delegati con più metodi nel relativo elenco chiamate derivano da MulticastDelegate, cioè una sottoclasse di
System.Delegate . Il codice sopra riportato funziona in entrambi i casi, perché entrambe le classi supportano
GetInvocationList .

I delegati multicast vengono ampiamente usati nella gestione degli eventi. Gli oggetti di origine evento inviano
notifiche di eventi agli oggetti destinatario registrati per ricevere l'evento. Per registrarsi a un evento, il
destinatario crea un metodo che può gestire l'evento, quindi crea un delegato per il metodo e passa il delegato
all'origine evento. L'origine chiama il delegato quando si verifica l'evento. Il delegato chiama quindi il metodo di
gestione eventi sul destinatario, recapitando i dati dell'evento. Il tipo delegato per un determinato evento è
definito dall'origine evento. Per altre informazioni, vedere Eventi.
Se si confrontano delegati di due tipi diversi assegnati in fase di compilazione si avrà un errore di compilazione.
Se le istanze dei delegati sono staticamente del tipo System.Delegate , il confronto è consentito, ma restituirà
false in fase di esecuzione. Ad esempio:

delegate void Delegate1();


delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)


{
// Compile-time error.
//Console.WriteLine(d == e);

// OK at compile-time. False if the run-time type of f


// is not the same as that of d.
Console.WriteLine(d == f);
}

Vedere anche
Guida per programmatori C#
Delegati
Uso della varianza nei delegati
Varianza nei delegati
Uso della varianza per i delegati generici Func e Action
Eventi
Delegati con metodi denominati e anonimi (Guida
per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

È possibile associare un delegato a un metodo denominato. Quando si crea un'istanza di un delegato usando un
metodo denominato, il metodo viene passato come parametro, ad esempio:

// Declare a delegate.
delegate void Del(int x);

// Define a named method.


void DoWork(int k) { /* ... */ }

// Instantiate the delegate using the method as a parameter.


Del d = obj.DoWork;

La chiamata viene eseguita usando un metodo denominato. I delegati costruiti con un metodo denominato
possono incapsulare un metodo statico o un metodo di istanza. I metodi denominati rappresentano l'unico
modo per creare un'istanza di un delegato nelle versioni precedenti di C#. In una situazione in cui la creazione di
un nuovo metodo rappresenta un sovraccarico inutile, tuttavia, C# consente di creare un'istanza di un delegato e
di specificare immediatamente un blocco di codice che verrà elaborato dal delegato al momento della chiamata.
Il blocco può contenere un'espressione lambda oppure un metodo anonimo. Per altre informazioni, vedere
Funzioni anonime.

Osservazioni
La firma del metodo che viene passato come parametro del delegato deve essere uguale a quella della
dichiarazione del delegato.
Un'istanza di delegato può incapsulare un metodo statico o un metodo di istanza.
Anche se il delegato può usare un parametro out, è consigliabile non usarlo con delegati di eventi multicast
perché non è possibile sapere quale delegato verrà chiamato.

Esempio 1
Di seguito è illustrato un semplice esempio di dichiarazione e uso di un delegato. Si noti che sia il delegato, Del ,
che il metodo associato, MultiplyNumbers , hanno la stessa firma
// Declare a delegate
delegate void Del(int i, double j);

class MathClass
{
static void Main()
{
MathClass m = new MathClass();

// Delegate instantiation using "MultiplyNumbers"


Del d = m.MultiplyNumbers;

// Invoke the delegate object.


Console.WriteLine("Invoking the delegate using 'MultiplyNumbers':");
for (int i = 1; i <= 5; i++)
{
d(i, 2);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

// Declare the associated method.


void MultiplyNumbers(int m, double n)
{
Console.Write(m * n + " ");
}
}
/* Output:
Invoking the delegate using 'MultiplyNumbers':
2 4 6 8 10
*/

Esempio 2
Nell'esempio che segue un delegato viene mappato sia al metodo statico che al metodo di istanza e restituisce
informazioni specifiche da ciascuno di essi.
// Declare a delegate
delegate void Del();

class SampleClass
{
public void InstanceMethod()
{
Console.WriteLine("A message from the instance method.");
}

static public void StaticMethod()


{
Console.WriteLine("A message from the static method.");
}
}

class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();

// Map the delegate to the instance method:


Del d = sc.InstanceMethod;
d();

// Map to the static method:


d = SampleClass.StaticMethod;
d();
}
}
/* Output:
A message from the instance method.
A message from the static method.
*/

Vedere anche
Guida per programmatori C#
Delegati
Come combinare delegati (delegati multicast)
Eventi
Come combinare delegati (delegati multicast)
(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio illustra come creare delegati multicast. Una proprietà utile degli oggetti delegate è la possibilità
di assegnare più oggetti a un'istanza di delegato usando l'operatore + . Il delegato multicast contiene un elenco
dei delegati assegnati. Quando il delegato multicast viene chiamato, richiama i delegati nell'elenco, in ordine.
Solo i delegati dello stesso tipo possono essere combinati.
L'operatore - può essere usato per rimuovere un delegato componente da un delegato multicast.

Esempio
using System;

// Define a custom delegate that has a string parameter and returns void.
delegate void CustomDel(string s);

class TestClass
{
// Define two methods that have the same signature as CustomDel.
static void Hello(string s)
{
Console.WriteLine($" Hello, {s}!");
}

static void Goodbye(string s)


{
Console.WriteLine($" Goodbye, {s}!");
}

static void Main()


{
// Declare instances of the custom delegate.
CustomDel hiDel, byeDel, multiDel, multiMinusHiDel;

// In this example, you can omit the custom delegate if you


// want to and use Action<string> instead.
//Action<string> hiDel, byeDel, multiDel, multiMinusHiDel;

// Create the delegate object hiDel that references the


// method Hello.
hiDel = Hello;

// Create the delegate object byeDel that references the


// method Goodbye.
byeDel = Goodbye;

// The two delegates, hiDel and byeDel, are combined to


// form multiDel.
multiDel = hiDel + byeDel;

// Remove hiDel from the multicast delegate, leaving byeDel,


// which calls only the method Goodbye.
multiMinusHiDel = multiDel - hiDel;

Console.WriteLine("Invoking delegate hiDel:");


hiDel("A");
Console.WriteLine("Invoking delegate byeDel:");
byeDel("B");
Console.WriteLine("Invoking delegate multiDel:");
multiDel("C");
Console.WriteLine("Invoking delegate multiMinusHiDel:");
multiMinusHiDel("D");
}
}
/* Output:
Invoking delegate hiDel:
Hello, A!
Invoking delegate byeDel:
Goodbye, B!
Invoking delegate multiDel:
Hello, C!
Goodbye, C!
Invoking delegate multiMinusHiDel:
Goodbye, D!
*/
Vedere anche
MulticastDelegate
Guida per programmatori C#
Eventi
Come dichiarare, creare un'istanza e usare un
delegato (Guida per programmatori C#)
28/01/2021 • 7 minutes to read • Edit Online

In C# 1.0 e versioni successive i delegati possono essere dichiarati come illustrato nell'esempio seguente.

// Declare a delegate.
delegate void Del(string str);

// Declare a method with the same signature as the delegate.


static void Notify(string name)
{
Console.WriteLine($"Notification received for: {name}");
}

// Create an instance of the delegate.


Del del1 = new Del(Notify);

In C# 2.0 è disponibile un metodo più semplice per scrivere la dichiarazione precedente, come illustrato
nell'esempio seguente.

// C# 2.0 provides a simpler way to declare an instance of Del.


Del del2 = Notify;

In C# 2.0 e versioni successive, è anche possibile usare un metodo anonimo per dichiarare e inizializzare un
delegato, come illustrato nell'esempio seguente.

// Instantiate Del by using an anonymous method.


Del del3 = delegate(string name)
{ Console.WriteLine($"Notification received for: {name}"); };

In C# 3.0 e versioni successive, è inoltre possibile dichiarare i delegati e crearne un'istanza usando
un'espressione lambda, come illustrato nell'esempio seguente.

// Instantiate Del by using a lambda expression.


Del del4 = name => { Console.WriteLine($"Notification received for: {name}"); };

Per altre informazioni, vedere espressioni lambda.


Nell'esempio che segue viene illustrato come dichiarare un delegato, crearne un'istanza e usarlo. La classe
BookDB incapsula il database di una libreria che gestisce un database di volumi. Espone un metodo,
ProcessPaperbackBooks , che ricerca tutti i tascabili all'interno del database e chiama un delegato per ognuno di
essi. Il tipo delegate usato viene denominato ProcessBookCallback . La classe Test usa questa classe per
stampare i titoli e il prezzo medio dei tascabili.
L'uso dei delegati consente la separazione ottimale delle funzionalità tra il database della libreria e il codice
client. Il codice client non contiene alcuna informazione sulle modalità di archiviazione dei libri o sul
meccanismo che consente al codice di individuare i tascabili. Il codice della libreria non contiene alcuna
informazione sull'elaborazione effettuata sui tascabili individuati.
Esempio
// A set of classes for handling a bookstore:
namespace Bookstore
{
using System.Collections;

// Describes a book in the book list:


public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?

public Book(string title, string author, decimal price, bool paperBack)


{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}

// Declare a delegate type for processing a book:


public delegate void ProcessBookCallback(Book book);

// Maintains a book database.


public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();

// Add a book to the database:


public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}

// Call a passed-in delegate on each paperback book to process it:


public void ProcessPaperbackBooks(ProcessBookCallback processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}

// Using the Bookstore classes:


namespace BookTestClient
{
using Bookstore;

// Class to total and average prices of books:


class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;

internal void AddBookToTotal(Book book)


{
countBooks += 1;
priceBooks += book.Price;
}
}

internal decimal AveragePrice()


{
return priceBooks / countBooks;
}
}

// Class to test the book database:


class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine($" {b.Title}");
}

// Execution starts here.


static void Main()
{
BookDB bookDB = new BookDB();

// Initialize the database with some books:


AddBooks(bookDB);

// Print all the titles of paperbacks:


Console.WriteLine("Paperback Book Titles:");

// Create a new delegate object associated with the static


// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(PrintTitle);

// Get the average price of a paperback by using


// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();

// Create a new delegate object associated with the nonstatic


// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

Console.WriteLine("Average Paperback Book Price: ${0:#.##}",


totaller.AveragePrice());
}

// Initialize the book database with some test books:


static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m,
true);
bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
}
}
}
/* Output:
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
*/

Programmazione efficiente
Dichiarazione di un delegato.
L'istruzione seguente dichiara un nuovo tipo di delegato.

public delegate void ProcessBookCallback(Book book);

Ogni tipo delegato descrive il numero e il tipo degli argomenti e il tipo di valore restituito dai metodi in
esso incapsulati. Ogni volta che è necessario un nuovo set di tipi di argomento o un nuovo tipo di valore
restituito, è necessario dichiarare un nuovo tipo di delegato.
Creazione di un'istanza di un delegato.
Dopo aver dichiarato un tipo delegato, è necessario creare un oggetto delegato e associarlo a un
determinato metodo. Nell'esempio precedente questa operazione viene eseguita passando il metodo
PrintTitle al metodo ProcessPaperbackBooks , come illustrato nell'esempio seguente:

bookDB.ProcessPaperbackBooks(PrintTitle);

In questo modo viene creato un nuovo oggetto delegato associato al metodo statico Test.PrintTitle .
Analogamente, il metodo non statico AddBookToTotal dell'oggetto totaller viene passato come
illustrato nell'esempio seguente:

bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

In entrambi i casi, al metodo ProcessPaperbackBooks viene passato un nuovo oggetto delegato.


Dopo la creazione di un delegato, il metodo ad esso associato non viene mai modificato, poiché gli
oggetti delegati non sono modificabili.
Chiamata a un delegato.
Una volta creato, un oggetto delegato viene in genere passato a un altro codice che chiamerà il delegato.
Per la chiamata di un oggetto delegato viene usato il nome dell'oggetto stesso, seguito dagli argomenti,
racchiusi tra parentesi, che devono essere passati al delegato. Di seguito viene riportato un esempio di
chiamata a un delegato:

processBook(b);

Un delegato può essere chiamato in modo sincrono, come in questo esempio, oppure in modo asincrono
usando i metodi BeginInvoke e EndInvoke .

Vedi anche
Guida per programmatori C#
Eventi
Delegati
Matrici (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

È possibile archiviare più variabili dello stesso tipo in una struttura di dati a matrice. Una matrice viene
dichiarata specificando il tipo degli elementi. Se si desidera che la matrice memorizzi elementi di qualsiasi tipo, è
possibile specificare object come tipo. Nel sistema di tipi unificato di C#, tutti i tipi, predefiniti e definiti
dall'utente, i tipi riferimento e i tipi valore ereditano direttamente o indirettamente da Object.

type[] arrayName;

Esempio
L'esempio seguente consente di creare matrici unidimensionali, multidimensionali e irregolari:

class TestArraysClass
{
static void Main()
{
// Declare a single-dimensional array of 5 integers.
int[] array1 = new int[5];

// Declare and set array element values.


int[] array2 = new int[] { 1, 3, 5, 7, 9 };

// Alternative syntax.
int[] array3 = { 1, 2, 3, 4, 5, 6 };

// Declare a two dimensional array.


int[,] multiDimensionalArray1 = new int[2, 3];

// Declare and set array element values.


int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };

// Declare a jagged array.


int[][] jaggedArray = new int[6][];

// Set the values of the first array in the jagged array structure.
jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
}
}

Panoramica dell'array
Le matrici hanno le proprietà seguenti:
Una matrice può essere unidimensionale, multidimensionale o irregolare.
Il numero di dimensioni e la lunghezza di ogni dimensione sono definiti durante la creazione dell'istanza
della matrice. Questi valori non possono essere modificati per la durata dell'istanza.
I valori predefiniti degli elementi numerici della matrice sono impostati su zero, mentre gli elementi di
riferimento sono impostati su null.
Una matrice irregolare è una matrice di matrici, quindi i relativi elementi sono tipi di riferimento inizializzati
su null .
Le matrici sono a indice zero. Una matrice con n elementi viene indicizzata da 0 a n-1 .
Gli elementi di una matrice possono essere di qualsiasi tipo, anche di tipo matrice.
I tipi matrice sono tipi di riferimento derivati dal tipo di base astratto Array. Poiché questo tipo implementa
IEnumerable e IEnumerable<T>, è possibile usare l'iterazione foreach su tutte le matrici in C#.
Matrici come oggetti
In C# le matrici sono in effetti oggetti e non semplicemente aree indirizzabili di memoria contigua come in C e
C++. Array è il tipo di base astratto di tutti i tipi di matrice. È possibile usare le proprietà e altri membri della
classe Array con. Un esempio è l'uso della Length proprietà per ottenere la lunghezza di una matrice. Il codice
seguente assegna la lunghezza della matrice numbers , ovvero 5 , a una variabile denominata lengthOfNumbers :

int[] numbers = { 1, 2, 3, 4, 5 };
int lengthOfNumbers = numbers.Length;

La classe Array offre numerosi altri utili metodi e proprietà per l'ordinamento, la ricerca e la copia di matrici.
Nell'esempio seguente viene utilizzata la Rank proprietà per visualizzare il numero di dimensioni di una matrice.

class TestArraysClass
{
static void Main()
{
// Declare and initialize an array.
int[,] theArray = new int[5, 10];
System.Console.WriteLine("The array has {0} dimensions.", theArray.Rank);
}
}
// Output: The array has 2 dimensions.

Vedere anche
Come usare le matrici unidimensionali
Come usare matrici multidimensionali
Come utilizzare matrici di matrici
Utilizzo di foreach con matrici
Passaggio di matrici come argomenti
Matrici tipizzate in modo implicito
Guida per programmatori C#
raccolte
Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Matrici unidimensionali (Guida per programmatori
C#)
02/11/2020 • 3 minutes to read • Edit Online

Si crea una matrice unidimensionale usando l'operatore New che specifica il tipo di elemento della matrice e il
numero di elementi. Nell'esempio seguente viene dichiarata una matrice di cinque Integer:

int[] array = new int[5];

Questa matrice contiene gli elementi da array[0] a array[4] . Gli elementi della matrice vengono inizializzati
sul valore predefinito del tipo di elemento, 0 per i numeri interi.
Le matrici possono archiviare qualsiasi tipo di elemento specificato, ad esempio l'esempio seguente che dichiara
una matrice di stringhe:

string[] stringArray = new string[6];

Inizializzazione di una matrice


È possibile inizializzare gli elementi di una matrice quando si dichiara la matrice. L'identificatore di lunghezza
non è necessario perché è dedotto dal numero di elementi nell'elenco di inizializzazione. Ad esempio:

int[] array1 = new int[] { 1, 3, 5, 7, 9 };

Nel codice seguente viene illustrata una dichiarazione di una matrice di stringhe in cui ogni elemento della
matrice viene inizializzato in base al nome di un giorno:

string[] weekDays = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

È possibile evitare l' new espressione e il tipo di matrice quando si Inizializza una matrice al momento della
dichiarazione, come illustrato nel codice seguente. Si tratta di una matrice tipizzata in modo implicito:

int[] array2 = { 1, 3, 5, 7, 9 };
string[] weekDays2 = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

È possibile dichiarare una variabile di matrice senza crearla, ma è necessario usare l' new operatore quando si
assegna una nuova matrice a questa variabile. Ad esempio:

int[] array3;
array3 = new int[] { 1, 3, 5, 7, 9 }; // OK
//array3 = {1, 3, 5, 7, 9}; // Error

Matrici di tipo valore e di tipo riferimento


Considerare la dichiarazione di matrice seguente:
SomeType[] array4 = new SomeType[10];

Il risultato di questa istruzione dipende dal fatto che SomeType sia un tipo valore o un tipo riferimento. Se è un
tipo di valore, l'istruzione crea una matrice di 10 elementi, ognuno dei quali ha il tipo SomeType . Se SomeType è
un tipo riferimento, l'istruzione crea una matrice di 10 elementi, ognuno dei quali è inizializzato su un
riferimento null. In entrambe le istanze gli elementi vengono inizializzati sul valore predefinito per il tipo di
elemento. Per ulteriori informazioni sui tipi di valore e sui tipi di riferimento, vedere tipi di valore e tipi di
riferimento.

Vedere anche
Array
Matrici
Matrici multidimensionali
Matrici irregolari
Matrici multidimensionali (Guida per programmatori
C#)
02/11/2020 • 3 minutes to read • Edit Online

Le matrici possono avere più di una dimensione. La dichiarazione seguente, ad esempio, crea una matrice
bidimensionale di quattro righe e due colonne.

int[,] array = new int[4, 2];

La dichiarazione seguente crea una matrice a tre dimensioni: 4, 2 e 3.

int[,,] array1 = new int[4, 2, 3];

Inizializzazione di una matrice


È possibile inizializzare la matrice al momento della dichiarazione, come illustrato nell'esempio seguente.
// Two-dimensional array.
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// The same array with dimensions specified.
int[,] array2Da = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// A similar array with string elements.
string[,] array2Db = new string[3, 2] { { "one", "two" }, { "three", "four" },
{ "five", "six" } };

// Three-dimensional array.
int[,,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };
// The same array with dimensions specified.
int[,,] array3Da = new int[2, 2, 3] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };

// Accessing array elements.


System.Console.WriteLine(array2D[0, 0]);
System.Console.WriteLine(array2D[0, 1]);
System.Console.WriteLine(array2D[1, 0]);
System.Console.WriteLine(array2D[1, 1]);
System.Console.WriteLine(array2D[3, 0]);
System.Console.WriteLine(array2Db[1, 0]);
System.Console.WriteLine(array3Da[1, 0, 1]);
System.Console.WriteLine(array3D[1, 1, 2]);

// Getting the total count of elements or the length of a given dimension.


var allLength = array3D.Length;
var total = 1;
for (int i = 0; i < array3D.Rank; i++)
{
total *= array3D.GetLength(i);
}
System.Console.WriteLine("{0} equals {1}", allLength, total);

// Output:
// 1
// 2
// 3
// 4
// 7
// three
// 8
// 12
// 12 equals 12

È anche possibile inizializzare la matrice senza specificare il rango.

int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };

Se si sceglie di dichiarare una variabile di matrice senza inizializzazione, è necessario usare l'operatore new per
assegnare una matrice alla variabile. L'utilizzo di new è illustrato nell'esempio seguente.

int[,] array5;
array5 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }; // OK
//array5 = {{1,2}, {3,4}, {5,6}, {7,8}}; // Error

Nell'esempio seguente viene assegnato un valore a un elemento specifico della matrice.

array5[2, 1] = 25;

Analogamente, nell'esempio seguente viene ottenuto il valore di un elemento specifico della matrice, che viene
assegnato alla variabile elementValue .

int elementValue = array5[2, 1];

Nell'esempio di codice seguente, gli elementi della matrice vengono inizializzati in base ai valori predefiniti (ad
eccezione delle matrici di matrici).

int[,] array6 = new int[10, 10];

Vedere anche
Guida per programmatori C#
Matrici
Matrici unidimensionali
Matrici irregolari
Matrici irregolari (Guida per programmatori C#)
28/01/2021 • 4 minutes to read • Edit Online

Una matrice irregolare è una matrice i cui elementi sono matrici, possibilmente di dimensioni diverse. Una
matrice di matrici è chiamata talvolta "matrice irregolare". Gli esempi seguenti mostrano come dichiarare,
inizializzare e accedere a matrici di matrici.
Di seguito è riportata la dichiarazione di una matrice unidimensionale a tre elementi, ognuno dei quali è una
matrice unidimensionale di Integer:

int[][] jaggedArray = new int[3][];

Prima di poter usare jaggedArray , è necessario che siano stati inizializzati i relativi elementi. È possibile
inizializzare gli elementi nel modo seguente:

jaggedArray[0] = new int[5];


jaggedArray[1] = new int[4];
jaggedArray[2] = new int[2];

Ognuno degli elementi è costituito da una matrice unidimensionale di Integer. Il primo elemento è una matrice
di 5 Integer, il secondo una matrice di 4 Integer e il terzo una matrice di 2 Integer.
È possibile usare gli inizializzatori anche per immettere i valori negli elementi delle matrici. In questo caso, non
occorre conoscere le dimensioni delle matrici. Ad esempio:

jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };


jaggedArray[1] = new int[] { 0, 2, 4, 6 };
jaggedArray[2] = new int[] { 11, 22 };

È inoltre possibile inizializzare la matrice al momento della dichiarazione, come nell'esempio seguente:

int[][] jaggedArray2 = new int[][]


{
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

È possibile usare la forma abbreviata seguente. Non è possibile omettere l'operatore new poiché non è prevista
alcuna inizializzazione predefinita per gli elementi:

int[][] jaggedArray3 =
{
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

Una matrice irregolare è una matrice di matrici, quindi i relativi elementi sono tipi di riferimento inizializzati su
null .
È possibile accedere ai singoli elementi di una matrice, come negli esempi seguenti:

// Assign 77 to the second element ([1]) of the first array ([0]):


jaggedArray3[0][1] = 77;

// Assign 88 to the second element ([1]) of the third array ([2]):


jaggedArray3[2][1] = 88;

È possibile combinare matrici frastagliate e multidimensionali. Di seguito sono riportate la dichiarazione e


l'inizializzazione di una matrice di matrici unidimensionale che contiene tre elementi matrice bidimensionali con
dimensioni diverse. Per ulteriori informazioni, vedere matrici multidimensionali.

int[][,] jaggedArray4 = new int[3][,]


{
new int[,] { {1,3}, {5,7} },
new int[,] { {0,2}, {4,6}, {8,10} },
new int[,] { {11,22}, {99,88}, {0,9} }
};

È possibile accedere a singoli elementi, come illustrato in questo esempio, in cui viene visualizzato il valore
dell'elemento [1,0] della prima matrice (valore 5 ):

System.Console.Write("{0}", jaggedArray4[0][1, 0]);

Il metodo Length restituisce il numero di matrici contenute nella matrice di matrici. Si supponga, ad esempio,
che sia stata dichiarata la matrice precedente. In questo caso, la riga

System.Console.WriteLine(jaggedArray4.Length);

restituisce il valore 3.

Esempio
In questo esempio viene compilata una matrice i cui elementi sono costituiti da matrici. Ogni elemento della
matrice ha una dimensione diversa.
class ArrayTest
{
static void Main()
{
// Declare the array of two elements.
int[][] arr = new int[2][];

// Initialize the elements.


arr[0] = new int[5] { 1, 3, 5, 7, 9 };
arr[1] = new int[4] { 2, 4, 6, 8 };

// Display the array elements.


for (int i = 0; i < arr.Length; i++)
{
System.Console.Write("Element({0}): ", i);

for (int j = 0; j < arr[i].Length; j++)


{
System.Console.Write("{0}{1}", arr[i][j], j == (arr[i].Length - 1) ? "" : " ");
}
System.Console.WriteLine();
}
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
Element(0): 1 3 5 7 9
Element(1): 2 4 6 8
*/

Vedere anche
Array
Guida per programmatori C#
Matrici
Matrici unidimensionali
Matrici multidimensionali
Utilizzo di foreach con matrici (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione foreach offre un metodo semplice e diretto per scorrere gli elementi di una matrice.
Per le matrici unidimensionali, l'istruzione foreach elabora gli elementi in ordine di indice crescente, a partire
dall'indice 0 e terminando con l'indice Length - 1 :

int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };


foreach (int i in numbers)
{
System.Console.Write("{0} ", i);
}
// Output: 4 5 6 1 2 3 -2 -1 0

Per le matrici multidimensionali, l'attraversamento degli elementi avviene in modo da incrementare per primi gli
indici della dimensione all'estrema destra, per poi proseguire con la dimensione successiva a sinistra e così via
verso sinistra:

int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } };


// Or use the short form:
// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };

foreach (int i in numbers2D)


{
System.Console.Write("{0} ", i);
}
// Output: 9 99 3 33 5 55

Con le matrici multidimensionali, tuttavia, l'uso di un ciclo for annidato fornisce maggiore controllo sull'ordine
di elaborazione degli elementi della matrice.

Vedere anche
Array
Guida per programmatori C#
Matrici
Matrici unidimensionali
Matrici multidimensionali
Matrici irregolari
Passaggio di matrici come argomenti (Guida per
programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Le matrici possono essere passate come argomenti ai parametri di metodo. Le matrici, infatti, sono tipi di
parametri e il metodo può quindi modificare il valore degli elementi.

Passaggio di matrici unidimensionali come argomenti


È possibile passare una matrice unidimensionale inizializzata a un metodo. L'istruzione seguente, ad esempio,
invia una matrice a un metodo di stampa.

int[] theArray = { 1, 3, 5, 7, 9 };
PrintArray(theArray);

Nel codice seguente viene illustrata un'implementazione parziale del metodo di stampa.

void PrintArray(int[] arr)


{
// Method code.
}

È possibile inizializzare e passare una nuova matrice in un passaggio, come mostrato nell'esempio seguente.

PrintArray(new int[] { 1, 3, 5, 7, 9 });

Esempio
Nell'esempio seguente, una matrice di stringhe viene inizializzata e passata come argomento a un metodo
DisplayArray per le stringhe. Nel metodo vengono visualizzati gli elementi della matrice. Successivamente, il
metodo ChangeArray inverte gli elementi della matrice e quindi il metodo ChangeArrayElements consente di
modificare i primi tre elementi della matrice. Al termine delle restituzioni di ogni metodo, il metodo
DisplayArray mostra che il passaggio di una matrice per valore non impedisce le modifiche agli elementi della
matrice.
using System;

class ArrayExample
{
static void DisplayArray(string[] arr) => Console.WriteLine(string.Join(" ", arr));

// Change the array by reversing its elements.


static void ChangeArray(string[] arr) => Array.Reverse(arr);

static void ChangeArrayElements(string[] arr)


{
// Change the value of the first three array elements.
arr[0] = "Mon";
arr[1] = "Wed";
arr[2] = "Fri";
}

static void Main()


{
// Declare and initialize an array.
string[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
// Display the array elements.
DisplayArray(weekDays);
Console.WriteLine();

// Reverse the array.


ChangeArray(weekDays);
// Display the array again to verify that it stays reversed.
Console.WriteLine("Array weekDays after the call to ChangeArray:");
DisplayArray(weekDays);
Console.WriteLine();

// Assign new values to individual array elements.


ChangeArrayElements(weekDays);
// Display the array again to verify that it has changed.
Console.WriteLine("Array weekDays after the call to ChangeArrayElements:");
DisplayArray(weekDays);
}
}
// The example displays the following output:
// Sun Mon Tue Wed Thu Fri Sat
//
// Array weekDays after the call to ChangeArray:
// Sat Fri Thu Wed Tue Mon Sun
//
// Array weekDays after the call to ChangeArrayElements:
// Mon Wed Fri Wed Tue Mon Sun

Passaggio di matrici multidimensionali come argomenti


Una matrice multidimensionale inizializzata viene passata a un metodo nello stesso modo in cui viene passata
una matrice unidimensionale.

int[,] theArray = { { 1, 2 }, { 2, 3 }, { 3, 4 } };
Print2DArray(theArray);

Nel codice seguente viene illustrata una dichiarazione parziale di un metodo di stampa che accetta una matrice
bidimensionale come argomento.
void Print2DArray(int[,] arr)
{
// Method code.
}

È possibile inizializzare e passare una nuova matrice in un passaggio, come mostrato nell'esempio seguente:

Print2DArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } });

Esempio
Nell'esempio seguente, una matrice bidimensionale di Integer viene inizializzata e passata al metodo
Print2DArray . Nel metodo vengono visualizzati gli elementi della matrice.

class ArrayClass2D
{
static void Print2DArray(int[,] arr)
{
// Display the array elements.
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
System.Console.WriteLine("Element({0},{1})={2}", i, j, arr[i, j]);
}
}
}
static void Main()
{
// Pass the array as an argument.
Print2DArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } });

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
Element(0,0)=1
Element(0,1)=2
Element(1,0)=3
Element(1,1)=4
Element(2,0)=5
Element(2,1)=6
Element(3,0)=7
Element(3,1)=8
*/

Vedere anche
Guida per programmatori C#
Matrici
Matrici unidimensionali
Matrici multidimensionali
Matrici irregolari
Matrici tipizzate in modo implicito (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile creare una matrice tipizzata in modo implicito in cui il tipo dell'istanza della matrice viene derivato
tramite inferenza dagli elementi specificati nell'inizializzatore di matrice. Le regole per qualsiasi variabile
tipizzata in modo implicito si applicano anche alle matrici tipizzate in modo implicito. Per altre informazioni,
vedere Variabili locali tipizzate in modo implicito.
Le matrici tipizzate in modo implicito vengono in genere usate nelle espressioni di query insieme ai tipi anonimi
e agli inizializzatori di oggetto e di raccolta.
Gli esempi seguenti illustrano come creare una matrice tipizzata in modo implicito:

class ImplicitlyTypedArraySample
{
static void Main()
{
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { "hello", null, "world" }; // string[]

// single-dimension jagged array


var c = new[]
{
new[]{1,2,3,4},
new[]{5,6,7,8}
};

// jagged array of strings


var d = new[]
{
new[]{"Luca", "Mads", "Luke", "Dinesh"},
new[]{"Karen", "Suma", "Frances"}
};
}
}

Nell'esempio precedente si noti che con le matrici tipizzate in modo implicito, non vengono usate parentesi
quadre sul lato sinistro dell'istruzione di inizializzazione. Si noti anche che le matrici di matrici vengono
inizializzate usando new [] esattamente come le matrici unidimensionali.

Matrici tipizzate in modo implicito negli inizializzatori di oggetto


Quando si crea un tipo anonimo che contiene una matrice, la matrice deve essere tipizzata in modo implicito
nell'inizializzatore di oggetto del tipo. Nell'esempio seguente contacts è una matrice tipizzata in modo
implicito di tipi anonimi, ognuno dei quali contiene una matrice denominata PhoneNumbers . Si noti che la parola
chiave var non viene usata negli inizializzatori di oggetto.
var contacts = new[]
{
new {
Name = " Eugene Zabokritski",
PhoneNumbers = new[] { "206-555-0108", "425-555-0001" }
},
new {
Name = " Hanying Feng",
PhoneNumbers = new[] { "650-555-0199" }
}
};

Vedere anche
Guida per programmatori C#
Variabili locali tipizzate in modo implicito
Matrici
Tipi anonimi
Inizializzatori di oggetto e di raccolta
var
LINQ in C#
Stringhe (Guida per programmatori C#)
02/11/2020 • 21 minutes to read • Edit Online

Una stringa è un oggetto di tipo String il cui valore è testo. Internamente il testo viene archiviato come una
raccolta di sola lettura sequenziale di oggetti Char. Le stringhe C# non presentano un carattere di terminazione
null alla fine, pertanto una stringa C# può contenere qualsiasi numero di caratteri null incorporati ('\0'). La
proprietà Length di una stringa rappresenta il numero di oggetti Char in essa contenuti e non il numero di
caratteri Unicode. Per accedere ai singoli punti di codice Unicode in una stringa usare l'oggetto StringInfo.

stringa e System. String


In Visual Basic la parola chiave string è un alias per String. String e string sono pertanto equivalenti ed è
possibile usare la convenzione di denominazione che si preferisce. La classe String fornisce molti metodi per
creare, modificare e confrontare stringhe in modo sicuro. Inoltre, il linguaggio C# esegue l'overload di alcuni
operatori per semplificare le operazioni comuni sulle stringhe. Per altre informazioni sull'uso della parola chiave,
vedere string. Per altre informazioni sul tipo e sui relativi metodi, vedere String.

Dichiarazione e inizializzazione di stringhe


È possibile dichiarare e inizializzare stringhe in vari modi, come mostrato nell'esempio seguente:

// Declare without initializing.


string message1;

// Initialize to null.
string message2 = null;

// Initialize as an empty string.


// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;

// Initialize with a regular string literal.


string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.


string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

// Use System.String if you prefer.


System.String greeting = "Hello World!";

// In local variables (i.e. within a method body)


// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";

// Use a const string to prevent 'message4' from


// being used to store another string value.
const string message4 = "You can't get rid of me!";

// Use the String constructor only when creating


// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);

Si noti che l'operatore new non viene usato per creare un oggetto stringa tranne nel caso in cui la stringa viene
inizializzata con una matrice di caratteri.
Inizializzare una stringa con il valore costante Empty per creare un nuovo oggetto String con stringa di
lunghezza zero. La rappresentazione del valore letterale stringa di una stringa di lunghezza zero è "".
L'inizializzazione di stringhe con il valore Empty anziché con null riduce le probabilità di un errore
NullReferenceException. Usare il metodo statico IsNullOrEmpty(String) per verificare il valore di una stringa
prima di provare ad accedere alla stringa.

Immutabilità degli oggetti stringa


Gli oggetti stringa sono immutabili, ovvero non possono essere modificati una volta creati. Tutti i metodi String e
gli operatori C# che sembrano modificare una stringa in realtà restituiscono i risultati in un nuovo oggetto
stringa. Nell'esempio seguente, quando il contenuto di s1 e s2 viene concatenato per formare un'unica
stringa, le due stringhe originali restano immutate. L'operatore += crea una nuova stringa che contiene il
contenuto delle due stringhe combinato. Il nuovo oggetto viene assegnato alla variabile s1 e l'oggetto
originale assegnato a s1 viene rilasciato per l'operazione di Garbage Collection perché nessun'altra variabile
contiene un riferimento a tale oggetto.

string s1 = "A string is more ";


string s2 = "than the sum of its chars.";

// Concatenate s1 and s2. This actually creates a new


// string object and stores it in s1, releasing the
// reference to the original object.
s1 += s2;

System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.

Dato che una "modifica" della stringa è in effetti la creazione di una nuova stringa, è necessario prestare
attenzione quando si creano riferimenti alle stringhe. Se si crea un riferimento a una stringa e quindi si
"modifica" la stringa originale, il riferimento continuerà a puntare all'oggetto originale anziché al nuovo oggetto
creato quando la stringa è stata modificata. Il codice seguente illustra questo comportamento:

string s1 = "Hello ";


string s2 = s1;
s1 += "World";

System.Console.WriteLine(s2);
//Output: Hello

Per ulteriori informazioni su come creare nuove stringhe basate su modifiche come le operazioni di ricerca e
sostituzione sulla stringa originale, vedere come modificare il contenuto di una stringa.

Valori letterali stringa normali e verbatim


Usare valori letterali stringa normali quando è necessario incorporare caratteri di escape forniti da C#, come
mostrato nell'esempio seguente:
string columns = "Column 1\tColumn 2\tColumn 3";
//Output: Column 1 Column 2 Column 3

string rows = "Row 1\r\nRow 2\r\nRow 3";


/* Output:
Row 1
Row 2
Row 3
*/

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";


//Output: "The Æolean Harp", by Samuel Taylor Coleridge

Usare stringhe verbatim per praticità e migliore leggibilità quando il testo della stringa contiene barre
rovesciate, ad esempio nei percorsi di file. Dato che le stringhe verbatim mantengono i caratteri di nuova riga
come parte del testo della stringa, possono essere usate per inizializzare stringhe su più righe. Usare virgolette
doppie per incorporare una virgoletta in una stringa verbatim. L'esempio seguente mostra alcuni usi comuni
delle stringhe verbatim:

string filePath = @"C:\Users\scoleridge\Documents\";


//Output: C:\Users\scoleridge\Documents\

string text = @"My pensive SARA ! thy soft cheek reclined


Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...";
/* Output:
My pensive SARA ! thy soft cheek reclined
Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...
*/

string quote = @"Her name was ""Sara.""";


//Output: Her name was "Sara."

Sequenze di escape delle stringhe


SEQ UEN Z A DI ESC A P E N O M E C A RAT T ERE C O DIF IC A UN IC O DE

\' Virgoletta singola 0x0027

\" Virgoletta doppia 0x0022

\\ Barra rovesciata 0x005C

\0 Null 0x0000

\a Avviso 0x0007

\b Backspace 0x0008

\f Avanzamento carta 0x000C

\n Nuova riga 0x000A

\r Ritorno a capo 0x000D


SEQ UEN Z A DI ESC A P E N O M E C A RAT T ERE C O DIF IC A UN IC O DE

\t Tabulazione orizzontale 0x0009

\v Tabulazione verticale 0x000B

\u Sequenza di escape Unicode (UTF-16) \uHHHH (intervallo: 0000-FFFF;


esempio: \u00E7 = "ç")

\U Sequenza di escape Unicode (UTF-32) \U00HHHHHH (intervallo: 000000-


10FFFF; esempio: \U0001F47D = "& #
x1F47D;")

\x Sequenza di escape Unicode simile a \xH[H][H][H] (intervallo: 0-FFFF;


"\u", ma con lunghezza variabile esempio: \x00E7 o \x0E7 o \xE7
= "ç")

WARNING
Quando si usa la sequenza di escape \x e si specificano meno di 4 cifre esadecimali, se i caratteri immediatamente
seguenti la sequenza di escape sono cifre esadecimali valide (ad esempio 0-9, A-F e a-f), questi verranno interpretati come
parte della sequenza di escape. Ad esempio, \xA1 produce "¡" che è il punto di codice U+00A1. Se tuttavia il carattere
successivo è "A" oppure "a", la sequenza di escape verrà invece interpretata come \xA1A e produrrà " " che è il punto di
codice U+0A1A. In questi casi, specificando tutte e 4 le cifre esadecimali (ad esempio, \x00A1 ) si eviteranno possibili
interpretazioni errate.

NOTE
In fase di compilazione, le stringhe verbatim vengono convertite in stringhe normali con tutte le stesse sequenze di
escape. Pertanto, se si visualizza una stringa verbatim nella finestra Espressioni di controllo del debugger, si vedranno i
caratteri di escape aggiunti dal compilatore e non la versione verbatim del codice sorgente. Ad esempio, la stringa
verbatim @"C:\files.txt" verrà visualizzata nella finestra delle espressioni di controllo come "C \\files.txt".

Stringhe di formato
Una stringa di formato è una stringa il cui contenuto viene determinato dinamicamente in fase di esecuzione. Le
stringhe di formato vengono create incorporando segnaposto o espressioni interpolate all'interno di parentesi
graffe in una stringa. Tutti gli elementi all'interno delle parentesi graffe ( {...} ) restituiranno un valore e
verranno visualizzati come stringa formattata in fase di esecuzione. Esistono due metodi per creare stringhe di
formato: interpolazione di stringhe e formattazione composita.
Interpolazione di stringhe
Le stringhe interpolate, disponibili in C# 6.0 e versioni successive, vengono identificate dal carattere speciale $
e includono le espressioni interpolate tra parentesi graffe. Se non si ha familiarità con l'interpolazione di
stringhe, vedere l'esercitazione interattiva Interpolazione di stringhe - C# per una veloce panoramica.
Usare l'interpolazione di stringhe per migliorare la leggibilità e la gestibilità del codice. L'interpolazione di
stringhe permette di ottenere gli stessi risultati del metodo String.Format , ma è più facile da usare e migliora la
chiarezza inline.
var jh = (firstName: "Jupiter", lastName: "Hammon", born: 1711, published: 1761);
Console.WriteLine($"{jh.firstName} {jh.lastName} was an African American poet born in {jh.born}.");
Console.WriteLine($"He was first published in {jh.published} at the age of {jh.published - jh.born}.");
Console.WriteLine($"He'd be over {Math.Round((2018d - jh.born) / 100d) * 100d} years old today.");

// Output:
// Jupiter Hammon was an African American poet born in 1711.
// He was first published in 1761 at the age of 50.
// He'd be over 300 years old today.

Formattazione composita
String.Format utilizza segnaposto tra parentesi graffe per creare una stringa di formato. Questo esempio
restituisce un output simile a quello del metodo di interpolazione di stringhe usato sopra.

var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);


Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);
Console.WriteLine("She was first published in {0} at the age of {1}.", pw.published, pw.published -
pw.born);
Console.WriteLine("She'd be over {0} years old today.", Math.Round((2018d - pw.born) / 100d) * 100d);

// Output:
// Phillis Wheatley was an African American poet born in 1753.
// She was first published in 1773 at the age of 20.
// She'd be over 300 years old today.

Per altre informazioni sulla formattazione dei tipi .NET, vedere Formattazione di tipi in .NET.

Sottostringhe
Una sottostringa è qualsiasi sequenza di caratteri contenuta in una stringa. Usare il metodo Substring per creare
una nuova stringa da una parte della stringa originale. È possibile cercare una o più occorrenze di una
sottostringa tramite il metodo IndexOf. Usare il metodo Replace per sostituire tutte le occorrenze di una
sottostringa specificata con una nuova stringa. Come il metodo Substring, anche Replace restituisce una nuova
stringa e non modifica la stringa originale. Per ulteriori informazioni, vedere come eseguire la ricerca di stringhe
e come modificare il contenutodi una stringa.

string s3 = "Visual C# Express";


System.Console.WriteLine(s3.Substring(7, 2));
// Output: "C#"

System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"

// Index values are zero-based


int index = s3.IndexOf("C");
// index = 7

Accesso a caratteri singoli


È possibile utilizzare la notazione di matrice con un valore di indice per ottenere l'accesso in sola lettura a singoli
caratteri, come nell'esempio seguente:
string s5 = "Printing backwards";

for (int i = 0; i < s5.Length; i++)


{
System.Console.Write(s5[s5.Length - i - 1]);
}
// Output: "sdrawkcab gnitnirP"

Se i metodi String non specificano la funzionalità richiesta per modificare singoli caratteri in una stringa, è
possibile usare un oggetto StringBuilder per modificare i singoli caratteri "sul posto", quindi creare una nuova
stringa per archiviare i risultati tramite i metodi StringBuilder. Nell'esempio seguente presupporre che sia
necessario modificare la stringa originale in un modo specifico e archiviare quindi i risultati per uso futuro:

string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);

for (int j = 0; j < sb.Length; j++)


{
if (System.Char.IsLower(sb[j]) == true)
sb[j] = System.Char.ToUpper(sb[j]);
else if (System.Char.IsUpper(sb[j]) == true)
sb[j] = System.Char.ToLower(sb[j]);
}
// Store the new string.
string corrected = sb.ToString();
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?

Stringhe null e stringhe vuote


Una stringa vuota è un'istanza di un oggetto System.String che contiene zero caratteri. Le stringhe vuote sono
utilizzate di frequente in diversi scenari di programmazione per rappresentare un campo di testo vuoto. È
possibile chiamare metodi su stringhe vuote, perché sono oggetti System.String validi. Le stringhe vuote
vengono inizializzate come indicato di seguito:

string s = String.Empty;

Al contrario una stringa null non fa riferimento a un'istanza di un oggetto System.String e qualsiasi chiamata di
un metodo su una stringa null causa un'eccezione NullReferenceException. È tuttavia possibile usare stringhe
null nelle operazioni di concatenazione e confronto con altre stringhe. Gli esempi seguenti illustrano alcuni casi
in cui un riferimento a una stringa null causa o meno la generazione di un'eccezione:
static void Main()
{
string str = "hello";
string nullStr = null;
string emptyStr = String.Empty;

string tempStr = str + nullStr;


// Output of the following line: hello
Console.WriteLine(tempStr);

bool b = (emptyStr == nullStr);


// Output of the following line: False
Console.WriteLine(b);

// The following line creates a new empty string.


string newStr = emptyStr + nullStr;

// Null strings and empty strings behave differently. The following


// two lines display 0.
Console.WriteLine(emptyStr.Length);
Console.WriteLine(newStr.Length);
// The following line raises a NullReferenceException.
//Console.WriteLine(nullStr.Length);

// The null character can be displayed and counted, like other chars.
string s1 = "\x0" + "abc";
string s2 = "abc" + "\x0";
// Output of the following line: * abc*
Console.WriteLine("*" + s1 + "*");
// Output of the following line: *abc *
Console.WriteLine("*" + s2 + "*");
// Output of the following line: 4
Console.WriteLine(s2.Length);
}

Uso di StringBuilder per la creazione veloce di stringhe


Le operazioni sulle stringhe in .NET sono altamente ottimizzate e nella maggior parte dei casi non influiscono
sulle prestazioni in modo significativo. Tuttavia, in alcuni scenari, ad esempio cicli rigidi eseguiti molte centinaia
o migliaia di volte, le operazioni sulle stringhe possono incidere sulle prestazioni. La classe StringBuilder crea un
buffer di stringhe che offre prestazioni migliori se il programma esegue numerose modifiche di stringhe. La
stringa StringBuilder consente anche di riassegnare singoli caratteri, un'operazione non supportata dal tipo di
dati string incorporato. Questo codice, ad esempio, consente di modificare il contenuto di una stringa senza
crearne una nuova:

System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");


sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
System.Console.ReadLine();

//Outputs Cat: the ideal pet

In questo esempio viene usato un oggetto StringBuilder per creare una stringa da un set di tipi numerici:
using System;
using System.Text;

namespace CSRefStrings
{
class TestStringBuilder
{
static void Main()
{
var sb = new StringBuilder();

// Create a string composed of numbers 0 - 9


for (int i = 0; i < 10; i++)
{
sb.Append(i.ToString());
}
Console.WriteLine(sb); // displays 0123456789

// Copy one character of the string (not possible with a System.String)


sb[0] = sb[9];

Console.WriteLine(sb); // displays 9123456789


Console.WriteLine();
}
}
}

Stringhe, metodi di estensione e LINQ


Poiché il tipo String implementa IEnumerable<T> è possibile usare i metodi di estensione definiti nella classe
Enumerable sulle stringhe. Per evitare confusioni a livello visivo questi metodi sono esclusi da IntelliSense per il
tipo String, ma sono comunque disponibili. È anche possibile usare espressioni di query LINQ sulle stringhe. Per
altre informazioni, vedere LINQ e stringhe.

Argomenti correlati
A RGO M EN TO DESC RIZ IO N E

Come modificare il contenuto delle stringhe Illustra le tecniche per trasformare le stringhe e modificare il
contenuto di queste.

Come confrontare le stringhe Illustra come eseguire confronti di stringhe tra ordinali e
impostazioni cultura.

Come concatenare più stringhe Illustra vari modi per unire più stringhe in una.

Come analizzare le stringhe con String. Split Esempi di codice che illustrano come usare il metodo
String.Split per analizzare le stringhe.

Come cercare stringhe Spiega come eseguire la ricerca di testo specifico o di motivi
nelle stringhe.

Come determinare se una stringa rappresenta un valore Viene illustrato come analizzare in modo sicuro una stringa
numerico per verificare se ha un valore numerico valido.

Interpolazione di stringhe Descrive la funzionalità di interpolazione di stringhe che offre


una sintassi efficiente per formattare le stringhe.
A RGO M EN TO DESC RIZ IO N E

Operazioni di base sulle stringhe Fornisce collegamenti ad argomenti che usano metodi
System.String e System.Text.StringBuilder per eseguire
operazioni di base sulle stringhe.

Analisi di stringhe Descrive come convertire le rappresentazioni stringa dei tipi


di base .NET in istanze dei tipi corrispondenti.

Analisi di stringhe di data e ora in .NET Illustra come convertire una stringa come "24/01/2008" in
un oggetto System.DateTime.

Confronto di stringhe Informazioni su come confrontare le stringhe ed esempi in


C# e Visual Basic.

Uso della classe StringBuilder Descrive come creare e modificare oggetti stringa dinamici
tramite la classe StringBuilder.

LINQ e stringhe Informazioni su come eseguire varie operazioni sulle stringhe


tramite query LINQ.

Guida per programmatori C# Collegamenti ad argomenti che spiegano i costrutti di


programmazione in C#.
Come determinare se una stringa rappresenta un
valore numerico (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Per determinare se una stringa è una rappresentazione valida di un tipo numerico specificato, usare il metodo
statico TryParse che viene implementato da tutti i tipi numerici primitivi e anche da tipi quali DateTime e
IPAddress. L'esempio seguente illustra come determinare se "108" è un tipo int valido.

int i = 0;
string s = "108";
bool result = int.TryParse(s, out i); //i now = 108

Se la stringa contiene caratteri non numerici o se il valore numerico è o troppo grande o troppo piccolo per un
determinato tipo specificato, TryParse restituisce false e imposta il parametro out su zero. In caso contrario,
restituisce true e imposta il parametro out sul valore numerico della stringa.

NOTE
È possibile che una stringa contenga solo caratteri numeri e che non sia tuttavia valida per il tipo per il quale si usa il
metodo TryParse . Ad esempio, "256" non è un valore valido per byte , ma lo è per int . "98,6" non è un valore valido
per int ma è un valore decimal valido.

Esempio
Gli esempi seguenti illustrano come usare TryParse con rappresentazioni di stringa di valori long , byte e
decimal .

string numString = "1287543"; //"1287543.0" will return false for a long


long number1 = 0;
bool canConvert = long.TryParse(numString, out number1);
if (canConvert == true)
Console.WriteLine("number1 now = {0}", number1);
else
Console.WriteLine("numString is not a valid long");

byte number2 = 0;
numString = "255"; // A value of 256 will return false
canConvert = byte.TryParse(numString, out number2);
if (canConvert == true)
Console.WriteLine("number2 now = {0}", number2);
else
Console.WriteLine("numString is not a valid byte");

decimal number3 = 0;
numString = "27.3"; //"27" is also a valid decimal
canConvert = decimal.TryParse(numString, out number3);
if (canConvert == true)
Console.WriteLine("number3 now = {0}", number3);
else
Console.WriteLine("number3 is not a valid decimal");
Programmazione efficiente
I tipi numerici primitivi implementano anche il metodo statico Parse , che genera un'eccezione se la stringa non
è un numero valido. Il metodo TryParse è in genere più efficiente in quanto restituisce false se il numero non è
valido.

Sicurezza .NET
Usare sempre i metodi TryParse o Parse per convalidare l'input dell'utente da controlli, come caselle di testo e
caselle combinate.

Vedere anche
Come convertire una matrice di byte in un Integer
Come convertire una stringa in un numero
Come eseguire la conversione tra stringhe esadecimali e tipi numerici
Analisi di stringhe numeriche
Formattazione di tipi
Indicizzatori (Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Gli indicizzatori consentono di indicizzare le istanze di una classe o struct esattamente come le matrici. Il valore
indicizzato può essere impostato o recuperato senza specificare in modo esplicito un membro di istanza o tipo.
Gli indicizzatori sono analoghi alle proprietà, con la differenza che le relative funzioni di accesso accettano i
parametri.
Nell'esempio seguente viene definita una classe generica con i semplici metodi delle funzioni di accesso get e
set per assegnare e recuperare i valori. La classe Program crea un'istanza di questa classe per archiviare le
stringhe.

using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];

// Define the indexer to allow client code to use [] notation.


public T this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.

NOTE
Per altri esempi, vedere Sezioni correlate.

Definizioni del corpo dell'espressione


È normale che un indicizzatore ottenga o imposti una funzione di accesso in modo che sia costituita da una
singola istruzione che restituisce o imposta un valore. I membri con corpo di espressione offrono una sintassi
semplificata per supportare questo scenario. A partire da C# 6 è possibile implementare un indicizzatore di sola
lettura come membro con corpo di espressione, come mostrato nell'esempio seguente.
using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
int nextIndex = 0;

// Define the indexer to allow client code to use [] notation.


public T this[int i] => arr[i];

public void Add(T value)


{
if (nextIndex >= arr.Length)
throw new IndexOutOfRangeException($"The collection can hold only {arr.Length} elements.");
arr[nextIndex++] = value;
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection.Add("Hello, World");
System.Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.

Si noti che => introduce il corpo dell'espressione e che la parola chiave get non è usata.
A partire da C# 7.0, le funzioni di accesso get e set possono essere implementate entrambe come membri con
corpo di espressione. In questo caso, è necessario usare entrambe le parole chiave get e set . Ad esempio:

using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];

// Define the indexer to allow client code to use [] notation.


public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World.";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.
Panoramica sugli indicizzatori
Gli indicizzatori consentono di indicizzare gli oggetti in modo simile alle matrici.
Una funzione di accesso get restituisce un valore. Una funzione di accesso set assegna un valore.
La parola chiave this viene usata per definire gli indicizzatori.
La parola chiave value viene usata per definire il valore che deve essere assegnato dalla funzione di
accesso set .
Non è necessario che gli indicizzatori vengano indicizzati da un valore Integer, perché la definizione del
meccanismo di ricerca specifico dipende dall'utente.
Gli indicizzatori possono essere sottoposti a overload.
Gli indicizzatori possono avere più di un parametro formale, ad esempio quando si accede a una matrice
bidimensionale.

Sezioni correlate
Utilizzo degli indicizzatori
Indicizzatori nelle interfacce
Confronto tra proprietà e indicizzatori
Limitazione dell'accessibilità delle funzioni di accesso

Specifiche del linguaggio C#


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

Vedi anche
Guida per programmatori C#
Proprietà
Uso di indicizzatori (Guida per programmatori C#)
02/11/2020 • 10 minutes to read • Edit Online

Gli indicizzatori sono una praticità sintattica che consente di creare una classe, uno structo un' interfaccia a cui le
applicazioni client possono accedere come matrice. Il compilatore genererà una Item Proprietà (o una proprietà
denominata in alternativa se IndexerNameAttribute è presente) e i metodi della funzione di accesso appropriati.
Gli indicizzatori sono in genere implementati in tipi il cui scopo principale è incapsulare una raccolta o una
matrice interna. Si supponga, ad esempio, di disporre di una classe TempRecord che rappresenta la temperatura
in gradi Fahrenheit registrata in 10 ore diverse durante un periodo di 24 ore. La classe contiene una temps
matrice di tipo float[] in cui archiviare i valori di temperatura. Implementando un indicizzatore in questa
classe, i client possono accedere alle temperature in un'istanza TempRecord come float temp = tempRecord[4]
invece che come float temp = tempRecord.temps[4] . La notazione dell'indicizzatore non solo semplifica la
sintassi per le applicazioni client. rende inoltre la classe e lo scopo più intuitivo per altri sviluppatori.
Per dichiarare un indicizzatore in una classe o uno struct, usare la parola chiave this, come nell'esempio
seguente:

// Indexer declaration
public int this[int index]
{
// get and set accessors
}

IMPORTANT
La dichiarazione di un indicizzatore genererà automaticamente una proprietà denominata Item per l'oggetto. La Item
proprietà non è direttamente accessibile dall'espressione di accesso ai membridell'istanza. Inoltre, se si aggiunge una
Item proprietà personalizzata a un oggetto con un indicizzatore, si otterrà un errore del compilatore CS0102. Per evitare
questo errore, usare IndexerNameAttribute Rinomina l'indicizzatore come descritto di seguito.

Commenti
Il tipo di un indicizzatore e dei relativi parametri deve essere accessibile almeno quanto l'indicizzatore. Per altre
informazioni sui livelli di accessibilità, vedere Modificatori di accesso.
Per altre informazioni sull'uso degli indicizzatori con un'interfaccia, vedere Indicizzatori nelle interfacce.
La firma di un indicizzatore è costituita dal numero e dai tipi dei relativi parametri formali. Non include il tipo di
indicizzatore o i nomi dei parametri formali. Se si dichiarano più indicizzatori nella stessa classe, gli indicizzatori
devono avere firme diverse.
Il valore di un indicizzatore non è classificato come una variabile, pertanto non è possibile passare il valore di un
indicizzatore come un parametro ref o out.
Per fornire all'indicizzatore un nome che possa essere usato da altri linguaggi, usare
System.Runtime.CompilerServices.IndexerNameAttribute, come nell'esempio seguente:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}

Questo indicizzatore avrà il nome TheItem , perché viene sottoposto a override dall'attributo del nome
dell'indicizzatore. Per impostazione predefinita, il nome dell'indicizzatore è Item .

Esempio 1
Nell'esempio seguente viene illustrato come dichiarare un campo di matrice privata, temps , e un indicizzatore.
L'indicizzatore consente l'accesso diretto all'istanza tempRecord[i] . In alternativa all'uso dell'indicizzatore, è
possibile dichiarare la matrice come un membro public e accedere direttamente ai relativi membri,
tempRecord.temps[i] .

public class TempRecord


{
// Array of temperature values
float[] temps = new float[10]
{
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
};

// To enable client code to validate input


// when accessing your indexer.
public int Length => temps.Length;

// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}

Si noti che quando viene valutato l'accesso di un indicizzatore, ad esempio in un'istruzione Console.Write , viene
richiamata la funzione di accesso get. Pertanto, se non esiste alcuna funzione di accesso get , si verifica un
errore in fase di compilazione.
using System;

class Program
{
static void Main()
{
var tempRecord = new TempRecord();

// Use the indexer's set accessor


tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;

// Use the indexer's get accessor


for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
/* Output:
Element #0 = 56.2
Element #1 = 56.7
Element #2 = 56.5
Element #3 = 58.3
Element #4 = 58.8
Element #5 = 60.1
Element #6 = 65.9
Element #7 = 62.1
Element #8 = 59.2
Element #9 = 57.5
*/
}

Indicizzazione tramite altri valori


C# non limita il tipo del parametro dell'indicizzatore a Integer. Può ad esempio essere utile usare una stringa con
un indicizzatore. Un indicizzatore di questo tipo potrebbe essere implementato eseguendo la ricerca della
stringa nella raccolta e restituendo il valore appropriato. Poiché è possibile eseguire l'overload delle funzioni di
accesso, la stringa e le versioni integer possono coesistere.

Esempio 2
L'esempio seguente dichiara una classe che archivia i giorni della settimana. Una funzione di accesso get
accetta una stringa e il nome di un giorno e restituisce l'intero corrispondente. Ad esempio, "Domenica"
restituisce 0, "Lunedì" restituisce 1 e così via.
using System;

// Using a string as an indexer value


class DayCollection
{
string[] days = { "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat" };

// Indexer with only a get accessor with the expression-bodied definition:


public int this[string day] => FindDayIndex(day);

private int FindDayIndex(string day)


{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}

throw new ArgumentOutOfRangeException(


nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}

Utilizzo dell'esempio 2

using System;

class Program
{
static void Main(string[] args)
{
var week = new DayCollection();
Console.WriteLine(week["Fri"]);

try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
}
// Output:
// 5
// Not supported input: Day Made-up day is not supported.
// Day input must be in the form "Sun", "Mon", etc (Parameter 'day')
}

Esempio 3
Nell'esempio seguente viene dichiarata una classe che archivia i giorni della settimana usando l'
System.DayOfWeek enumerazione. Una get funzione di accesso accetta un oggetto DayOfWeek , il valore di un
giorno e restituisce l'intero corrispondente. Ad esempio, DayOfWeek.Sunday restituisce 0, DayOfWeek.Monday
restituisce 1 e così via.
using System;
using Day = System.DayOfWeek;

class DayOfWeekCollection
{
Day[] days =
{
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
};

// Indexer with only a get accessor with the expression-bodied definition:


public int this[Day day] => FindDayIndex(day);

private int FindDayIndex(Day day)


{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}

Utilizzo dell'esempio 3

using System;

class Program
{
static void Main()
{
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);

try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
}
// Output:
// 5
// Not supported input: Day 43 is not supported.
// Day input must be a defined System.DayOfWeek value. (Parameter 'day')
}

Programmazione efficiente
Esistono due modi principali per migliorare la sicurezza e l'affidabilità degli indicizzatori:
Assicurarsi di incorporare qualche tipo di strategia di gestione degli errori per gestire la possibilità che il
codice client passi un valore di indice non valido. Nel primo esempio riportato in questo argomento, la
classe TempRecord fornisce una proprietà Length che consente al codice client di verificare l'input prima
di passarlo all'indicizzatore. È anche possibile inserire il codice di gestione degli errori nell'indicizzatore
stesso. Assicurarsi di documentare per gli utenti qualsiasi eccezione generata all'interno di una funzione
di accesso dell'indicizzatore.
Impostare l'accessibilità delle funzioni di accesso get e set in modo che siano quanto più restrittive
possibile. Questo è particolarmente importante per la funzione di accesso set . Per altre informazioni,
vedere Limitazione dell'accessibilità delle funzioni di accesso.

Vedere anche
Guida per programmatori C#
Indicizzatori
Proprietà
Indicizzatori nelle interfacce (Guida per
programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Gli indicizzatori possono essere dichiarati su una interfaccia. Le funzioni di accesso degli indicizzatori di
interfaccia differiscono dalle funzioni di accesso degli indicizzatori di classe per gli aspetti seguenti:
Le funzioni di accesso di interfaccia non usano modificatori.
Una funzione di accesso di interfaccia non dispone in genere di un corpo.
Lo scopo della funzione di accesso è indicare se l'indicizzatore è di lettura/scrittura, di sola lettura o di sola
scrittura. È possibile fornire un'implementazione per un indicizzatore definito in un'interfaccia, ma si tratta di una
situazione rara. Gli indicizzatori in genere definiscono un'API per accedere ai campi dati e i campi dati non
possono essere definiti in un'interfaccia.
Nell'esempio seguente viene illustrata la funzione di accesso di un indicizzatore di interfaccia:

public interface ISomeInterface


{
//...

// Indexer declaration:
string this[int index]
{
get;
set;
}
}

È necessario che la firma di un indicizzatore sia diversa dalle firme di tutti gli altri indicizzatori dichiarati nella
stessa interfaccia.

Esempio
Nell'esempio seguente viene illustrato come implementare gli indicizzatori di interfaccia.
// Indexer on an interface:
public interface IIndexInterface
{
// Indexer declaration:
int this[int index]
{
get;
set;
}
}

// Implementing the interface.


class IndexerClass : IIndexInterface
{
private int[] arr = new int[100];
public int this[int index] // indexer declaration
{
// The arr object will throw IndexOutOfRange exception.
get => arr[index];
set => arr[index] = value;
}
}

IndexerClass test = new IndexerClass();


System.Random rand = new System.Random();
// Call the indexer to initialize its elements.
for (int i = 0; i < 10; i++)
{
test[i] = rand.Next();
}
for (int i = 0; i < 10; i++)
{
System.Console.WriteLine($"Element #{i} = {test[i]}");
}

/* Sample output:
Element #0 = 360877544
Element #1 = 327058047
Element #2 = 1913480832
Element #3 = 1519039937
Element #4 = 601472233
Element #5 = 323352310
Element #6 = 1422639981
Element #7 = 1797892494
Element #8 = 875761049
Element #9 = 393083859
*/

Nell'esempio precedente era possibile adottare l'implementazione esplicita del membro dell'interfaccia usando
il nome completo del membro dell'interfaccia. Ad esempio:

string IIndexInterface.this[int index]


{
}

Il nome completo, tuttavia, è necessario soltanto per evitare l'ambiguità quando la classe implementa più di
un'interfaccia con la stessa firma di indicizzatore. Se una classe Employee implementa, ad esempio, due
interfacce, ICitizen e IEmployee , ed entrambe le interfacce hanno la stessa firma di indicizzatore, sarà
necessaria l'implementazione esplicita del membro dell'interfaccia. In altre parole, la dichiarazione di
indicizzatore seguente:
string IEmployee.this[int index]
{
}

implementa l'indicizzatore nell'interfaccia IEmployee , mentre la dichiarazione seguente:

string ICitizen.this[int index]


{
}

implementa l'indicizzatore nell'interfaccia ICitizen .

Vedere anche
Guida per programmatori C#
Indicizzatori
Proprietà
Interfacce
Confronto tra proprietà e indicizzatori (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Gli indicizzatori sono come proprietà. Ad eccezione delle differenze illustrate nella tabella seguente, tutte le
regole definite per le funzioni di accesso a proprietà si applicano anche alle funzioni di accesso a indicizzatori.

P RO P RIETÀ IN DIC IZ Z ATO RE

Consente di chiamare metodi come se fossero membri dati Consente di accedere agli elementi di una raccolta interna di
pubblici. un oggetto tramite la notazione di matrice per l'oggetto
stesso.

Accesso tramite nome semplice. Accesso tramite indice.

Può essere un membro statico o un membro di istanza. Deve essere un membro di istanza.

La funzione di accesso get di una proprietà non ha La funzione di accesso get di un indicizzatore ha lo stesso
parametri. elenco di parametri formali dell'indicizzatore.

La funzione di accesso set di una proprietà contiene il La funzione di accesso set di un indicizzatore ha lo stesso
parametro value implicito. elenco di parametri formali dell'indicizzatore e anche il
parametro value.

Supporta la sintassi abbreviata con proprietà implementate Supporta membri con corpo di espressione per ottenere solo
automaticamente. indicizzatori.

Vedere anche
Guida per programmatori C#
Indicizzatori
Proprietà
Eventi (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Tramite gli eventi, una classe o un oggetto è in grado di segnalare ad altre classi o oggetti una situazione di
interesse. La classe che invia (o genera) l'evento viene chiamata server di pubblicazione e le classi che ricevono
(o gestiscono) l'evento sono chiamate sottoscrittori.
In un'applicazione C# Web o Windows Form tipica si sottoscrivono eventi generati da controlli quali pulsanti e
caselle di riepilogo. È possibile usare l'ambiente di sviluppo integrato (IDE) di Visual C# per cercare gli eventi
pubblicati da un controllo e selezionare quelli che si vuole gestire. L'IDE consente di aggiungere
automaticamente un metodo vuoto del gestore eventi e il codice per sottoscrivere l'evento. Per ulteriori
informazioni, vedere come sottoscrivere e annullare la sottoscrizione di eventi.

Cenni preliminari sugli eventi


Di seguito sono riportate le proprietà degli eventi:
L'autore determina quando viene generato un evento. I sottoscrittori determinano quale azione viene
eseguita in risposta all'evento.
Un evento può avere più sottoscrittori. Un sottoscrittore può gestire più eventi da più autori.
Gli eventi che non hanno sottoscrittore non vengono mai generati.
Gli eventi vengono in genere usati per segnalare azioni dell'utente, ad esempio la scelta di un pulsante o
di una voce di menu nell'interfaccia utente grafica.
Quando un evento ha più sottoscrittori, i gestori eventi vengono richiamati in modo sincrono al
momento della generazione dell'evento. Per richiamare gli eventi in modo asincrono, vedere Calling
Synchronous Methods Asynchronously.
Nella libreria di classi .NET gli eventi sono basati sul EventHandler delegato e sulla EventArgs classe di
base.

Sezioni correlate
Per altre informazioni, vedere:
Come eseguire e annullare la sottoscrizione a eventi
Come pubblicare eventi conformi alle linee guida di .NET
Come generare eventi di classe base nelle classi derivate
Come implementare eventi di interfaccia
Come implementare funzioni di accesso a eventi personalizzati

Specifiche del linguaggio C#


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

Capitoli del libro rappresentati


Delegates, Events, and Lambda Expressions (Delegati, eventi ed espressioni lambda) in C# 3.0 Cookbook, Third
Edition: More than 250 solutions for C# 3.0 programmers
Delegati ed eventi (Delegati, eventi ed espressioni lambda) in Learning C# 3.0: Master the fundamentals of C#
3.0

Vedere anche
EventHandler
Guida per programmatori C#
Delegati
Creazione di gestori eventi in Windows Form
Come sottoscrivere e annullare la sottoscrizione di
eventi (Guida per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

Si sottoscrive un evento pubblicato da un'altra classe quando si vuole scrivere codice personalizzato che viene
chiamato quando viene generato tale evento. È ad esempio possibile sottoscrivere l'evento click di un
pulsante perché l'applicazione esegua un'operazione utile quando l'utente fa clic sul pulsante in questione.
Per sottoscrivere gli eventi usando l'IDE di Visual Studio
1. Se la finestra Proprietà non viene visualizzata, nella visualizzazione Progettazione fare clic con il
pulsante destro del mouse sul modulo o sul controllo per cui si vuole creare un gestore eventi e
selezionare Proprietà .
2. Nella parte superiore della finestra Proprietà fare clic sull'icona Eventi .
3. Fare doppio clic sull'evento che si vuole creare, ad esempio sull'evento Load .
Visual C# crea un metodo del gestore eventi vuoto e lo aggiunge al codice. In alternativa, è possibile
aggiungere manualmente il codice nella visualizzazione Codice . Ad esempio, le righe di codice seguenti
dichiarano un metodo del gestore eventi che verrà chiamato quando la classe Form genera l'evento
Load .

private void Form1_Load(object sender, System.EventArgs e)


{
// Add your form load event handling code here.
}

La riga di codice necessaria per sottoscrivere l'evento viene generata automaticamente nel metodo
InitializeComponent nel file Form1.Designer.cs del progetto. La riga ha un aspetto simile a quanto
riportato di seguito:

this.Load += new System.EventHandler(this.Form1_Load);

Per sottoscrivere gli eventi a livello di codice


1. Definire un metodo del gestore eventi la cui firma corrisponda alla firma del delegato per l'evento. Se ad
esempio l'evento è basato sul tipo di delegato EventHandler, il codice riportato di seguito rappresenta lo
stub del metodo:

void HandleCustomEvent(object sender, CustomEventArgs a)


{
// Do something useful here.
}

2. Usare l'operatore di assegnazione di addizione ( += ) per associare un gestore all'evento. Nell'esempio


seguente si supponga che a un oggetto denominato publisher sia associato un evento denominato
RaiseCustomEvent . Si noti che per la classe subscriber è necessario un riferimento alla classe publisher
per sottoscrivere gli eventi corrispondenti.
publisher.RaiseCustomEvent += HandleCustomEvent;

Si noti che la sintassi precedente è nuova in C# 2.0. Equivale esattamente alla sintassi di C# 1.0, in cui è
necessario creare in modo esplicito il delegato incapsulante tramite la parola chiave new :

publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);

È anche possibile usare un' espressione lambda per specificare un gestore eventi:

public Form1()
{
InitializeComponent();
this.Click += (s,e) =>
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
}

Per sottoscrivere gli eventi usando un metodo anonimo


Se non è necessario annullare la sottoscrizione di un evento in un secondo momento, è possibile usare
l'operatore di assegnazione di addizione ( += ) per associare un metodo anonimo all'evento. Nell'esempio
seguente si supponga che a un oggetto denominato publisher sia associato un evento denominato
RaiseCustomEvent e che sia stata definita una classe CustomEventArgs con informazioni specializzate
sull'evento. Si noti che per la classe subscriber è necessario un riferimento alla classe publisher per
sottoscrivere gli eventi corrispondenti.

publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)


{
string s = o.ToString() + " " + e.ToString();
Console.WriteLine(s);
};

È importante notare che non si può annullare facilmente la sottoscrizione di un evento se per la
sottoscrizione è stata usata una funzione anonima. Per annullare la sottoscrizione in questo scenario, è
necessario tornare al codice in cui è stato sottoscritto l'evento, archiviare il metodo anonimo in una
variabile del delegato e quindi aggiungere il delegato all'evento. In generale è consigliabile non usare
funzioni anonime per sottoscrivere eventi se si prevede di dover annullare la sottoscrizione all'evento in
un punto successivo nel codice. Per altre informazioni sulle funzioni anonime, vedere Funzioni anonime.

Annullamento della sottoscrizione


Per evitare che il gestore eventi venga chiamato al momento della generazione dell'evento, annullare la
sottoscrizione all'evento stesso. Per evitare di perdere risorse, è necessario annullare la sottoscrizione agli eventi
prima di eliminare un oggetto sottoscrittore. Finché non si annulla la sottoscrizione di un evento, il delegato
multicast sottostante all'evento nell'oggetto publisher contiene un riferimento al delegato che incapsula il
gestore eventi del sottoscrittore. Finché l'oggetto publisher include tale riferimento, l'oggetto subscriber non
verrà eliminato dal processo di Garbage Collection.
Per annullare la sottoscrizione di un evento
Usare l'operatore di assegnazione di sottrazione ( -= ):

publisher.RaiseCustomEvent -= HandleCustomEvent;
Quando tutti i sottoscrittori hanno annullato la sottoscrizione a un evento, l'istanza dell'evento nella
classe publisher viene impostata su null .

Vedere anche
Eventi
event
Come pubblicare eventi conformi alle linee guida di .NET
operatori-and-=
Operatori + e +=
Come pubblicare eventi conformi alle linee guida di
.NET (Guida per programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

La procedura seguente illustra come aggiungere eventi che seguono il modello .NET standard alle classi e agli
struct. Tutti gli eventi della libreria di classi .NET sono basati sul EventHandler delegato, che viene definito come
segue:

public delegate void EventHandler(object sender, EventArgs e);

NOTE
.NET Framework 2,0 introduce una versione generica di questo delegato EventHandler<TEventArgs> . Negli esempi
seguenti viene illustrato l'uso di entrambe le versioni.

Anche se gli eventi nelle classi definite possono essere basati su qualsiasi tipo di delegato valido, anche delegati
che restituiscono un valore, è in genere consigliabile basare gli eventi sul modello .NET tramite EventHandler ,
come illustrato nell'esempio seguente.
Il nome EventHandler può causare un po' di confusione perché non gestisce effettivamente l'evento.
EventHandler, E Generic EventHandler<TEventArgs> sono tipi delegati. Un metodo o un'espressione lambda la
cui firma corrisponde alla definizione del delegato è il gestore eventi e verrà richiamato quando viene generato
l'evento.

Pubblicare eventi in base al modello EventHandler


1. Ignorare questo passaggio e andare al passaggio 3a se non è necessario inviare dati personalizzati con
l'evento. Dichiarare la classe per i dati personalizzati in un ambito visibile per le classi del server di
pubblicazione e del Sottoscrittore. Aggiungere i membri necessari per contenere i dati di evento
personalizzati. In questo esempio viene restituita una semplice stringa.

public class CustomEventArgs : EventArgs


{
public CustomEventArgs(string message)
{
Message = message;
}

public string Message { get; set; }


}

2. Ignorare questo passaggio se si usa la versione generica di EventHandler<TEventArgs> . Dichiarare un


delegato nella classe di pubblicazione. Assegnargli un nome che termina con EventHandler . Il secondo
parametro specifica il EventArgs tipo personalizzato.

public delegate void CustomEventHandler(object sender, CustomEventArgs args);

3. Dichiarare l'evento nella classe di pubblicazione usando uno dei passaggi seguenti.
a. Se non si ha una classe EventArgs personalizzata, il tipo di evento sarà il delegato EventHandler
non generico. Non è necessario dichiarare il delegato perché è già dichiarato nello spazio dei nomi
System, incluso quando si crea il progetto C#. Aggiungere il codice seguente alla classe
dell'editore.

public event EventHandler RaiseCustomEvent;

b. Se si usa la versione non generica di EventHandler e si ha una classe personalizzata derivata da


EventArgs, dichiarare l'evento all'interno della classe di pubblicazione e usare il delegato del
passaggio 2 come tipo.

public event CustomEventHandler RaiseCustomEvent;

c. Se si usa la versione generica, non è necessario un delegato personalizzato. Al contrario, nella


classe di pubblicazione specificare il tipo di evento come EventHandler<CustomEventArgs> ,
sostituendo il nome della classe racchiuso tra parentesi acute.

public event EventHandler<CustomEventArgs> RaiseCustomEvent;

Esempio
L'esempio seguente illustra i passaggi precedenti usando una classe EventArgs personalizzata e
EventHandler<TEventArgs> come tipo di evento.

using System;

namespace DotNetEvents
{
// Define a class to hold custom event info
public class CustomEventArgs : EventArgs
{
public CustomEventArgs(string message)
{
Message = message;
}

public string Message { get; set; }


}

// Class that publishes an event


class Publisher
{
// Declare the event using EventHandler<T>
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

public void DoSomething()


{
// Write some code that does something useful here
// then raise the event. You can also raise an event
// before you execute a block of code.
OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
}

// Wrap event invocations inside a protected virtual method


// to allow derived classes to override the event invocation behavior
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> raiseEvent = RaiseCustomEvent;

// Event will be null if there are no subscribers


if (raiseEvent != null)
{
// Format the string to send inside the CustomEventArgs parameter
e.Message += $" at {DateTime.Now}";

// Call to raise the event.


raiseEvent(this, e);
}
}
}

//Class that subscribes to an event


class Subscriber
{
private readonly string _id;

public Subscriber(string id, Publisher pub)


{
_id = id;

// Subscribe to the event


pub.RaiseCustomEvent += HandleCustomEvent;
}

// Define what actions to take when the event is raised.


void HandleCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine($"{_id} received this message: {e.Message}");
}
}

class Program
{
static void Main()
{
var pub = new Publisher();
var sub1 = new Subscriber("sub1", pub);
var sub2 = new Subscriber("sub2", pub);

// Call the method that raises the event.


pub.DoSomething();

// Keep the console window open


Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
}

Vedi anche
Delegate
Guida per programmatori C#
Eventi
Delegati
Come generare eventi di classe base nelle classi
derivate (Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

L'esempio seguente illustra il metodo standard di dichiarazione degli eventi in una classe base in modo che
possano essere generati anche dalle classi derivate. Questo modello viene ampiamente usato nelle classi
Windows Forms nelle librerie di classi .NET.
Quando si crea una classe che può essere usata come classe base di altre classi, è necessario tenere presente che
gli eventi sono un tipo speciale di delegati che possono essere richiamati solo dall'interno della classe che li ha
dichiarati. Le classi derivate non possono richiamare direttamente gli eventi dichiarati all'interno della classe
base. Nonostante sia necessario a volte un evento che possa essere generato solo dalla classe base, nella
maggior parte dei casi è opportuno consentire alla classe derivata di richiamare eventi della classe base. A tale
scopo, è possibile creare un metodo di chiamata protetto nella classe base che esegue il wrapping dell'evento.
Le classi derivate possono richiamare indirettamente l'evento richiamando o eseguendo l'override di questo
metodo di chiamata.

NOTE
Non dichiarare eventi virtuali in una classe base ed eseguire l'override in una classe derivata. Il compilatore C# non è in
grado di gestirli correttamente e non è possibile prevedere se un sottoscrittore dell'evento derivato sottoscriverà
effettivamente l'evento della classe base.

Esempio
namespace BaseClassEvents
{
// Special EventArgs class to hold info about Shapes.
public class ShapeEventArgs : EventArgs
{
public ShapeEventArgs(double area)
{
NewArea = area;
}

public double NewArea { get; }


}

// Base class event publisher


public abstract class Shape
{
protected double _area;

public double Area


{
get => _area;
set => _area = value;
}

// The event. Note that by using the generic EventHandler<T> event type
// we do not need to declare a separate delegate type.
public event EventHandler<ShapeEventArgs> ShapeChanged;

public abstract void Draw();


public abstract void Draw();

//The event-invoking method that derived classes can override.


protected virtual void OnShapeChanged(ShapeEventArgs e)
{
// Safely raise the event for all subscribers
ShapeChanged?.Invoke(this, e);
}
}

public class Circle : Shape


{
private double _radius;

public Circle(double radius)


{
_radius = radius;
_area = 3.14 * _radius * _radius;
}

public void Update(double d)


{
_radius = d;
_area = 3.14 * _radius * _radius;
OnShapeChanged(new ShapeEventArgs(_area));
}

protected override void OnShapeChanged(ShapeEventArgs e)


{
// Do any circle-specific processing here.

// Call the base class event invocation method.


base.OnShapeChanged(e);
}

public override void Draw()


{
Console.WriteLine("Drawing a circle");
}
}

public class Rectangle : Shape


{
private double _length;
private double _width;

public Rectangle(double length, double width)


{
_length = length;
_width = width;
_area = _length * _width;
}

public void Update(double length, double width)


{
_length = length;
_width = width;
_area = _length * _width;
OnShapeChanged(new ShapeEventArgs(_area));
}

protected override void OnShapeChanged(ShapeEventArgs e)


{
// Do any rectangle-specific processing here.

// Call the base class event invocation method.


base.OnShapeChanged(e);
}

public override void Draw()


public override void Draw()
{
Console.WriteLine("Drawing a rectangle");
}
}

// Represents the surface on which the shapes are drawn


// Subscribes to shape events so that it knows
// when to redraw a shape.
public class ShapeContainer
{
private readonly List<Shape> _list;

public ShapeContainer()
{
_list = new List<Shape>();
}

public void AddShape(Shape shape)


{
_list.Add(shape);

// Subscribe to the base class event.


shape.ShapeChanged += HandleShapeChanged;
}

// ...Other methods to draw, resize, etc.

private void HandleShapeChanged(object sender, ShapeEventArgs e)


{
if (sender is Shape shape)
{
// Diagnostic message for demonstration purposes.
Console.WriteLine($"Received event. Shape area is now {e.NewArea}");

// Redraw the shape here.


shape.Draw();
}
}
}

class Test
{
static void Main()
{
//Create the event publishers and subscriber
var circle = new Circle(54);
var rectangle = new Rectangle(12, 9);
var container = new ShapeContainer();

// Add the shapes to the container.


container.AddShape(circle);
container.AddShape(rectangle);

// Cause some events to be raised.


circle.Update(57);
rectangle.Update(7, 7);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}
/* Output:
Received event. Shape area is now 10201.86
Drawing a circle
Received event. Shape area is now 49
Drawing a rectangle
*/
*/

Vedere anche
Guida per programmatori C#
Eventi
Delegati
Modificatori di accesso
Creazione di gestori eventi in Windows Form
Come implementare gli eventi di interfaccia (Guida
per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Un'interfaccia consente di dichiarare un evento. Nell'esempio seguente viene illustrato come implementare
eventi di interfaccia in una classe. Le regole sono le stesse usate per l'implementazione di qualsiasi metodo di
interfaccia o proprietà.

Per implementare eventi di interfaccia in una classe


Dichiarare l'evento nella classe e quindi richiamarlo nelle posizioni appropriate.

namespace ImplementInterfaceEvents
{
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs
{
// class members
}
public class Shape : IDrawingObject
{
public event EventHandler ShapeChanged;
void ChangeShape()
{
// Do something here before the event…

OnShapeChanged(new MyEventArgs(/*arguments*/));

// or do something here after the event.


}
protected virtual void OnShapeChanged(MyEventArgs e)
{
ShapeChanged?.Invoke(this, e);
}
}

Esempio
Nell'esempio seguente viene illustrato come gestire la situazione meno comune in cui la classe eredita da due o
più interfacce e a ogni interfaccia è associato un evento con lo stesso nome. In questo caso, è necessario fornire
un'implementazione di interfaccia esplicita per almeno uno degli eventi. Quando si scrive un'implementazione di
interfaccia esplicita per un evento, è anche necessario scrivere le funzioni di accesso agli eventi add e remove .
In genere queste funzioni sono fornite dal compilatore, ma in questo caso il compilatore non è in grado di farlo.
Fornendo funzioni di accesso personalizzate, è possibile specificare se i due eventi sono rappresentati dallo
stesso evento nella classe o da eventi diversi. Ad esempio, se gli eventi devono essere generati in momenti
diversi secondo le specifiche dell'interfaccia, è possibile associare ogni evento a un'implementazione separata
nella classe. Nell'esempio seguente i sottoscrittori determinano l'evento OnDraw che riceveranno eseguendo il
cast del riferimento della forma su IShape o IDrawingObject .
namespace WrapTwoInterfaceEvents
{
using System;

public interface IDrawingObject


{
// Raise this event before drawing
// the object.
event EventHandler OnDraw;
}
public interface IShape
{
// Raise this event after drawing
// the shape.
event EventHandler OnDraw;
}

// Base class event publisher inherits two


// interfaces, each with an OnDraw event
public class Shape : IDrawingObject, IShape
{
// Create an event for each interface event
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;

object objectLock = new Object();

// Explicit interface implementation required.


// Associate IDrawingObject's event with
// PreDrawEvent
#region IDrawingObjectOnDraw
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}
#endregion
// Explicit interface implementation required.
// Associate IShape's event with
// PostDrawEvent
event EventHandler IShape.OnDraw
{
add
{
lock (objectLock)
{
PostDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PostDrawEvent -= value;
}
}
}
}

// For the sake of simplicity this one method


// implements both interfaces.
public void Draw()
{
// Raise IDrawingObject's event before the object is drawn.
PreDrawEvent?.Invoke(this, EventArgs.Empty);

Console.WriteLine("Drawing a shape.");

// Raise IShape's event after the object is drawn.


PostDrawEvent?.Invoke(this, EventArgs.Empty);
}
}
public class Subscriber1
{
// References the shape object as an IDrawingObject
public Subscriber1(Shape shape)
{
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += d_OnDraw;
}

void d_OnDraw(object sender, EventArgs e)


{
Console.WriteLine("Sub1 receives the IDrawingObject event.");
}
}
// References the shape object as an IShape
public class Subscriber2
{
public Subscriber2(Shape shape)
{
IShape d = (IShape)shape;
d.OnDraw += d_OnDraw;
}

void d_OnDraw(object sender, EventArgs e)


{
Console.WriteLine("Sub2 receives the IShape event.");
}
}

public class Program


{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
/* Output:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Sub2 receives the IShape event.
*/

Vedere anche
Guida per programmatori C#
Eventi
Delegati
Implementazione esplicita dell'interfaccia
Come generare eventi di classe base nelle classi derivate
Come implementare funzioni di accesso a eventi
personalizzati (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Un evento è un tipo speciale di delegato multicast che può essere richiamato solo dall'interno della classe in cui
è dichiarato. Il codice client sottoscrive l'evento fornendo un riferimento a un metodo che deve essere
richiamato quando l'evento viene generato. Questi metodi vengono aggiunti all'elenco di chiamate del delegato
tramite funzioni di accesso agli eventi, che sono simili alle funzioni di accesso alle proprietà, ad eccezione del
fatto che sono denominate add e remove . Nella maggior parte dei casi, non è necessario fornire funzioni di
accesso agli eventi personalizzate. Se nel codice non vengono fornite funzioni di accesso agli eventi
personalizzate, il compilatore le aggiunge automaticamente. In alcuni casi tuttavia può essere necessario fornire
un comportamento personalizzato. Uno di questi casi viene illustrato nell'argomento come implementare gli
eventi di interfaccia.

Esempio
L'esempio seguente mostra come implementare le funzioni di accesso agli eventi add e remove personalizzate.
Anche se è possibile sostituire qualsiasi parte di codice nelle funzioni di accesso, si consiglia di bloccare l'evento
prima di aggiungere o rimuovere un nuovo metodo del gestore eventi.

event EventHandler IDrawingObject.OnDraw


{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}

Vedere anche
Eventi
event
Generics (Guida per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

I generics introducono il concetto di parametri di tipo in .NET, che rendono possibile la progettazione di classi e
metodi che rinviano la specifica di uno o più tipi fino a quando la classe o il metodo non viene dichiarato e ne
viene creata un'istanza dal codice client. Usando, ad esempio, un parametro di tipo generico T , è possibile
scrivere una singola classe che può essere usata da un altro codice client senza sostenere il costo o il rischio di
cast di runtime o di operazioni di conversione boxing, come illustrato di seguito:

// Declare the generic class.


public class GenericList<T>
{
public void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
// Declare a list of type int.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);

// Declare a list of type string.


GenericList<string> list2 = new GenericList<string>();
list2.Add("");

// Declare a list of type ExampleClass.


GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
}
}

Classi e metodi generici combinano riusabilità, indipendenza dai tipi ed efficienza in modo che non possano
essere presenti controparti non generiche. I generics sono in genere usati con le raccolte e i metodi che operano
su di essi. Lo System.Collections.Generic spazio dei nomi contiene diverse classi di raccolta basate su generiche.
Le raccolte non generiche, ad esempio, ArrayList non sono consigliate e vengono mantenute per motivi di
compatibilità. Per altre informazioni, vedere Generics in .NET.
Naturalmente, è anche possibile creare tipi e metodi generici personalizzati per offrire le proprie soluzioni e
schemi progettuali generalizzati che sono indipendenti dai tipi ed efficienti. Nell'esempio di codice riportato di
seguito viene illustrata una classe di elenco collegato generica semplice a scopo dimostrativo. Nella maggior
parte dei casi, è consigliabile usare la List<T> classe fornita da .NET anziché crearne una personalizzata. Il
parametro di tipo T viene usato in diverse posizioni in cui un tipo concreto viene normalmente usato per
indicare il tipo di elemento archiviato nell'elenco. In particolare, viene usato nei seguenti modi:
Come tipo di un parametro del metodo nel metodo AddHead .
Come tipo restituito della proprietà Data nella classe Node annidata.
Come tipo del membro privato data nella classe annidata.
Si noti che T è disponibile per la Node classe annidata. Quando si crea un'istanza di GenericList<T> con un
tipo concreto, ad esempio GenericList<int> , ogni occorrenza di T verrà sostituita con int .
// type parameter T in angle brackets
public class GenericList<T>
{
// The nested class is also generic on T.
private class Node
{
// T used in non-generic constructor.
public Node(T t)
{
next = null;
data = t;
}

private Node next;


public Node Next
{
get { return next; }
set { next = value; }
}

// T as private member data type.


private T data;

// T as return type of property.


public T Data
{
get { return data; }
set { data = value; }
}
}

private Node head;

// constructor
public GenericList()
{
head = null;
}

// T as method parameter type:


public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}

public IEnumerator<T> GetEnumerator()


{
Node current = head;

while (current != null)


{
yield return current.Data;
current = current.Next;
}
}
}

Nell'esempio di codice riportato di seguito viene illustrato come il codice client usa la classe generica
GenericList<T> per creare un elenco di interi. Cambiando semplicemente l'argomento relativo al tipo, è
possibile modificare il codice riportato di seguito per creare elenchi di stringhe o di qualsiasi altro tipo
personalizzato:
class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();

for (int x = 0; x < 10; x++)


{
list.AddHead(x);
}

foreach (int i in list)


{
System.Console.Write(i + " ");
}
System.Console.WriteLine("\nDone");
}
}

Cenni preliminari sui generics


Usare i tipi generici per ottimizzare il riutilizzo del codice, l'indipendenza dai tipi e le prestazioni.
L'uso più comune dei generics consiste nel creare classi di raccolte.
La libreria di classi .NET contiene varie classi di raccolte generiche nello System.Collections.Generic spazio dei
nomi. Queste classi devono essere usate ogni volta che sia possibile al posto di classi come ArrayList nello
spazio dei nomi System.Collections.
È possibile creare interfacce, classi, metodi, eventi e delegati generici.
Le classi generiche possono essere limitate in modo da abilitare l'accesso ai metodi per particolari tipi di dati.
Le informazioni sui tipi usati in un tipo di dati generico possono essere ottenute usando la reflection in fase
di esecuzione.

Sezioni correlate
Parametri di tipo generico
Vincoli sui parametri di tipo
Classi generiche
Interfacce generiche
Metodi generici
Delegati generici
Differenze tra modelli C++ e generics C#
Generics e reflection
Generics in fase di esecuzione

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#.

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Tipi
<typeparam>
<typeparamref>
Generics in .NET
Parametri di tipo generico (Guida per
programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

In una definizione di tipo o metodo generico un parametro di tipo è un segnaposto per un determinato tipo
specificato da un client durante la creazione di un'istanza del tipo generico. Una classe generica, ad esempio
GenericList<T> , elencata in Introduzione ai generics, non può essere usata in quanto tale perché non è
effettivamente un tipo, ma è piuttosto un progetto iniziale per un tipo. Per usare GenericList<T> , il codice client
deve dichiarare un tipo costruito e crearne un'istanza specificando un argomento tipo racchiuso tra parentesi
angolari. L'argomento tipo per questa classe specifica può essere qualsiasi tipo riconosciuto dal compilatore. È
possibile creare un numero qualsiasi di istanze del tipo costruito, ognuna con un argomento tipo diverso, in
questo modo:

GenericList<float> list1 = new GenericList<float>();


GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

In ognuna di queste istanze di GenericList<T> ogni occorrenza di T nella classe viene sostituita in fase di
esecuzione con l'argomento tipo. Per mezzo di questa sostituzione, sono stati creati tre oggetti efficienti e
indipendenti dai tipi separati usando un'unica definizione di classe. Per altre informazioni su come viene
eseguita questa sostituzione da CLR, vedere Generics nel runtime.

Linee guida per la denominazione dei parametri di tipo


Assegnare ai parametri di tipo generico nomi descrittivi, a meno che un nome di una sola lettera non sia
già completamente comprensibile, senza che sia necessario un nome descrittivo più lungo.

public interface ISessionChannel<TSession> { /*...*/ }


public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }

Provare a usare T come nome del parametro di tipo per i tipi con parametro di tipo di una sola lettera.

public int IComparer<T>() { return 0; }


public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }

Aggiungere ai nomi di parametro di tipo descrittivi il prefisso "T".

public interface ISessionChannel<TSession>


{
TSession Session { get; }
}

Provare a indicare i vincoli applicati a un parametro di tipo nel nome del parametro. Ad esempio,
assegnare a un parametro vincolato a ISession il nome TSession .
La regola di analisi codice CA1715 può essere usata per garantire che i parametri di tipo vengano denominati in
modo appropriato.
Vedere anche
System.Collections.Generic
Guida per programmatori C#
Generics
Differenze tra modelli C++ e generics C#
Vincoli sui parametri di tipo (Guida per
programmatori C#)
28/01/2021 • 18 minutes to read • Edit Online

I vincoli indicano al compilatore quali funzionalità deve usare un argomento tipo. Senza i vincoli, l'argomento
tipo può essere qualsiasi tipo. Il compilatore è in grado di dedurre solo i membri di System.Object, che è la
principale classe di base per qualsiasi tipo .NET. Per altre informazioni, vedere Motivi per cui usare i vincoli. Se il
codice client usa un tipo che non soddisfa un vincolo, il compilatore genera un errore. I vincoli vengono
specificati usando la parola chiave contestuale where . Nella tabella seguente sono elencati i vari tipi di vincoli:

VIN C O LO DESC RIZ IO N E

where T : struct L'argomento di tipo deve essere un tipo di valorenon


nullable. Per informazioni sui tipi di valore Nullable, vedere
tipi di valore Nullable. Poiché tutti i tipi di valore hanno un
costruttore senza parametri accessibile, il struct vincolo
implica il new() vincolo e non può essere combinato con il
new() vincolo. Non è possibile combinare il struct
vincolo con il unmanaged vincolo.

where T : class L'argomento tipo deve essere un tipo riferimento. Questo


vincolo si applica anche a qualsiasi tipo di classe, interfaccia,
delegato o matrice. In un contesto nullable in C# 8,0 o
versione successiva T deve essere un tipo di riferimento
non nullable.

where T : class? L'argomento di tipo deve essere un tipo riferimento, che


ammette i valori null o che non ammette valori null. Questo
vincolo si applica anche a qualsiasi tipo di classe, interfaccia,
delegato o matrice.

where T : notnull L'argomento di tipo deve essere un tipo non nullable.


L'argomento può essere un tipo di riferimento non nullable
in C# 8,0 o versione successiva oppure un tipo di valore non
nullable.

where T : unmanaged L'argomento di tipo deve essere un tipo non gestitoche non
ammette i valori null. Il unmanaged vincolo implica il
struct vincolo e non può essere combinato con i struct
new() vincoli o.

where T : new() L'argomento tipo deve avere un costruttore pubblico senza


parametri. Quando il vincolo new() viene usato con altri
vincoli, deve essere specificato per ultimo. Il new() vincolo
non può essere combinato con struct i unmanaged
vincoli e.

where T : <base class name> L'argomento tipo deve corrispondere alla classe di base
specificata o derivare da essa. In un contesto nullable in C#
8,0 e versioni successive, T deve essere un tipo di
riferimento non nullable derivato dalla classe base specificata.
VIN C O LO DESC RIZ IO N E

where T : <base class name>? L'argomento tipo deve corrispondere alla classe di base
specificata o derivare da essa. In un contesto nullable in C#
8,0 e versioni successive, T può essere un tipo nullable o
non nullable derivato dalla classe base specificata.

where T : <interface name> L'argomento tipo deve corrispondere all'interfaccia


specificata o implementare tale interfaccia. È possibile
specificare più vincoli di interfaccia. L'interfaccia vincolante
può anche essere generica. In un contesto nullable in C# 8,0
e versioni successive, T deve essere un tipo non nullable
che implementa l'interfaccia specificata.

where T : <interface name>? L'argomento tipo deve corrispondere all'interfaccia


specificata o implementare tale interfaccia. È possibile
specificare più vincoli di interfaccia. L'interfaccia vincolante
può anche essere generica. In un contesto nullable in C# 8,0,
T può essere un tipo di riferimento Nullable, un tipo di
riferimento non nullable o un tipo di valore. T non può
essere un tipo di valore Nullable.

where T : U L'argomento di tipo fornito per T deve essere o derivare


dall'argomento fornito per U . In un contesto Nullable, se
U è un tipo di riferimento non nullable, T deve essere un
tipo di riferimento non nullable. Se U è un tipo di
riferimento Nullable, T può essere nullable o non nullable.

Motivi per cui usare i vincoli


I vincoli specificano le funzionalità e le aspettative di un parametro di tipo. Dichiarando tali vincoli, è possibile
utilizzare le operazioni e le chiamate al metodo del tipo vincolante. Se la classe o il metodo generico Usa
qualsiasi operazione sui membri generici oltre l'assegnazione semplice o la chiamata a metodi non supportati
da System.Object , sarà necessario applicare vincoli al parametro di tipo. Specificando il vincolo della classe di
base, ad esempio, si indica al compilatore che verranno usati come argomenti tipo solo gli oggetti del tipo
specificato o derivati da esso. In presenza di questa garanzia, il compilatore può consentire le chiamate ai metodi
del tipo all'interno della classe generica. L'esempio di codice seguente illustra la funzionalità che è possibile
aggiungere alla classe GenericList<T> (in Introduzione ai generics) applicando un vincolo della classe di base.
public class Employee
{
public Employee(string name, int id) => (Name, ID) = (name, id);
public string Name { get; set; }
public int ID { get; set; }
}

public class GenericList<T> where T : Employee


{
private class Node
{
public Node(T t) => (Next, Data) = (null, t);

public Node Next { get; set; }


public T Data { get; set; }
}

private Node head;

public void AddHead(T t)


{
Node n = new Node(t) { Next = head };
head = n;
}

public IEnumerator<T> GetEnumerator()


{
Node current = head;

while (current != null)


{
yield return current.Data;
current = current.Next;
}
}

public T FindFirstOccurrence(string s)
{
Node current = head;
T t = null;

while (current != null)


{
//The constraint enables access to the Name property.
if (current.Data.Name == s)
{
t = current.Data;
break;
}
else
{
current = current.Next;
}
}
return t;
}
}

Il vincolo consente alla classe generica di usare la proprietà Employee.Name . Il vincolo specifica che tutti gli
elementi di tipo T sono sicuramente un oggetto Employee o un oggetto che eredita da Employee .
È possibile applicare più vincoli allo stesso parametro di tipo. I vincoli stessi possono essere tipi generici, come
illustrato di seguito:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}

Quando si applica il vincolo where T : class , evitare gli operatori == e != nel parametro di tipo perché questi
operatori verificano solo l'identità del riferimento e non l'uguaglianza dei valori. Questo comportamento si
verifica anche se si esegue l'overload degli operatori in un tipo usato come argomento. Il codice seguente
illustra questo aspetto. L'output è false anche se la classe String esegue l'overload dell'operatore == .

public static void OpEqualsTest<T>(T s, T t) where T : class


{
System.Console.WriteLine(s == t);
}

private static void TestStringEquality()


{
string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpEqualsTest<string>(s1, s2);
}

Il compilatore sa solo che T è un tipo di riferimento in fase di compilazione e deve usare gli operatori
predefiniti validi per tutti i tipi di riferimento. Per verificare l'uguaglianza dei valori, è consigliabile applicare
anche il vincolo where T : IEquatable<T> o where T : IComparable<T> e implementare l'interfaccia nelle classi
che verranno usate per costruire la classe generica.

Vincolo di più parametri


È possibile applicare vincoli a più parametri e più vincoli a un singolo parametro, come illustrato nell'esempio
seguente:

class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }

Parametri di tipo senza vincoli


I parametri di tipo che non hanno vincoli, ad esempio T nella classe pubblica SampleClass<T>{} , sono detti
parametri di tipo senza vincoli. I parametri di tipo senza vincoli prevedono le regole seguenti:
Gli != operatori e non == possono essere usati perché non vi è alcuna garanzia che l'argomento di tipo
concreto supporterà questi operatori.
Possono essere convertiti in e da System.Object oppure convertiti in modo esplicito in qualsiasi tipo di
interfaccia.
È possibile confrontarli con Null. Se si confronta un parametro senza vincoli con null e l'argomento tipo è
un tipo valore, verrà sempre restituito false.

Parametri di tipo come vincoli


L'uso di un parametro di tipo generico come vincolo è utile quando una funzione membro con il proprio
parametro di tipo deve vincolare tale parametro a quello del tipo che lo contiene, come illustrato nell'esempio
seguente:

public class List<T>


{
public void Add<U>(List<U> items) where U : T {/*...*/}
}

Nell'esempio precedente T è un vincolo di tipo nel contesto del metodo Add e un parametro di tipo senza
vincoli nel contesto della classe List .
I parametri di tipo possono anche essere usati come vincoli nelle definizioni di classi generiche. Il parametro di
tipo deve essere dichiarato tra parentesi acute, insieme a eventuali altri parametri di tipo:

//Type parameter V is used as a type constraint.


public class SampleClass<T, U, V> where T : V { }

L'utilità dei parametri di tipo usati come vincoli in classi generiche è limitata poiché il compilatore non può
presupporre niente riguardo al parametro di tipo, tranne il fatto che deriva da System.Object . Usare i parametri
di tipo come vincoli nelle classi generiche in scenari in cui si vuole applicare una relazione di ereditarietà tra due
parametri di tipo.

Vincolo NotNull
A partire da C# 8,0 in un contesto Nullable, è possibile usare il notnull vincolo per specificare che l'argomento
di tipo deve essere un tipo di valore non nullable o un tipo di riferimento non nullable. Il notnull vincolo può
essere usato solo in un nullable enable contesto. Il compilatore genera un avviso se si aggiunge il notnull
vincolo in un contesto ignaro che ammette i valori null.
Diversamente da altri vincoli, quando un argomento di tipo viola il notnull vincolo, il compilatore genera un
avviso quando il codice viene compilato in un nullable enable contesto. Se il codice viene compilato in un
contesto ignaro Nullable, il compilatore non genera avvisi o errori.
A partire da C# 8,0 in un contesto Nullable, il class vincolo specifica che l'argomento di tipo deve essere un
tipo di riferimento non nullable. In un contesto Nullable, quando un parametro di tipo è un tipo di riferimento
Nullable, il compilatore genera un avviso.

Vincolo non gestito


A partire da C# 7,3, è possibile usare il unmanaged vincolo per specificare che il parametro di tipo deve essere un
tipo non gestitoche non ammette i valori null. Il vincolo unmanaged consente di scrivere routine riutilizzabili per
lavorare con tipi che possono essere modificati come blocchi di memoria, come illustrato nell'esempio seguente:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged


{
var size = sizeof(T);
var result = new Byte[size];
Byte* p = (byte*)&argument;
for (var i = 0; i < size; i++)
result[i] = *p++;
return result;
}

Il metodo precedente deve essere compilato in un contesto unsafe perché usa l'operatore sizeof per un tipo
non noto come tipo predefinito. Senza il vincolo unmanaged l'operatore sizeof non è disponibile.
Il unmanaged vincolo implica il struct vincolo e non può essere combinato con esso. Poiché il vincolo struct
implica il new() vincolo, il unmanaged vincolo non può essere combinato anche con il vincolo new() .

Vincoli dei delegati


A partire da C# 7.3 è inoltre possibile usare System.Delegate o System.MulticastDelegate come vincolo di classe
di base. Il supporto Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del
linguaggio C#. Il vincolo System.Delegate consente di scrivere codice che funziona con i delegati in modo
indipendente dai tipi. Il codice seguente definisce un metodo di estensione che combina due delegati purché
siano dello stesso tipo:

public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)


where TDelegate : System.Delegate
=> Delegate.Combine(source, target) as TDelegate;

Per combinare delegati dello stesso tipo, è possibile usare il metodo riportato sopra:

Action first = () => Console.WriteLine("this");


Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);


combined();

Func<bool> test = () => true;


// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Se si rimuove il commento dall'ultima riga, non verrà compilata. Sia first che test sono tipi delegati, ma si
tratta di tipi delegati diversi.

Vincoli di enumerazione
A partire da C# 7.3 è anche possibile specificare il tipo System.Enum come vincolo di classe di base. Il supporto
Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del linguaggio C#. I generics
che usano System.Enum offrono una programmazione indipendente dai tipi che consente di memorizzare nella
cache i risultati dei metodi statici in System.Enum . Nell'esempio seguente vengono individuati tutti i valori validi
per un tipo di enumerazione e viene compilato un dizionario che esegue il mapping di tali valori alla propria
rappresentazione di stringa.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum


{
var result = new Dictionary<int, string>();
var values = Enum.GetValues(typeof(T));

foreach (int item in values)


result.Add(item, Enum.GetName(typeof(T), item));
return result;
}

Enum.GetValues e Enum.GetName usano la reflection, che ha implicazioni sulle prestazioni. È possibile chiamare
EnumNamedValues per compilare una raccolta memorizzata nella cache e riutilizzata anziché ripetere le chiamate
che richiedono la reflection.
Il metodo può essere usato come illustrato nell'esempio seguente per creare un'enumerazione e compilare un
dizionario dei relativi valori e nomi:
enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}

var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)


Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Vedi anche
System.Collections.Generic
Guida per programmatori C#
Introduzione ai generics
Classi generiche
nuovo vincolo
Classi generiche (Guida per programmatori C#)
02/11/2020 • 7 minutes to read • Edit Online

Le classi generiche incapsulano operazioni che non sono specifiche di un determinato tipo di dati. L'uso più
comune per le classi generiche è con raccolte come elenchi collegati, tabelle hash, stack, code, alberi e così via.
Le operazioni come l'aggiunta e la rimozione di elementi dalla raccolta vengono eseguite praticamente allo
stesso modo, indipendentemente dal tipo dei dati archiviati.
Per la maggior parte degli scenari che richiedono classi di raccolta, l'approccio consigliato consiste nell'usare
quelle disponibili nella libreria di classi .NET. Per altre informazioni sull'uso di queste classi, vedere Generic
Collections in .NET (Raccolte generiche in .NET).
Normalmente, per creare classi generiche è possibile iniziare da una classe concreta esistente, modificando
quindi i tipi in parametri di tipo uno per volta fino a raggiungere l'equilibrio ottimale tra generalizzazione e
usabilità. Ecco alcuni aspetti importanti di cui tenere conto quando si creano classi generiche personalizzate:
Tipi da generalizzare in parametri di tipo.
Di norma, maggiore è il numero di tipi che è possibile parametrizzare, più flessibile e riutilizzabile sarà il
codice. Tuttavia, una generalizzazione eccessiva può creare codice difficile da leggere e comprendere per
gli altri sviluppatori.
Vincoli, se presenti, da applicare ai parametri di tipo. Vedere Vincoli sui parametri di tipo.
Una buona regola consiste nell'applicare il numero massimo di vincoli possibile, ma che permetta di
continuare a gestire tutti i tipi necessari. Se, ad esempio, la classe generica è destinata solo all'uso con tipi
riferimento, applicare il vincolo di classe. In questo modo, si eviterà l'uso indesiderato della classe con tipi
valore e sarà possibile usare l'operatore as in T e verificare la presenza di valori null.
Se suddividere il comportamento generico in classi e sottoclassi base.
Poiché le classi generiche possono fungere da classi base, valgono le stesse considerazioni di
progettazione relative alle classi non generiche. Vedere le regole sull'ereditarietà da classi base generiche
più avanti in questo argomento.
Se implementare una o più interfacce generiche.
Se, ad esempio, si progetta una classe che verrà usata per creare elementi in una raccolta basata su
generics, potrebbe essere necessario implementare un'interfaccia come IComparable<T>, dove T è il
tipo della classe.
Per un esempio di una classe generica semplice, vedere Introduzione ai generics.
Le regole per i parametri di tipo e i vincoli hanno diverse implicazioni per il comportamento delle classi
generiche, in particolare riguardo a ereditarietà e accessibilità dei membri. Prima di continuare, è utile
comprendere alcuni termini. Per una classe generica, il codice client Node<T>, può fare riferimento alla classe
specificando un argomento tipo, per creare un tipo costruito chiuso ( Node<int> ). In alternativa, può lasciare il
parametro di tipo non specificato, ad esempio quando si specifica una classe base generica, per creare un tipo
costruito aperto ( Node<T> ). Le classi generiche possono ereditare da classi concrete, classi costruite chiuse o
classi base costruite aperte:
class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type


class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type


class NodeOpen<T> : BaseNodeGeneric<T> { }

Le classi non generiche, ovvero concrete, possono ereditare da classi base costruite chiuse, ma non da classi
costruite aperte o da parametri di tipo, perché in fase di esecuzione il codice client non può in alcun modo
specificare l'argomento tipo necessario per creare un'istanza della classe base.

//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

Le classi generiche che ereditano da tipi costruiti aperti devono specificare gli argomenti tipo per qualsiasi
parametro di tipo di classe base che non viene condiviso dalla classe che eredita, come mostrato nel codice
seguente:

class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

Le classi generiche che ereditano da tipi costruiti aperti devono specificare vincoli che implichino o siano un
superset dei vincoli sul tipo di base:

class NodeItem<T> where T : System.IComparable<T>, new() { }


class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

I tipi generici possono usare più parametri di tipo e vincoli, in questo modo:

class SuperKeyType<K, V, U>


where U : System.IComparable<U>
where V : new()
{ }

I tipo costruiti aperti e i tipi costruiti chiusi possono essere usati come parametri di metodo:
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}

void Swap(List<int> list1, List<int> list2)


{
//code to swap items
}

Se una classe generica implementa un'interfaccia, è possibile eseguire il cast di tutte le istanze della classe
all'interfaccia.
Le classi generiche sono invariabili. In altri termini, se un parametro di input specifica un oggetto
List<BaseClass> , se si prova a specificare un oggetto List<DerivedClass> , viene restituito un errore in fase di
compilazione.

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Generics
Saving the State of Enumerators (Salvataggio dello stato degli enumeratori)
An Inheritance Puzzle, Part One (Indovinello sull'ereditarietà - Parte 1)
Interfacce generiche (Guida per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

Spesso è utile definire interfacce per classi di raccolta generiche o per le classi generiche che rappresentano
elementi nella raccolta. Per le classi generiche è preferibile usare interfacce generiche, ad esempio
IComparable<T> invece di IComparable, per evitare operazioni di conversione boxing e unboxing sui tipi valore.
La libreria di classi .NET definisce diverse interfacce generiche da usare con le classi di raccolta nello
System.Collections.Generic spazio dei nomi.
Quando un'interfaccia viene specificata come vincolo o parametro di tipo, è possibile usare solo i tipi che
implementano l'interfaccia. L'esempio di codice seguente mostra una classe SortedList<T> che deriva dalla
classe GenericList<T> . Per altre informazioni, vedere Introduzione ai generics. SortedList<T> aggiunge il
vincolo where T : IComparable<T> . In questo modo, il metodo BubbleSort in SortedList<T> può usare il
metodo CompareTo generico negli elementi dell'elenco. In questo esempio gli elementi dell'elenco sono una
classe semplice, ovvero Person , che implementa IComparable<Person> .

//Type parameter T in angle brackets.


public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;

// Nested class is also generic on T


protected class Node
{
public Node next;
private T data; //T as private member datatype

public Node(T t) //T used in non-generic constructor


{
next = null;
data = t;
}

public Node Next


{
get { return next; }
set { next = value; }
}

public T Data //T as return type of property


{
get { return data; }
set { data = value; }
}
}

public GenericList() //constructor


{
head = null;
}

public void AddHead(T t) //T as method parameter type


{
Node n = new Node(t);
n.Next = head;
head = n;
}
// Implementation of the iterator
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}

// IEnumerable<T> inherits from IEnumerable, therefore this class


// must implement both the generic and non-generic versions of
// GetEnumerator. In most cases, the non-generic method can
// simply call the generic method.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>


{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:

public void BubbleSort()


{
if (null == head || null == head.Next)
{
return;
}
bool swapped;

do
{
Node previous = null;
Node current = head;
swapped = false;

while (current.next != null)


{
// Because we need to call this method, the SortedList
// class is constrained on IComparable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;

if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}

// A simple class that implements IComparable<T> using itself as the


// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
string name;
int age;

public Person(string s, int i)


{
name = s;
age = i;
}

// This will cause list elements to be sorted on age values.


public int CompareTo(Person p)
{
return age - p.age;
}

public override string ToString()


{
return name + ":" + age;
}

// Must implement Equals.


public bool Equals(Person p)
{
return (this.age == p.age);
}
}

public class Program


{
public static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();

//Create name and age values to initialize Person objects.


string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};

int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };

//Populate the list.


for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}

//Print out unsorted list.


foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");

//Sort the list.


list.BubbleSort();

//Print out sorted list.


foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}

È possibile specificare più interfacce come vincoli su un unico tipo, in questo modo:

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>


{
}

Un'interfaccia può definire più parametri di tipo, in questo modo:

interface IDictionary<K, V>


{
}

Le regole di ereditarietà valide per le classi si applicano anche alle interfacce:

interface IMonth<T> { }

interface IJanuary : IMonth<int> { } //No error


interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U> {} //Error

Le interfacce generiche possono ereditare da interfacce non generiche se l'interfaccia generica è controvariante,
ovvero usa solo il proprio parametro di tipo come valore restituito. Nella libreria di classi .NET IEnumerable<T>
eredita da IEnumerable perché IEnumerable<T> Usa solo T nel valore restituito di GetEnumerator e nel getter
della Current Proprietà.
Le classi concrete possono implementare interfacce costruite chiuse, in questo modo:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Le classi generiche possono implementare interfacce generiche o interfacce costruite chiuse purché l'elenco di
parametri delle classi specifichi tutti gli argomenti necessari per l'interfaccia, in questo modo:

interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { } //No error


class SampleClass2<T> : IBaseInterface2<T, string> { } //No error

Le regole che controllano l'overload dei metodi sono le stesse per i metodi all'interno di classi generiche, struct
generici o interfacce generiche. Per altre informazioni, vedere Metodi generici.

Vedere anche
Guida per programmatori C#
Introduzione ai generics
interface
Generics
Metodi generici (Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Un metodo generico è un metodo che viene dichiarato con parametri di tipo, in questo modo:

static void Swap<T>(ref T lhs, ref T rhs)


{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}

L'esempio di codice seguente mostra un modo per chiamare il metodo usando int per l'argomento tipo:

public static void TestSwap()


{
int a = 1;
int b = 2;

Swap<int>(ref a, ref b);


System.Console.WriteLine(a + " " + b);
}

È anche possibile omettere l'argomento tipo perché venga dedotto dal compilatore. La chiamata seguente a
Swap equivale alla chiamata precedente:

Swap(ref a, ref b);

Ai metodi statici e ai metodi di istanza si applicano le stesse regole relative all'inferenza del tipo. Il compilatore
può dedurre i parametri di tipo in base agli argomenti metodo passati, ma non può dedurli solo da un vincolo o
da un valore restituito. Di conseguenza, l'inferenza del tipo non funziona con metodi che non includono
parametri. L'inferenza del tipo avviene in fase di compilazione prima che il compilatore provi a risolvere le firme
dei metodi di overload. Il compilatore applica la logica di inferenza del tipo a tutti i metodi generici che
condividono lo stesso nome. Nel passaggio di risoluzione dell'overload, il compilatore include solo i metodi
generici per cui l'inferenza del tipo è riuscita.
All'interno di una classe generica metodi non generici possono accedere ai parametri di tipo a livello di classe, in
questo modo:

class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}

Se si definisce un metodo generico che accetta gli stessi parametri di tipo della classe che lo contiene, il
compilatore genera l'avviso CS0693 perché nell'ambito del metodo l'argomento specificato per il parametro T
interno nasconde l'argomento specificato per il parametro T esterno. Se è necessaria la flessibilità di poter
chiamare un metodo di una classe generica con argomenti tipo diversi da quelli specificati alla creazione di
un'istanza della classe, provare a specificare un altro identificatore per il parametro di tipo del metodo, come
mostrato in GenericList2<T> nell'esempio seguente.
class GenericList<T>
{
// CS0693
void SampleMethod<T>() { }
}

class GenericList2<T>
{
//No warning
void SampleMethod<U>() { }
}

Usare vincoli per consentire operazioni più specializzate sui parametri di tipo nei metodi. Questa versione di
Swap<T> , ora chiamata SwapIfGreater<T> , può essere usata solo con argomenti tipo che implementano
IComparable<T>.

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>


{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}

È possibile eseguire l'overload di metodi generici in diversi parametri di tipo. Ad esempio, i metodi seguenti
possono essere contenuti tutti nella stessa classe:

void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#.

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Introduzione ai generics
Metodi
Generics e matrici (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

In C# 2.0 e versioni successive le matrici unidimensionali con limite inferiore pari a zero implementano
automaticamente IList<T>. Questo permette di creare metodi generici che possono usare lo stesso codice per
eseguire l'iterazione di matrici e altri tipi di raccolta. Questa tecnica è particolarmente utile per leggere dati nelle
raccolte. Non è possibile usare l'interfaccia IList<T> per aggiungere o rimuovere elementi in una matrice. Se si
prova a chiamare un metodo IList<T> come RemoveAt in una matrice in questo contesto, viene generata
un'eccezione.
L'esempio di codice seguente mostra in che modo un solo metodo generico che accetta un parametro di input
IList<T> può eseguire l'iterazione sia di un elenco sia di una matrice, in questo caso una matrice di interi.

class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();

for (int x = 5; x < 10; x++)


{
list.Add(x);
}

ProcessItems<int>(arr);
ProcessItems<int>(list);
}

static void ProcessItems<T>(IList<T> coll)


{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);

// The following statement causes a run-time exception for the


// array, but not for the List.
//coll.RemoveAt(4);

foreach (T item in coll)


{
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();
}
}

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Generics
Matrici
Generics
Delegati generici (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Un delegato può definire i propri parametri di tipo. Il codice che fa riferimento al delegato generico può
specificare l'argomento tipo per creare un tipo costruito chiuso, proprio come per la creazione di un'istanza di
una classe generica o la chiamata di un metodo generico, come mostrato nell'esempio seguente:

public delegate void Del<T>(T item);


public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);

C# versione 2.0 include una nuova funzionalità chiamata conversione di gruppi di metodi, applicabile a tipi
delegato sia concreti sia generici, e che permette di scrivere la riga precedente con questa sintassi semplificata:

Del<int> m2 = Notify;

I delegati definiti in una classe generica possono usare parametri di tipo di classe generica allo stesso modo dei
metodi della classe.

class Stack<T>
{
T[] items;
int index;

public delegate void StackDelegate(T[] items);


}

Il codice che fa riferimento al delegato deve specificare l'argomento tipo della classe che lo contiene, in questo
modo:

private static void DoWork(float[] items) { }

public static void TestStack()


{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}

I delegati generici sono particolarmente utili per definire eventi basati sul normale schema progettuale, perché
l'argomento sender può essere fortemente tipizzato e non è più necessario eseguirne il cast a e da Object.
delegate void StackEventHandler<T, U>(T sender, U eventArgs);

class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;

protected virtual void OnStackChanged(StackEventArgs a)


{
stackEvent(this, a);
}
}

class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}

public static void Test()


{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Introduzione ai generics
Metodi generici
Classi generiche
Interfacce generiche
Delegati
Generics
Differenze tra modelli C++ e generics C# (Guida
per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

I generics C# e i modelli C++ sono entrambi funzionalità del linguaggio che offrono il supporto per i tipi con
parametri. Esistono tuttavia numerose differenze tra queste due funzionalità. A livello di sintassi, i generics C#
rappresentano un approccio più semplice ai tipi con parametri, senza la complessità dei modelli C++. Inoltre, il
linguaggio C# non è stato concepito per offrire tutte le funzionalità dei modelli C++. A livello di
implementazione, la differenza principale consiste nel fatto che le sostituzioni di tipi generici C# vengono
eseguite durante il runtime e, di conseguenza, le informazioni sui tipi generici vengono mantenute per gli
oggetti di cui viene creata un'istanza. Per altre informazioni, vedere Generics nel runtime.
Di seguito sono riportate le differenze principali tra generics C# e modelli C++:
I generics C# non offrono lo stesso livello di flessibilità dei modelli C++. Ad esempio non è possibile
chiamare operatori aritmetici in una classe generica C#, anche se è possibile chiamare operatori definiti
dall'utente.
C# non consente di usare parametri di modello non di tipo, ad esempio template C<int i> {} .
C# non supporta la specializzazione esplicita, ovvero un'implementazione personalizzata di un modello
per un tipo specifico.
C# non supporta la specializzazione parziale, ovvero un'implementazione personalizzata per un subset
degli argomenti del tipo.
C# non consente l'uso del parametro di tipo come classe base per il tipo generico.
C# non consente l'uso di tipi predefiniti per i parametri di tipo.
In C# un parametro di tipo generico non può essere di per sé un elemento generico, anche se i tipi
costruiti possono essere usati come generics. C++ non consente l'uso di parametri di modello.
C++ consente di usare codice che potrebbe non essere valido per tutti i parametri di tipo nel modello,
che viene quindi controllato per verificare la disponibilità del tipo specifico usato come parametro di tipo.
In C# il codice di una classe deve essere scritto in modo da funzionare con qualsiasi tipo in grado di
soddisfare i vincoli. Ad esempio, in C++ è possibile scrivere una funzione che usa gli operatori aritmetici
+ e - sugli oggetti del parametro di tipo, generando un errore al momento della creazione di
un'istanza del modello con un tipo che non supporta gli operatori. In C# questo non è possibile. Gli unici
costrutti di linguaggio ammessi sono quelli deducibili dai vincoli.

Vedere anche
Guida per programmatori C#
Introduzione ai generics
Modelli
Generics nel runtime (Guida per programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

Quando un tipo o un metodo generico viene compilato in Microsoft Intermediate Language (MSIL), contiene
metadati che lo identificano come contenente parametri di tipo. L'uso di MSIL per un tipo generico differisce a
seconda che il parametro di tipo sia un tipo valore o un tipo riferimento.
La prima volta che viene costruito un tipo generico con un tipo valore come parametro, il runtime crea un tipo
generico specializzato con il parametro o i parametri specificati sostituiti nelle posizioni appropriate in MSIL. I
tipi generici specializzati vengono creati una sola volta per ogni tipo valore univoco usato come parametro.
Ad esempio, si supponga che il codice programma abbia dichiarato uno stack costituito da interi:

Stack<int> stack;

A questo punto, il runtime genera una versione specializzata della classe Stack<T> sostituendo l'intero nel modo
appropriato per il parametro. D'ora in poi, ogni volta che il codice programma usa uno stack di interi, il runtime
riutilizzerà la classe Stack<T> specializzata generata. Nell'esempio seguente vengono create due istanze di uno
stack di interi che condividono un'istanza singola nel codice Stack<int> :

Stack<int> stackOne = new Stack<int>();


Stack<int> stackTwo = new Stack<int>();

Tuttavia, si supponga che venga creata un'altra classe Stack<T> con un tipo valore diverso, ad esempio long , o
una struttura definita dall'utente come parametro in un altro punto del codice. Di conseguenza, il runtime
genererà un'altra versione del tipo generico e sostituirà un valore long nelle posizioni appropriate in MSIL. Le
conversioni non sono più necessarie, perché ogni classe generica specializzata contiene il tipo valore in modo
nativo.
I generics hanno un funzionamento leggermente diverso con i tipi riferimento. La prima volta che viene
costruito un tipo generico con qualsiasi tipo riferimento, il runtime crea un tipo generico specializzato con
riferimenti a oggetti sostituiti per i parametri in MSIL. Quindi, ogni volta che viene creata un'istanza di un tipo
costruito con un tipo riferimento come parametro, indipendentemente dal tipo, il runtime riutilizza la versione
specializzata precedentemente creata del tipo generico. Questo è possibile perché tutti i riferimenti hanno le
stesse dimensioni.
Ad esempio, si supponga di avere due tipi riferimento, una classe Customer e una classe Order , e di aver creato
uno stack di tipi Customer :

class Customer { }
class Order { }

Stack<Customer> customers;

A questo punto, il runtime genera una versione specializzata della classe Stack<T> in cui sono archiviati
riferimenti a oggetti che verranno compilati successivamente invece di archiviare i dati. Si supponga che la riga
di codice successiva crei uno stack di un altro tipo riferimento, chiamato Order :
Stack<Order> orders = new Stack<Order>();

Diversamente dai tipi valore, non viene creata un'altra versione specializzata della classe Stack<T> per il tipo
Order . Viene invece creata un'istanza della versione specializzata della classe Stack<T> e viene impostata la
variabile orders per referenziarla. Si supponga di individuare una riga di codice per la creazione di uno stack di
un tipo Customer :

customers = new Stack<Customer>();

Come per l'uso precedente della classe Stack<T> creata con il tipo Order , verrà creata un'altra istanza della
classe Stack<T> specializzata. I puntatori contenuti vengono impostati in modo da fare riferimento a un'area di
memoria delle dimensioni di un tipo Customer . Poiché il numero di tipi riferimento può variare ampiamente a
seconda del programma, l'implementazione C# di generics riduce notevolmente la quantità di codice limitando
a uno il numero di classi specializzate create dal compilatore per classi generiche di tipi riferimento.
Inoltre, quando viene creata un'istanza di una classe C# generica usando un tipo valore o un parametro di tipo
riferimento, la reflection può eseguire query sulla classe, accertandone il tipo effettivo e il parametro di tipo.

Vedere anche
System.Collections.Generic
Guida per programmatori C#
Introduzione ai generics
Generics
Generics e reflection (Guida per programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

Poiché Common Language Runtime (CLR) ha accesso alle informazioni sui tipi generici in fase di esecuzione, è
possibile usare il processo di reflection per ottenere informazioni sui tipi generici, analogamente a quanto
avviene per i tipi non generici. Per altre informazioni, vedere Generics nel runtime.
In .NET Framework 2,0 sono stati aggiunti diversi nuovi membri alla Type classe per abilitare le informazioni di
run-time per i tipi generici. Vedere la documentazione su queste classi per altre informazioni su come usare tali
metodi e proprietà. Anche lo spazio dei nomi System.Reflection.Emit contiene nuovi membri che supportano i
generics. Vedere Procedura: Definire un tipo generico tramite reflection emit.
Per un elenco delle condizioni invariabili relative ai termini usati dal processo di reflection generico, vedere i
commenti sulla proprietà IsGenericType.

N O M E DEL M EM B RO SY ST EM . T Y P E DESC RIZ IO N E

IsGenericType Restituisce true se un tipo è generico.

GetGenericArguments Restituisce una matrice di oggetti Type che rappresentano


gli argomenti di tipo specificati per un tipo costruito oppure i
parametri di tipo di una definizione di un tipo generico.

GetGenericTypeDefinition Restituisce la definizione di un tipo generico sottostante per


il tipo costruito corrente.

GetGenericParameterConstraints Restituisce una matrice di oggetti Type che rappresentano i


vincoli sul parametro di tipo generico corrente.

ContainsGenericParameters Restituisce true se il tipo o uno dei tipi o dei metodi che lo
contengono comprendono parametri di tipo per cui non
sono stati indicati tipi specifici.

GenericParameterAttributes Ottiene una combinazione di flag


GenericParameterAttributes che descrivono i vincoli
speciali del parametro di tipo generico corrente.

GenericParameterPosition Per un oggetto Type che rappresenta un parametro di tipo,


ottiene la posizione del parametro di tipo nell'elenco dei
parametri di tipo della definizione di un tipo generico o della
definizione di un metodo generico che ha dichiarato il
parametro di tipo.

IsGenericParameter Ottiene un valore che indica se l'oggetto Type corrente


rappresenta un parametro di tipo di una definizione di un
tipo o metodo generico.

IsGenericTypeDefinition Ottiene un valore che indica se la classe Type corrente


rappresenta una definizione di tipo generico, da cui è
possibile costruire altri tipi generici. Restituisce true se il tipo
rappresenta la definizione di un tipo generico.
N O M E DEL M EM B RO SY ST EM . T Y P E DESC RIZ IO N E

DeclaringMethod Restituisce il metodo generico che ha definito il parametro di


tipo generico corrente oppure Null se il parametro di tipo
non è stato definito da un metodo generico.

MakeGenericType Sostituisce gli elementi di una matrice di tipi ai parametri di


tipo della definizione di tipo generico corrente e restituisce
un oggetto Type che rappresenta il tipo costruito risultante.

Inoltre, i membri della classe MethodInfo abilitano le informazioni di runtime per i tipi generici. Per un elenco
delle condizioni invariabili relative ai termini usati dal processo di reflection per i metodi generici, vedere le note
sulla proprietà IsGenericMethod.

SY ST EM . REF L EC T IO N . M EM B ERIN F O M EM B ER N A M E DESC RIZ IO N E

IsGenericMethod Restituisce true se un metodo è generico.

GetGenericArguments Restituisce una matrice di oggetti Type che rappresentano gli


argomenti di tipo di un metodo generico costruito oppure i
parametri di tipo di una definizione di un metodo generico.

GetGenericMethodDefinition Restituisce la definizione di un metodo generico sottostante


per il metodo costruito corrente.

ContainsGenericParameters Restituisce true se il metodo o uno dei tipi che lo


contengono comprendono parametri di tipo per cui non
sono stati indicati tipi specifici.

IsGenericMethodDefinition Restituisce true se l'oggetto MethodInfo corrente


rappresenta la definizione di un metodo generico.

MakeGenericMethod Sostituisce con gli elementi di una matrice di tipi i parametri


di tipo della definizione di metodo generica corrente e
restituisce un oggetto MethodInfo che rappresenta il
metodo costruito risultante.

Vedere anche
Guida per programmatori C#
Generics
Reflection e tipi generici
Generics
Generics e attributi (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Gli attributi possono essere applicati a tipi generici allo stesso modo che ai tipi non generici. Per altre
informazioni sull'applicazione di attributi, vedere Attributi.
Gli attributi personalizzati sono consentiti solo per fare riferimento a tipi generici aperti, che sono tipi generici
per cui non è specificato alcun argomento tipo, e tipi generici costruiti chiusi, che specificano argomenti per tutti
i parametri di tipo.
Gli esempi seguenti usano questo attributo personalizzato:

class CustomAttribute : System.Attribute


{
public System.Object info;
}

Un attributo può fare riferimento a un tipo generico aperto:

public class GenericClass1<T> { }

[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }

Specificare più parametri di tipo usando il numero appropriato di virgole. In questo esempio GenericClass2
include due parametri di tipo:

public class GenericClass2<T, U> { }

[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }

Un attributo può fare riferimento a un tipo generico costruito chiuso:

public class GenericClass3<T, U, V> { }

[CustomAttribute(info = typeof(GenericClass3<int, double, string>))]


class ClassC { }

Un attributo che fa riferimento a un parametro di tipo generico provoca un errore in fase di compilazione:

//[CustomAttribute(info = typeof(GenericClass3<int, T, string>))] //Error


class ClassD<T> { }

Un tipo generico non può ereditare da Attribute:

//public class CustomAtt<T> : System.Attribute {} //Error

Per ottenere informazioni su un tipo generico o un parametro di tipo in fase di esecuzione, è possibile usare i
metodi di System.Reflection. Per altre informazioni, vedere Generics e reflection
Vedere anche
Guida per programmatori C#
Generics
Attributes (Attributi)
Spazi dei nomi (Guida per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Gli spazi dei nomi vengono usati frequentemente nella programmazione C# in due modi. In primo luogo, .NET
usa gli spazi dei nomi per organizzare le numerose classi, come indicato di seguito:

System.Console.WriteLine("Hello World!");

System è uno spazio dei nomi e Console è una classe in quello spazio dei nomi. Si può usare la parola chiave
using in modo tale che il nome completo non sia necessario, come nell'esempio seguente:

using System;

Console.WriteLine("Hello World!");

Per altre informazioni, vedere la direttiva using.


Secondo, dichiarando i propri spazi dei nomi è possibile controllare l'ambito dei nomi di classi e metodi nei
progetti di programmazione più grandi. Usare la parola chiave namespace per dichiarare uno spazio dei nomi,
come nell'esempio seguente:

namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}

Il nome dello spazio dei nomi deve essere un nome di identificatore C# valido.

Panoramica degli spazi dei nomi


Gli spazi dei nomi hanno le proprietà riportate di seguito:
Consentono di organizzare progetti di codice di grandi dimensioni.
Vengono delimitati usando l'operatore . .
La direttiva using elimina la necessità di specificare il nome dello spazio dei nomi per ogni classe.
Lo spazio dei nomi global è lo spazio dei nomi "radice": global::System farà sempre riferimento allo spazio
dei nomi System di .NET.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Spazi dei nomi della specifica del linguaggio C#.
Vedere anche
Guida per programmatori C#
Uso degli spazi dei nomi
Come usare lo spazio dei nomi My
Nomi di identificatore
Direttiva using
Operatore::
Uso degli spazi dei nomi (Guida per programmatori
C#)
02/11/2020 • 7 minutes to read • Edit Online

Gli spazi dei nomi vengono usati frequentemente nei programmi C# in due modi. In primo luogo, le classi .NET
usano gli spazi dei nomi per organizzare le numerose classi. In secondo luogo, dichiarando i propri spazi dei
nomi è possibile controllare l'ambito dei nomi di classi e metodi nei progetti di programmazione più grandi.

Accesso agli spazi dei nomi


La maggior parte delle applicazioni C# iniziano con una sezione di direttive using . In questa sezione sono
elencati gli spazi dei nomi usati di frequente dall'applicazione, evitando al programmatore di specificare un
nome completo ogni volta che viene usato un metodo contenuto negli spazi dei nomi.
Ad esempio, includendo la riga:

using System;

All'inizio di un programma, il programmatore può usare il codice:

Console.WriteLine("Hello, World!");

Anziché:

System.Console.WriteLine("Hello, World!");

Alias dello spazio dei nomi


È anche possibile usare la using direttiva per creare un alias per uno spazio dei nomi. Usare il qualificatore di
alias dello spazio dei nomi :: per accedere ai membri dello spazio dei nomi con alias. Nell'esempio seguente
viene illustrato come creare e usare un alias dello spazio dei nomi:
using generics = System.Collections.Generic;

namespace AliasExample
{
class TestClass
{
static void Main()
{
generics::Dictionary<string, int> dict = new generics::Dictionary<string, int>()
{
["A"] = 1,
["B"] = 2,
["C"] = 3
};

foreach (string name in dict.Keys)


{
System.Console.WriteLine($"{name} {dict[name]}");
}
// Output:
// A 1
// B 2
// C 3
}
}
}

Uso degli spazi dei nomi per controllare l'ambito


La parola chiave namespace viene usata per dichiarare un ambito. La possibilità di creare ambiti all'interno del
progetto consente di organizzare il codice e di creare tipi univoci globali. Nell'esempio seguente, una classe
denominata SampleClass è definita in due spazi dei nomi, uno annidato all'interno dell'altro. Il . token viene
usato per distinguere il metodo che viene chiamato.
namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}

// Create a nested namespace, and define another class.


namespace NestedNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside NestedNamespace");
}
}
}

class Program
{
static void Main(string[] args)
{
// Displays "SampleMethod inside SampleNamespace."
SampleClass outer = new SampleClass();
outer.SampleMethod();

// Displays "SampleMethod inside SampleNamespace."


SampleNamespace.SampleClass outer2 = new SampleNamespace.SampleClass();
outer2.SampleMethod();

// Displays "SampleMethod inside NestedNamespace."


NestedNamespace.SampleClass inner = new NestedNamespace.SampleClass();
inner.SampleMethod();
}
}
}

Nomi completi
Spazi dei nomi e tipi hanno nomi univoci, descritti da nomi completi che indicano una gerarchia logica. Ad
esempio, l'istruzione A.B implica che A è il nome dello spazio dei nomi o del tipo e B è annidato al suo
interno.
Nell'esempio seguente sono presenti classi e spazi dei nomi annidati. Il nome completo è indicato come
commento dopo ogni entità.
namespace N1 // N1
{
class C1 // N1.C1
{
class C2 // N1.C1.C2
{
}
}
namespace N2 // N1.N2
{
class C2 // N1.N2.C2
{
}
}
}

Nel segmento di codice precedente:


Lo spazio dei nomi N1 è un membro dello spazio dei nomi globale. Il relativo nome completo è N1 .
Lo spazio dei nomi N2 è un membro di N1 . Il relativo nome completo è N1.N2 .
La classe C1 è un membro di N1 . Il relativo nome completo è N1.C1 .
Il nome della classe C2 viene usato due volte in questo codice. Tuttavia, i nomi completi sono univoci. La
prima istanza di C2 è dichiarata all'interno di C1 , pertanto il relativo nome completo è: N1.C1.C2 . La
seconda istanza di C2 è dichiarata all'interno di uno spazio dei nomi N2 , pertanto il relativo nome
completo è N1.N2.C2 .

Usando il segmento di codice precedente, è possibile aggiungere un nuovo membro della classe, C3 , allo
spazio dei nomi N1.N2 come indicato di seguito:

namespace N1.N2
{
class C3 // N1.N2.C3
{
}
}

In generale, usare il qualificatore di alias dello spazio dei nomi :: per fare riferimento a un alias dello spazio
dei nomi oppure global:: per fare riferimento allo spazio dei nomi globale e . per qualificare i tipi o i
membri.
È un errore usare :: con un alias che fa riferimento a un tipo, anziché a uno spazio dei nomi. Ad esempio:

using Alias = System.Console;

class TestClass
{
static void Main()
{
// Error
//Alias::WriteLine("Hi");

// OK
Alias.WriteLine("Hi");
}
}
Tenere presente che la parola global non è un alias predefinito, pertanto global.X non ha alcun significato
speciale. Acquisisce un significato speciale solo quando viene usata con :: .
Se si definisce un alias denominato global, viene generato l'avviso del compilatore CS0440, perché global:: fa
sempre riferimento allo spazio dei nomi globale e non a un alias. Ad esempio, la riga seguente genera l'avviso:

using global = System.Collections; // Warning

L'uso di :: con gli alias è consigliabile e garantisce la protezione contro l'introduzione imprevista di tipi
aggiuntivi. Si prenda, ad esempio, in considerazione quanto segue:

using Alias = System;

namespace Library
{
public class C : Alias::Exception { }
}

Questo metodo funziona, ma se un tipo denominato Alias dovesse essere introdotto successivamente, Alias.
sarebbe associato a tale tipo. L'uso di Alias::Exception assicura che Alias sia trattato come un alias di spazio
dei nomi e non venga erroneamente considerato un tipo.

Vedere anche
Guida per programmatori C#
Spazi dei nomi
Espressione di accesso ai membri
:: (operatore)
extern alias
Come usare lo spazio dei nomi My (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Lo Microsoft.VisualBasic.MyServices spazio dei nomi ( My in Visual Basic) consente di accedere in modo


semplice e intuitivo a numerose classi .NET, consentendo di scrivere codice che interagisce con il computer,
l'applicazione, le impostazioni, le risorse e così via. Anche se originariamente progettato per l'uso con Visual
Basic, lo spazio dei nomi MyServices può essere usato nelle applicazioni C#.
Per altre informazioni sull'uso dello spazio dei nomi MyServices da Visual Basic, vedere Development with My
(Sviluppo con My).

Aggiungere un riferimento
Prima di poter usare le classi MyServices nella soluzione, è necessario aggiungere un riferimento alla libreria di
Visual Basic.
Aggiungere un riferimento alla libreria Visual Basic
1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo riferimenti e selezionare
Aggiungi riferimento .
2. Nella finestra di dialogo Riferimenti visualizzata scorrere l'elenco e selezionare Microsoft.VisualBasic.dll.
È anche possibile includere la riga seguente nella sezione using all'inizio del programma.

using Microsoft.VisualBasic.Devices;

Esempio
Questo esempio chiama diversi metodi statici contenuti nello spazio dei nomi MyServices . Per compilare questo
codice, è necessario aggiungere al progetto un riferimento a Microsoft.VisualBasic.DLL.
using System;
using Microsoft.VisualBasic.Devices;

class TestMyServices
{
static void Main()
{
// Play a sound with the Audio class:
Audio myAudio = new Audio();
Console.WriteLine("Playing sound...");
myAudio.Play(@"c:\WINDOWS\Media\chimes.wav");

// Display time information with the Clock class:


Clock myClock = new Clock();
Console.Write("Current day of the week: ");
Console.WriteLine(myClock.LocalTime.DayOfWeek);
Console.Write("Current date and time: ");
Console.WriteLine(myClock.LocalTime);

// Display machine information with the Computer class:


Computer myComputer = new Computer();
Console.WriteLine("Computer name: " + myComputer.Name);

if (myComputer.Network.IsAvailable)
{
Console.WriteLine("Computer is connected to network.");
}
else
{
Console.WriteLine("Computer is not connected to network.");
}
}
}

Non tutte le classi incluse nello spazio dei nomi MyServices possono essere chiamate da un'applicazione C#. La
classe FileSystemProxy, ad esempio, non è compatibile. In questo caso specifico è possibile usare i metodi statici
inclusi in FileSystem e contenuti anche nel file VisualBasic.dll. Ecco ad esempio come usare uno di questi metodi
per duplicare una directory:

// Duplicate a directory
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(
@"C:\original_directory",
@"C:\copy_of_original_directory");

Vedere anche
Guida per programmatori C#
Namespaces (Spazi dei nomi)
Uso degli spazi dei nomi
Codice non gestito e puntatori (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Per mantenere l'indipendenza dai tipi e la sicurezza, C# non supporta il puntatore aritmetico per impostazione
predefinita. Tuttavia, se si usa la parola chiave unsafe, è possibile definire un contesto non sicuro in cui si
possono usare i puntatori. Per altre informazioni sui puntatori, vedere Tipi puntatore.

NOTE
Nel supporto Common Language Runtime (CLR) il codice di tipo unsafe è detto codice non verificabile. Il codice unsafe in
C# non è necessariamente pericoloso, è solo codice di cui il supporto CLR non può verificare la sicurezza. CLR pertanto
eseguirà il codice unsafe solo se si trova in un assembly completamente attendibile. Se si usa codice unsafe, è
responsabilità dell'utente verificare che il codice non introduca rischi per la sicurezza o errori dei puntatori.

Panoramica del codice non gestito


Il codice unsafe presenta le proprietà seguenti:
Metodi, tipi e blocchi di codice possono essere definiti come unsafe.
In alcuni casi il codice unsafe può migliorare le prestazioni di un'applicazione poiché vengono rimosse le
verifiche dei limiti di matrice.
Il codice unsafe è necessario quando si chiamano funzioni native che richiedono i puntatori.
L'uso del codice unsafe implica rischi per la sicurezza e la stabilità.
Il codice che contiene blocchi non gestiti deve essere compilato con l'opzione -unsafe del compilatore.

Sezioni correlate
Per altre informazioni, vedere:
Tipi puntatore
Buffer a dimensione fissa

Specifiche del linguaggio C#


Per altre informazioni, vedere l'argomento Codice di tipo unsafe nella specifica del linguaggio C#.

Vedere anche
Guida per programmatori C#
unsafe
Buffer a dimensione fissa (Guida per programmatori
C#)
02/11/2020 • 5 minutes to read • Edit Online

In C# è possibile usare l'istruzione fixed per creare un buffer con una matrice di dimensioni fisse in una struttura
di dati. I buffer a dimensione fissa sono utili quando si scrivono metodi con interoperabilità con origini dati di
altri linguaggi o piattaforme. La matrice fissa può accettare attributi o modificatori consentiti per i membri struct
normali. L'unica restrizione è rappresentata dal fatto che il tipo di matrice deve essere bool , byte , char ,
short , int , long , sbyte , ushort , uint , ulong , float o double .

private fixed char name[30];

Commenti
Nel codice safe, uno struct C# che contiene una matrice non contiene gli elementi della matrice. Lo struct
contiene invece un riferimento agli elementi. È possibile incorporare una matrice di dimensioni fisse in uno
struct quando viene usata in un blocco di codice unsafe.
Le dimensioni del codice seguente struct non dipendono dal numero di elementi nella matrice, poiché
pathName è un riferimento:

public struct PathArray


{
public char[] pathName;
private int reserved;
}

Un oggetto struct può contenere una matrice incorporata in codice unsafe. Nell'esempio seguente la matrice
fixedBuffer è di dimensioni fisse. Viene usata un'istruzione fixed per definire un puntatore al primo
elemento. Gli elementi della matrice sono accessibili tramite il puntatore. L'istruzione fixed blocca un'istanza di
fixedBuffer in un percorso specifico nella memoria.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}

internal unsafe class Example


{
public Buffer buffer = default;
}

private static void AccessEmbeddedArray()


{
var example = new Example();

unsafe
{
// Pin the buffer to a fixed location in memory.
fixed (char* charPtr = example.buffer.fixedBuffer)
{
*charPtr = 'A';
}
// Access safely through the index:
char c = example.buffer.fixedBuffer[0];
Console.WriteLine(c);

// Modify through the index:


example.buffer.fixedBuffer[0] = 'B';
Console.WriteLine(example.buffer.fixedBuffer[0]);
}
}

Le dimensioni della matrice char a 128 elementi sono di 256 byte. I buffer char a dimensione fissa accettano
sempre due byte per carattere, indipendentemente dalla codifica. Questo vale anche quando viene eseguito il
marshalling di buffer char in metodi API o struct con CharSet = CharSet.Auto o CharSet = CharSet.Ansi . Per
altre informazioni, vedere CharSet.
L'esempio precedente mostra l'accesso ai campi fixed senza blocco, opzione disponibile a partire da C# 7.3.
Un'altra matrice a dimensione fissa comune è la matrice bool. Gli elementi in una matrice bool hanno sempre
le dimensioni di un byte. Le matrici bool non sono adatte per la creazione di matrici o buffer di bit.
I buffer a dimensione fissa vengono compilati con l'oggetto
System.Runtime.CompilerServices.UnsafeValueTypeAttribute , che indica al Common Language Runtime (CLR)
che un tipo contiene una matrice non gestita che può causare un overflow. Questa operazione è simile alla
memoria creata con stackalloc, che abilita automaticamente le funzionalità di rilevamento del sovraccarico del
buffer in CLR. Nell'esempio precedente viene illustrato come potrebbe esistere un buffer a dimensione fissa in
un oggetto unsafe struct .

internal unsafe struct Buffer


{
public fixed char fixedBuffer[128];
}

Il compilatore genera C# per Buffer , è attribuito come segue:


internal struct Buffer
{
[StructLayout(LayoutKind.Sequential, Size = 256)]
[CompilerGenerated]
[UnsafeValueType]
public struct <fixedBuffer>e__FixedBuffer
{
public char FixedElementField;
}

[FixedBuffer(typeof(char), 128)]
public <fixedBuffer>e__FixedBuffer fixedBuffer;
}

I buffer a dimensione fissa differiscono dalle matrici normali nei modi seguenti:
Può essere utilizzato solo in un contesto unsafe .
Può essere solo campi di istanza di struct.
Sono sempre vettori o matrici unidimensionali.
La dichiarazione deve includere la lunghezza, ad esempio fixed char id[8] . Non è possibile usare
fixed char id[] .

Vedere anche
Guida per programmatori C#
Codice unsafe e puntatori
Istruzione fixed
Interoperabilità
Tipi di puntatori (Guida per programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

In un contesto unsafe, un tipo può essere un tipo di puntatore, un tipo di valore o un tipo di riferimento. La
dichiarazione di un tipo di puntatore può assumere uno dei seguenti formati:

type* identifier;
void* identifier; //allowed but not recommended

Il tipo specificato prima di * in un tipo di puntatore viene chiamato tipo referente . Solo un tipo non gestito
può essere un tipo referente.
I tipi di puntatore non ereditano da object. Non sono inoltre previste conversioni tra i tipi di puntatore e object .
Con i puntatori non sono inoltre supportate le operazioni di boxing e unboxing. È tuttavia possibile eseguire
conversioni tra tipi di puntatore diversi e tra tipi di puntatore e tipi integrali.
Quando si dichiarano più puntatori nella stessa dichiarazione, l'asterisco (*) viene scritto solo con il tipo
sottostante. Non viene utilizzato come prefisso di ogni nome di puntatore. Ad esempio:

int* p1, p2, p3; // Ok


int *p1, *p2, *p3; // Invalid in C#

Un puntatore non può puntare a un riferimento o a uno struct che contiene riferimenti, perché un riferimento a
un oggetto può essere sottoposto a processi di Garbage Collection anche se un puntatore punta a esso. Il
Garbage Collector non tiene traccia degli altri tipi di puntatore che puntano all'oggetto.
Il valore della variabile del puntatore di tipo myType* è l'indirizzo di una variabile di tipo myType . Di seguito
sono riportati alcuni esempi di dichiarazioni di tipi di puntatore:

ESEM P IO DESC RIZ IO N E

int* p p è un puntatore a un Integer.

int** p p è un puntatore a un puntatore a un Integer.

int*[] p p è una matrice unidimensionale di puntatori a Integer.

char* p p è un puntatore a un carattere.

void* p p è un puntatore a un tipo sconosciuto.

Per accedere al contenuto nella posizione a cui punta la variabile del puntatore, è possibile utilizzare *, ovvero
l'operatore di riferimento indiretto a puntatore. Si consideri ad esempio la seguente dichiarazione:

int* myVariable;

L'espressione *myVariable indica la variabile int individuata all'indirizzo contenuto in myVariable .


Gli argomenti Istruzione fixed e Conversioni di puntatori includono diversi esempi di puntatori. L'esempio
seguente usa la parola chiave unsafe e l'istruzione fixed e mostra come incrementare un puntatore interno. È
possibile incollare il codice nella funzione Main di un'applicazione console per eseguirlo. Questi esempi devono
essere compilati con il set di opzioni del compilatore -unsafe.

// Normal pointer to an object.


int[] a = new int[5] { 10, 20, 30, 40, 50 };
// Must be in unsafe code to use interior pointers.
unsafe
{
// Must pin object on heap so that it doesn't move while using interior pointers.
fixed (int* p = &a[0])
{
// p is pinned as well as object, so create another pointer to show incrementing it.
int* p2 = p;
Console.WriteLine(*p2);
// Incrementing p2 bumps the pointer by four bytes due to its type ...
p2 += 1;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
Console.WriteLine("--------");
Console.WriteLine(*p);
// Dereferencing p and incrementing changes the value of a[0] ...
*p += 1;
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
}
}

Console.WriteLine("--------");
Console.WriteLine(a[0]);

/*
Output:
10
20
30
--------
10
11
12
--------
12
*/

Non è possibile applicare l'operatore di riferimento indiretto a un puntatore di tipo void* . È tuttavia possibile
eseguire un cast per convertire un puntatore void in qualsiasi altro tipo e viceversa.
Un puntatore può essere null . Se l'operatore di riferimento indiretto viene applicato a un puntatore Null, si
otterrà un comportamento definito dall'implementazione.
Tenere presente che il passaggio di puntatori tra metodi può generare un comportamento non definito.
Prendere in considerazione un metodo che restituisce un puntatore a una variabile locale tramite un parametro
in , out o ref oppure come risultato della funzione. Se il puntatore è stato impostato in un blocco fisso, la
variabile a cui punta potrebbe non essere più fissa.
Nella tabella riportata di seguito sono elencati gli operatori e le istruzioni che è possibile utilizzare con i
puntatori in un contesto unsafe:
O P ERATO RE/ IST RUZ IO N E USO

* Esegue il riferimento indiretto al puntatore.

-> Accede a un membro di struct tramite un puntatore.

[] Indicizza un puntatore.

& Ottiene l'indirizzo di una variabile.

++ e -- Incrementa e decrementa puntatori.

+ e - Utilizza l'aritmetica dei puntatori.

== , != , < , > , <= e >= Confronta puntatori.

stackalloc Alloca memoria nello stack.

fixed istruzione Corregge temporaneamente una variabile per consentire di


trovarne l'indirizzo.

Per altre informazioni sugli operatori correlati ai puntatori, vedere Operatori correlati ai puntatori.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Tipi puntatore nella specifica del linguaggio C#.

Vedere anche
Guida per programmatori C#
Codice unsafe e puntatori
Conversioni di puntatori
Tipi riferimento
Tipi valore
unsafe
Conversioni di puntatori (Guida per programmatori
C#)
02/11/2020 • 2 minutes to read • Edit Online

Nella tabella seguente sono illustrate le conversioni di puntatori implicite predefinite. Le conversioni implicite
possono avere luogo in numerose situazioni, ad esempio le chiamate di metodi e le istruzioni di assegnazione.

Conversioni di puntatori implicite


F RO M A

Qualsiasi tipo di puntatore void*

Null Qualsiasi tipo di puntatore

La conversione di puntatori esplicita consente di usare un'espressione cast per eseguire conversioni nei casi in
cui non è possibile la conversione implicita. La tabella seguente illustra queste conversioni.

Conversioni di puntatori esplicite


F RO M A

Qualsiasi tipo di puntatore Qualsiasi altro tipo di puntatore

sbyte, byte, short, ushort, int, uint, long o ulong Qualsiasi tipo di puntatore

Qualsiasi tipo di puntatore sbyte, byte, short, ushort, int, uint, long o ulong

Esempio
Nell'esempio seguente, un puntatore a int viene convertito in un puntatore a byte . Osservare come il
puntatore punti al byte della variabile con l'indirizzo più basso. Quando si incrementa successivamente il
risultato, fino a raggiungere la dimensione di int (4 byte), è possibile visualizzare i byte rimanenti della
variabile.

// compile with: -unsafe


class ClassConvert
{
static void Main()
{
int number = 1024;

unsafe
{
// Convert to byte:
byte* p = (byte*)&number;

System.Console.Write("The 4 bytes of the integer:");

// Display the 4 bytes of the int variable:


for (int i = 0 ; i < sizeof(int) ; ++i)
{
System.Console.Write(" {0:X2}", *p);
// Increment the pointer:
p++;
}
System.Console.WriteLine();
System.Console.WriteLine("The value of the integer: {0}", number);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
/* Output:
The 4 bytes of the integer: 00 04 00 00
The value of the integer: 1024
*/

Vedere anche
Guida per programmatori C#
Tipi puntatore
Tipi riferimento
Tipi valore
unsafe
Istruzione fixed
stackalloc
Come usare i puntatori per copiare una matrice di
byte (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Nell'esempio seguente vengono usati i puntatori per copiare i byte da una matrice a un'altra.
In questo esempio viene usata la parola chiave unsafe, che consente l'uso di puntatori nel metodo Copy . Per
dichiarare i puntatori nelle matrici di origine e destinazione, viene usata l'istruzione fixed, L'istruzione fixed
blocca la posizione delle matrici di origine e destinazione nella memoria in modo che non vengano rimosse da
Garbage Collection. I blocchi di memoria per le matrici vengono rimossi quando il blocco fixed è completato. Il
metodo Copy in questo esempio usa la parola chiave unsafe . Deve pertanto essere compilato con l'opzione del
compilatore -unsafe.
L'esempio accede agli elementi di entrambe le matrici usando gli indici invece di un secondo puntatore non
gestito. La dichiarazione dei puntatori pSource e pTarget blocca le matrici. Questa funzionalità è disponibile a
partire da C# 7.3.

Esempio
static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
int targetOffset, int count)
{
// If either array is not instantiated, you cannot complete the copy.
if ((source == null) || (target == null))
{
throw new System.ArgumentException();
}

// If either offset, or the number of bytes to copy, is negative, you


// cannot complete the copy.
if ((sourceOffset < 0) || (targetOffset < 0) || (count < 0))
{
throw new System.ArgumentException();
}

// If the number of bytes from the offset to the end of the array is
// less than the number of bytes you want to copy, you cannot complete
// the copy.
if ((source.Length - sourceOffset < count) ||
(target.Length - targetOffset < count))
{
throw new System.ArgumentException();
}

// The following fixed statement pins the location of the source and
// target objects in memory so that they will not be moved by garbage
// collection.
fixed (byte* pSource = source, pTarget = target)
{
// Copy the specified number of bytes from source to target.
for (int i = 0; i < count; i++)
{
pTarget[targetOffset + i] = pSource[sourceOffset + i];
}
}
}

static void UnsafeCopyArrays()


static void UnsafeCopyArrays()
{
// Create two arrays of the same length.
int length = 100;
byte[] byteArray1 = new byte[length];
byte[] byteArray2 = new byte[length];

// Fill byteArray1 with 0 - 99.


for (int i = 0; i < length; ++i)
{
byteArray1[i] = (byte)i;
}

// Display the first 10 elements in byteArray1.


System.Console.WriteLine("The first 10 elements of the original are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray1[i] + " ");
}
System.Console.WriteLine("\n");

// Copy the contents of byteArray1 to byteArray2.


Copy(byteArray1, 0, byteArray2, 0, length);

// Display the first 10 elements in the copy, byteArray2.


System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");

// Copy the contents of the last 10 elements of byteArray1 to the


// beginning of byteArray2.
// The offset specifies where the copying begins in the source array.
int offset = length - 10;
Copy(byteArray1, offset, byteArray2, 0, length - offset);

// Display the first 10 elements in the copy, byteArray2.


System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");
/* Output:
The first 10 elements of the original are:
0 1 2 3 4 5 6 7 8 9

The first 10 elements of the copy are:


0 1 2 3 4 5 6 7 8 9

The first 10 elements of the copy are:


90 91 92 93 94 95 96 97 98 99
*/
}

Vedere anche
Guida per programmatori C#
Codice unsafe e puntatori
-unsafe (opzioni del compilatore C#)
Garbage Collection
Commenti in formato documentazione XML (Guida
per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

In C# è possibile creare la documentazione per il codice includendo elementi XML in campi di commento
speciali (indicati da barre triple) nel codice sorgente direttamente prima del blocco di codice a cui fanno
riferimento i commenti, ad esempio.

/// <summary>
/// This class performs an important function.
/// </summary>
public class MyClass {}

Quando si esegue la compilazione con l'opzione -doc , il compilatore cerca tutti i tag XML nel codice sorgente e
crea un file di documentazione XML. Per creare la documentazione finale basata sul file generato dal
compilatore, è possibile creare uno strumento personalizzato o usare uno strumento come DocFX o Sandcastle.
Per fare riferimento agli elementi XML (ad esempio, la funzione elabora elementi XML specifici che si desidera
descrivere in un commento della documentazione XML), è possibile utilizzare il meccanismo standard ( < e > ).
Per fare riferimento agli identificatori generici in elementi di riferimento di codice ( cref ), è possibile usare
caratteri di escape, ad esempio cref="List&lt;T&gt;" , o parentesi graffe ( cref="List{T}" ). Come caso
particolare, il compilatore analizza le parentesi graffe come parentesi uncinate per rendere il commento relativo
alla documentazione meno complesso da creare quando viene fatto riferimento a identificatori generici.

NOTE
I commenti relativi alla documentazione XML non sono metadati, ovvero non vengono inclusi nell'assembly compilato e
pertanto non sono accessibili mediante reflection.

Contenuto della sezione


Tag consigliati per i commenti relativi alla documentazione
Elaborazione del file XML
Delimitatori per i tag della documentazione
Come usare le funzionalità relative alla documentazione XML

Sezioni correlate
Per altre informazioni, vedere:
-DOC (elabora i commenti relativi alla documentazione)

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla
documentazione (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Il compilatore C# elabora i commenti alla documentazione nel codice e li formatta come XML in un file il cui
nome è stato specificato nell'opzione della riga di comando /doc . Per creare la documentazione finale basata sul
file generato dal compilatore, è possibile creare uno strumento personalizzato o usare uno strumento come
DocFX o Sandcastle.
I tag vengono elaborati in costrutti di codice come tipi e membri dei tipi.

NOTE
Non è possibile applicare a uno spazio dei nomi i commenti relativi alla documentazione.

Il compilatore elabora tutti i tag validi per XML. I tag seguenti forniscono le funzionalità comunemente usate
nella documentazione dell'utente.

Tag

<c> <para> <see>* <value>

<code> <param>* <seealso>*

<example> <paramref> <summary>

<exception>* <permission>* <typeparam>*

<include>* <remarks> <typeparamref>

<list> <inheritdoc> <returns>

( * indica che il compilatore verifica la sintassi).


Se si vuole che nel testo di un commento alla documentazione vengano visualizzate parentesi angolari, usare la
codifica HTML di < e > , ovvero rispettivamente &lt; e &gt; . Questa codifica è illustrata nell'esempio
seguente.

/// <summary>
/// This property always returns a value &lt; 1.
/// </summary>

Vedere anche
Guida per programmatori C#
-DOC (opzioni del compilatore C#)
Commenti relativi alla documentazione XML
Elaborare il file XML (Guida per programmatori C#)
02/11/2020 • 9 minutes to read • Edit Online

Il compilatore genera una stringa ID per ogni costrutto nel codice contrassegnato per generare la
documentazione. Per informazioni su come contrassegnare il codice, vedere Tag consigliati per i commenti
relativi alla documentazione. La stringa ID identifica in modo univoco il costrutto. I programmi che elaborano il
file XML possono utilizzare la stringa ID per identificare i metadati .NET corrispondenti o l'elemento di Reflection
a cui si applica la documentazione.

Stringhe ID
Il file XML non è una rappresentazione gerarchica del codice. Si tratta di un elenco semplice con un ID generato
per ogni elemento.
Per generare gli ID, il compilatore applica le regole seguenti:
Assenza di spazi vuoti nella stringa.
La prima parte della stringa identifica il tipo di membro usando un singolo carattere seguito da due punti.
Vengono usati i tipi di membri seguenti:

C A RAT T ERE T IP O DI M EM B RO N OT E

N namespace Non è possibile aggiungere a uno


spazio dei nomi commenti relativi
alla documentazione, ma, se
supportati, è possibile creare
riferimenti cref a tali commenti.

T tipo Un tipo può essere una classe,


un'interfaccia, una struttura,
un'enumerazione o un delegato.

F campo

P proprietà Include gli indicizzatori o altre


proprietà indicizzate.

M method Include metodi speciali, ad esempio


costruttori e operatori.

E evento

! stringa di errore Nella parte restante della stringa


vengono fornite informazioni
sull'errore. Il compilatore C# genera
informazioni sugli errori per tutti i
collegamenti che non è possibile
risolvere.

La seconda parte della stringa identifica il nome completo dell'elemento, a partire dalla radice dello
spazio dei nomi. Il nome dell'elemento, i tipi di inclusione e lo spazio dei nomi sono separati da punti. Se
il nome dell'elemento contiene dei punti, questi verranno sostituiti con il segno di cancelletto ('#'), Si
presuppone che nessun elemento abbia un segno di hash direttamente nel nome. Ad esempio, il nome
completo del costruttore di stringa è "System. String. #ctor".
Per le proprietà e i metodi, l'elenco di parametri racchiuso tra parentesi segue. Se non sono presenti
parametri, non è presente alcuna parentesi. I parametri sono separati da virgole. La codifica di ogni
parametro segue direttamente il modo in cui viene codificata in una firma .NET:
Tipi di base. I tipi regolari (ELEMENT_TYPE_CLASS o ELEMENT_TYPE_VALUETYPE) vengono
rappresentati con il nome completo del tipo.
I tipi intrinseci, ad esempio ELEMENT_TYPE_I4, ELEMENT_TYPE_OBJECT, ELEMENT_TYPE_STRING,
ELEMENT_TYPE_TYPEDBYREF e ELEMENT_TYPE_VOID, sono rappresentati come nome completo
del tipo completo corrispondente. ad esempio System.Int32 o System.TypedReference.
ELEMENT_TYPE_PTR viene rappresentato con '*' dopo il tipo modificato.
ELEMENT_TYPE_BYREF viene rappresentato con "@" dopo il tipo modificato.
ELEMENT_TYPE_PINNED viene rappresentato con '^' dopo il tipo modificato. Non viene mai
generato dal compilatore C#.
ELEMENT_TYPE_CMOD_REQ viene rappresentato con '|' seguito dal nome completo della classe di
modificatori dopo il tipo modificato. Non viene mai generato dal compilatore C#.
ELEMENT_TYPE_CMOD_OPT viene rappresentato con '!' seguito dal nome completo della classe di
modificatori dopo il tipo modificato.
ELEMENT_TYPE_SZARRAY viene rappresentato con "[]" dopo il tipo di elemento della matrice.
ELEMENT_TYPE_GENERICARRAY viene rappresentato con "[?]" dopo il tipo di elemento della
matrice. Non viene mai generato dal compilatore C#.
ELEMENT_TYPE_ARRAY è rappresentato come [lowerbound: size ,lowerbound: size ] dove il
numero di virgole è il rango-1 e i limiti inferiori e le dimensioni di ogni dimensione, se noti, sono
rappresentati in decimali. Se non si specifica una dimensione o un limite inferiore, viene omesso.
Se vengono omessi il limite inferiore e la dimensione per una dimensione specifica, viene omesso
anche ':'. Ad esempio, una matrice a due dimensioni con limiti inferiori pari a 1 e dimensioni non
specificate viene rappresentata con [1:,1:].
ELEMENT_TYPE_FNPTR viene rappresentato con "=FUNC: type (signature)", dove type
rappresenta il tipo restituito e signature identifica gli argomenti del metodo. Se non vi sono
argomenti, le parentesi vengono omesse. Non viene mai generato dal compilatore C#.
I componenti della firma seguenti non sono rappresentati perché non vengono usati per distinguere i
metodi di overload:
convenzione di chiamata
tipo restituito
ELEMENT_TYPE_SENTINEL
Solo per gli operatori di conversione ( op_Implicit e op_Explicit ), il valore restituito del metodo viene
codificato come ' ~' seguito dal tipo restituito.
Nel caso di tipi generici, il nome del tipo è seguito da un apice inverso e quindi da un numero che indica il
numero di parametri di tipo generici. Ad esempio:
<member name="T:SampleClass`2"> è il tag di un tipo che viene definito come
public class SampleClass<T, U> .
Per i metodi che accettano tipi generici come parametri, i parametri di tipo generico vengono specificati
come numeri preceduti da un apice inverso (ad esempio ` , 0, ` 1). Ogni numero rappresenta una
notazione di matrice in base zero per i parametri generici del tipo.

Esempio
Negli esempi seguenti viene illustrato come vengono generate le stringhe ID per una classe e i relativi membri:

namespace N
{
/// <summary>
/// Enter description here for class X.
/// ID string generated is "T:N.X".
/// </summary>
public unsafe class X
{
/// <summary>
/// Enter description here for the first constructor.
/// ID string generated is "M:N.X.#ctor".
/// </summary>
public X() { }

/// <summary>
/// Enter description here for the second constructor.
/// ID string generated is "M:N.X.#ctor(System.Int32)".
/// </summary>
/// <param name="i">Describe parameter.</param>
public X(int i) { }

/// <summary>
/// Enter description here for field q.
/// ID string generated is "F:N.X.q".
/// </summary>
public string q;

/// <summary>
/// Enter description for constant PI.
/// ID string generated is "F:N.X.PI".
/// </summary>
public const double PI = 3.14;

/// <summary>
/// Enter description for method f.
/// ID string generated is "M:N.X.f".
/// </summary>
/// <returns>Describe return value.</returns>
public int f() { return 1; }

/// <summary>
/// Enter description for method bb.
/// ID string generated is "M:N.X.bb(System.String,System.Int32@,System.Void*)".
/// </summary>
/// <param name="s">Describe parameter.</param>
/// <param name="y">Describe parameter.</param>
/// <param name="z">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public int bb(string s, ref int y, void* z) { return 1; }

/// <summary>
/// Enter description for method gg.
/// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])".
/// </summary>
/// <param name="array1">Describe parameter.</param>
/// <param name="array">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public int gg(short[] array1, int[,] array) { return 0; }
/// <summary>
/// Enter description for operator.
/// ID string generated is "M:N.X.op_Addition(N.X,N.X)".
/// </summary>
/// <param name="x">Describe parameter.</param>
/// <param name="xx">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public static X operator +(X x, X xx) { return x; }

/// <summary>
/// Enter description for property.
/// ID string generated is "P:N.X.prop".
/// </summary>
public int prop { get { return 1; } set { } }

/// <summary>
/// Enter description for event.
/// ID string generated is "E:N.X.d".
/// </summary>
public event D d;

/// <summary>
/// Enter description for property.
/// ID string generated is "P:N.X.Item(System.String)".
/// </summary>
/// <param name="s">Describe parameter.</param>
/// <returns></returns>
public int this[string s] { get { return 1; } }

/// <summary>
/// Enter description for class Nested.
/// ID string generated is "T:N.X.Nested".
/// </summary>
public class Nested { }

/// <summary>
/// Enter description for delegate.
/// ID string generated is "T:N.X.D".
/// </summary>
/// <param name="i">Describe parameter.</param>
public delegate void D(int i);

/// <summary>
/// Enter description for operator.
/// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32".
/// </summary>
/// <param name="x">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public static explicit operator int(X x) { return 1; }
}
}

Vedere anche
Guida per programmatori C#
-DOC (opzioni del compilatore C#)
Commenti relativi alla documentazione XML
Delimitatori per i tag della documentazione (Guida
per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

L'uso dei commenti XML relativi alla documentazione richiede la specifica di delimitatori per indicare al
compilatore il punto di inizio e di fine di un commento relativo alla documentazione. È possibile usare con i tag
della documentazione XML i tipi di delimitatori seguenti:
///

Delimitatore di riga singola. Questo è il formato illustrato negli esempi di documentazione e usato dai
modelli di progetto C#. Se dopo il delimitatore è presente un carattere di spazio vuoto, quest'ultimo non
è incluso nell'output XML.

NOTE
Il Integrated Development Environment (IDE) di Visual Studio inserisce automaticamente <summary> i
</summary> tag e e sposta il cursore all'interno di questi tag dopo aver digitato il /// delimitatore nell'editor di
codice. È possibile attivare o disattivare questa funzionalità nella finestra di dialogo Opzioni.

/** */

Delimitatori di più righe.


Quando si usano i delimitatori, è necessario seguire alcune regole di formattazione /** */ :
Nella riga contenente il delimitatore /** se la parte restante della riga è rappresentata da uno
spazio vuoto, la riga non viene elaborata per i commenti. Se il primo carattere dopo il delimitatore
/** è uno spazio vuoto, lo spazio vuoto viene ignorato e il resto della riga viene elaborato. In caso
contrario, l'intero testo della riga dopo il delimitatore /** viene elaborato come parte del
commento.
Se sulla riga contenente il delimitatore */ sono presenti solo spazi vuoti fino al delimitatore */ ,
la riga viene ignorata. In caso contrario, il testo nella riga fino al */ delimitatore viene elaborato
come parte del commento.
Per le righe successive a quella che inizia con il delimitatore /** , il compilatore cerca un modello
comune all'inizio di ogni riga. Il modello può essere costituito da uno spazio vuoto facoltativo e un
asterisco ( * ) seguiti da altri spazi vuoti facoltativi. Se trova un modello comune all'inizio di ogni
riga che non inizia con il delimitatore /** o */ , il compilatore ignora tale modello per ogni riga.
Gli esempi seguenti illustrano queste regole.
L'unica parte del commento seguente elaborato è la riga che inizia con <summary> . I tre formati di
tag producono gli stessi commenti.
/** <summary>text</summary> */

/**
<summary>text</summary>
*/

/**
* <summary>text</summary>
*/

Il compilatore identifica un modello comune di " * " all'inizio della seconda e della terza riga. Il
modello non è incluso nell'output.

/**
* <summary>
* text </summary>*/

Il compilatore non trova alcun modello comune nel commento seguente poiché il secondo
carattere nella terza riga non è un asterisco. Di conseguenza, tutto il testo contenuto nella seconda
e nella terza riga viene elaborato come parte del commento.

/**
* <summary>
text </summary>
*/

Nel commento seguente il compilatore non rileva alcun modello per due motivi. In primo luogo, il
numero di spazi prima dell'asterisco non è coerente. In secondo luogo, la quinta riga inizia con una
tabulazione senza corrispondenza degli spazi. Di conseguenza, tutto il testo dalla seconda alla
quinta riga verrà elaborato come parte del commento.

/**
* <summary>
* text
* text2
* </summary>
*/

Vedere anche
Guida per programmatori C#
Commenti relativi alla documentazione XML
-DOC (opzioni del compilatore C#)
Come usare le funzionalità relative alla
documentazione XML
02/11/2020 • 5 minutes to read • Edit Online

L'esempio seguente fornisce una panoramica di base di un tipo documentato.

Esempio
// If compiling from the command line, compile with: -doc:YourFileName.xml

/// <summary>
/// Class level summary documentation goes here.
/// </summary>
/// <remarks>
/// Longer comments can be associated with a type or member through
/// the remarks tag.
/// </remarks>
public class TestClass : TestInterface
{
/// <summary>
/// Store for the Name property.
/// </summary>
private string _name = null;

/// <summary>
/// The class constructor.
/// </summary>
public TestClass()
{
// TODO: Add Constructor Logic here.
}

/// <summary>
/// Name property.
/// </summary>
/// <value>
/// A value tag is used to describe the property value.
/// </value>
public string Name
{
get
{
if (_name == null)
{
throw new System.Exception("Name is null");
}
return _name;
}
}

/// <summary>
/// Description for SomeMethod.
/// </summary>
/// <param name="s"> Parameter description for s goes here.</param>
/// <seealso cref="System.String">
/// You can use the cref attribute on any tag to reference a type or member
/// and the compiler will check that the reference exists.
/// </seealso>
public void SomeMethod(string s)
{
{
}

/// <summary>
/// Some other method.
/// </summary>
/// <returns>
/// Return values are described through the returns tag.
/// </returns>
/// <seealso cref="SomeMethod(string)">
/// Notice the use of the cref attribute to reference a specific method.
/// </seealso>
public int SomeOtherMethod()
{
return 0;
}

public int InterfaceMethod(int n)


{
return n * n;
}

/// <summary>
/// The entry point for the application.
/// </summary>
/// <param name="args"> A list of command line arguments.</param>
static int Main(System.String[] args)
{
// TODO: Add code to start application here.
return 0;
}
}

/// <summary>
/// Documentation that describes the interface goes here.
/// </summary>
/// <remarks>
/// Details about the interface go here.
/// </remarks>
interface TestInterface
{
/// <summary>
/// Documentation that describes the method goes here.
/// </summary>
/// <param name="n">
/// Parameter n requires an integer argument.
/// </param>
/// <returns>
/// The method returns an integer.
/// </returns>
int InterfaceMethod(int n);
}

Nell'esempio viene generato un file con estensione XML con il contenuto seguente.

<?xml version="1.0"?>
<doc>
<assembly>
<name>xmlsample</name>
</assembly>
<members>
<member name="T:TestClass">
<summary>
Class level summary documentation goes here.
</summary>
<remarks>
Longer comments can be associated with a type or member through
the remarks tag.
</remarks>
</member>
<member name="F:TestClass._name">
<summary>
Store for the Name property.
</summary>
</member>
<member name="M:TestClass.#ctor">
<summary>
The class constructor.
</summary>
</member>
<member name="P:TestClass.Name">
<summary>
Name property.
</summary>
<value>
A value tag is used to describe the property value.
</value>
</member>
<member name="M:TestClass.SomeMethod(System.String)">
<summary>
Description for SomeMethod.
</summary>
<param name="s"> Parameter description for s goes here.</param>
<seealso cref="T:System.String">
You can use the cref attribute on any tag to reference a type or member
and the compiler will check that the reference exists.
</seealso>
</member>
<member name="M:TestClass.SomeOtherMethod">
<summary>
Some other method.
</summary>
<returns>
Return values are described through the returns tag.
</returns>
<seealso cref="M:TestClass.SomeMethod(System.String)">
Notice the use of the cref attribute to reference a specific method.
</seealso>
</member>
<member name="M:TestClass.Main(System.String[])">
<summary>
The entry point for the application.
</summary>
<param name="args"> A list of command line arguments.</param>
</member>
<member name="T:TestInterface">
<summary>
Documentation that describes the interface goes here.
</summary>
<remarks>
Details about the interface go here.
</remarks>
</member>
<member name="M:TestInterface.InterfaceMethod(System.Int32)">
<summary>
Documentation that describes the method goes here.
</summary>
<param name="n">
Parameter n requires an integer argument.
</param>
<returns>
The method returns an integer.
</returns>
</member>
</members>
</doc>
Compilazione del codice
Per compilare l'esempio, immettere il comando seguente:
csc XMLsample.cs /doc:XMLsample.xml

Questo comando crea il file XML XMLsample.xml, che è possibile visualizzare nel browser o tramite il TYPE
comando.

Programmazione efficiente
La documentazione XML inizia con /// . Quando si crea un nuovo progetto, le procedure guidate inseriscono
alcune /// righe iniziali. L'elaborazione di questi commenti presenta alcune restrizioni:
La documentazione deve essere in codice XML ben formato. Se il codice XML non è ben formato, viene
generato un avviso e il file di documentazione conterrà un commento che segnalerà che si è verificato un
errore.
Gli sviluppatori sono liberi di creare set di tag personalizzati. È disponibile un set di tag consigliato. Alcuni
tag consigliati hanno un significato speciale:
Il <param> tag viene usato per descrivere i parametri. Se usato, il compilatore verifica che il
parametro esista e che tutti i parametri siano descritti nella documentazione. Se la verifica ha esito
negativo, il compilatore genera un avviso.
L' cref attributo può essere allegato a qualsiasi tag per fare riferimento a un elemento di codice.
Il compilatore verifica l'esistenza di questo elemento. Se la verifica ha esito negativo, il compilatore
genera un avviso. Il compilatore rispetta eventuali istruzioni using quando esegue la ricerca di un
tipo descritto nell'attributo cref .
Il <summary> tag viene usato da IntelliSense all'interno di Visual Studio per visualizzare
informazioni aggiuntive su un tipo o un membro.

NOTE
Il file XML non fornisce informazioni complete sul tipo e sui membri, ad esempio, non contiene alcuna
informazione sul tipo. Per ottenere informazioni complete su un tipo o un membro, usare il file di
documentazione insieme alla reflection sul tipo o sul membro effettivo.

Vedere anche
Guida per programmatori C#
-DOC (opzioni del compilatore C#)
Commenti relativi alla documentazione XML
Processore della documentazione di DocFX
Processore di documentazione Sandcastle
<c>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<c>text</c>

Parametri
text

Il testo che si desidera indicare come codice.

Commenti
Il <c> tag fornisce un modo per indicare che il testo all'interno di una descrizione deve essere contrassegnato
come codice. Usare <code> per indicare più righe come codice.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <summary><c>DoWork</c> is a method in the <c>TestClass</c> class.
/// </summary>
public static void DoWork(int Int1)
{
}

/// text for Main


static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<code>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<code>content</code>

Parametri
content

Testo da contrassegnare come codice.

Commenti
Il <code> tag viene usato per indicare più righe di codice. Usare <c> per indicare che il testo a riga singola
all'interno di una descrizione deve essere contrassegnato come codice.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
Vedere l' <example> articolo per un esempio di come usare il <code> tag.

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
attributo cref (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

L'attributo cref in un tag della documentazione XML indica un "riferimento al codice". Specifica che il testo
all'interno del tag è un elemento di codice, ad esempio un tipo, un metodo o una proprietà. Gli strumenti per la
creazione di documentazione, come DocFX e Sandcastle, usano attributi cref per generare automaticamente
collegamenti ipertestuali alla pagina in cui è documentato il tipo o il membro.

Esempio
Nell'esempio seguente vengono illustrati cref gli attributi utilizzati nei <see> tag.

// Save this file as CRefTest.cs


// Compile with: csc CRefTest.cs -doc:Results.xml

namespace TestNamespace
{
/// <summary>
/// TestClass contains several cref examples.
/// </summary>
public class TestClass
{
/// <summary>
/// This sample shows how to specify the <see cref="TestClass"/> constructor as a cref attribute.
/// </summary>
public TestClass()
{ }

/// <summary>
/// This sample shows how to specify the <see cref="TestClass(int)"/> constructor as a cref
attribute.
/// </summary>
public TestClass(int value)
{ }

/// <summary>
/// The GetZero method.
/// </summary>
/// <example>
/// This sample shows how to call the <see cref="GetZero"/> method.
/// <code>
/// class TestClass
/// {
/// static int Main()
/// {
/// return GetZero();
/// }
/// }
/// </code>
/// </example>
public static int GetZero()
{
return 0;
}

/// <summary>
/// The GetGenericValue method.
/// </summary>
/// <remarks>
/// This sample shows how to specify the <see cref="GetGenericValue"/> method as a cref attribute.
/// </remarks>

public static T GetGenericValue<T>(T para)


{
return para;
}
}

/// <summary>
/// GenericClass.
/// </summary>
/// <remarks>
/// This example shows how to specify the <see cref="GenericClass{T}"/> type as a cref attribute.
/// </remarks>
class GenericClass<T>
{
// Fields and members.
}

class Program
{
static int Main()
{
return TestClass.GetZero();
}
}
}

Durante la compilazione, il programma crea il file XML seguente. Si noti che l'attributo cref del metodo
GetZero , ad esempio, è stato trasformato dal compilatore in "M:TestNamespace.TestClass.GetZero" . Il prefisso
"M:" significa "metodo" ed è una convenzione riconosciuta dagli strumenti di creazione della documentazione
come DocFX e Sandcastle. Per un elenco completo dei prefissi, vedere Elaborazione del file XML.
<?xml version="1.0"?>
<doc>
<assembly>
<name>CRefTest</name>
</assembly>
<members>
<member name="T:TestNamespace.TestClass">
<summary>
TestClass contains several cref examples.
</summary>
</member>
<member name="M:TestNamespace.TestClass.#ctor">
<summary>
This sample shows how to specify the <see cref="T:TestNamespace.TestClass"/> constructor as a
cref attribute.
</summary>
</member>
<member name="M:TestNamespace.TestClass.#ctor(System.Int32)">
<summary>
This sample shows how to specify the <see cref="M:TestNamespace.TestClass.#ctor(System.Int32)"/>
constructor as a cref attribute.
</summary>
</member>
<member name="M:TestNamespace.TestClass.GetZero">
<summary>
The GetZero method.
</summary>
<example>
This sample shows how to call the <see cref="M:TestNamespace.TestClass.GetZero"/> method.
<code>
class TestClass
{
static int Main()
{
return GetZero();
}
}
</code>
</example>
</member>
<member name="M:TestNamespace.TestClass.GetGenericValue``1(``0)">
<summary>
The GetGenericValue method.
</summary>
<remarks>
This sample shows how to specify the <see
cref="M:TestNamespace.TestClass.GetGenericValue``1(``0)"/> method as a cref attribute.
</remarks>
</member>
<member name="T:TestNamespace.GenericClass`1">
<summary>
GenericClass.
</summary>
<remarks>
This example shows how to specify the <see cref="T:TestNamespace.GenericClass`1"/> type as a
cref attribute.
</remarks>
</member>
</members>
</doc>

Vedere anche
Commenti relativi alla documentazione XML
Tag consigliati per i commenti relativi alla documentazione
<example>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<example>description</example>

Parametri
description

Descrizione dell'esempio di codice.

Commenti
Il <example> tag consente di specificare un esempio di come usare un metodo o un altro membro della libreria.
Questa operazione comporta in genere l'uso del <code> tag.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// Save this file as CRefTest.cs
// Compile with: csc CRefTest.cs -doc:Results.xml

namespace TestNamespace
{
/// <summary>
/// TestClass contains several cref examples.
/// </summary>
public class TestClass
{
/// <summary>
/// This sample shows how to specify the <see cref="TestClass"/> constructor as a cref attribute.
/// </summary>
public TestClass()
{ }

/// <summary>
/// This sample shows how to specify the <see cref="TestClass(int)"/> constructor as a cref
attribute.
/// </summary>
public TestClass(int value)
{ }

/// <summary>
/// The GetZero method.
/// </summary>
/// <example>
/// This sample shows how to call the <see cref="GetZero"/> method.
/// <code>
/// class TestClass
/// {
/// static int Main()
/// {
/// return GetZero();
/// }
/// }
/// }
/// </code>
/// </example>
public static int GetZero()
{
return 0;
}

/// <summary>
/// The GetGenericValue method.
/// </summary>
/// <remarks>
/// This sample shows how to specify the <see cref="GetGenericValue"/> method as a cref attribute.
/// </remarks>

public static T GetGenericValue<T>(T para)


{
return para;
}
}

/// <summary>
/// GenericClass.
/// </summary>
/// <remarks>
/// This example shows how to specify the <see cref="GenericClass{T}"/> type as a cref attribute.
/// </remarks>
class GenericClass<T>
{
// Fields and members.
}

class Program
{
static int Main()
{
return TestClass.GetZero();
}
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<exception>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<exception cref="member">description</exception>

Parametri
cref = " member "
Riferimento ad un'eccezione disponibile dall'ambiente di compilazione corrente. Il compilatore controlla
che l'eccezione specificata esista e converte member nel nome canonico dell'elemento nel file XML di
output. member deve essere racchiuso tra virgolette doppie (" ").
Per altre informazioni su come formattare member per fare riferimento a un tipo generico, vedere
Elaborazione del file XML.
description

Descrizione dell'eccezione.

Commenti
Il <exception> tag consente di specificare le eccezioni che possono essere generate. Questo tag può essere
applicato alle definizioni di metodi, proprietà, eventi e indicizzatori.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.
Per altre informazioni sulla gestione delle eccezioni, vedere Eccezioni e gestione delle eccezioni.

Esempio
// compile with: -doc:DocFileName.xml

/// Comment for class


public class EClass : System.Exception
{
// class definition...
}

/// Comment for class


class TestClass
{
/// <exception cref="System.Exception">Thrown when...</exception>
public void DoSomething()
{
try
{
}
catch (EClass)
{
}
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<include> (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Sintassi
<include file='filename' path='tagpath[@name="id"]' />

Parametri
filename

Nome del file XML che contiene la documentazione. Il nome file può essere qualificato con un percorso
relativo al file del codice sorgente. Racchiudere filename tra virgolette singole (' ').
tagpath

Percorso dei tag di filename che porta al name del tag. Racchiudere il percorso tra virgolette singole (' ').
name

Identificatore del nome contenuto nel tag che precede i commenti. name ha sempre un id .
id

ID del tag che precede i commenti. Racchiudere l'ID tra virgolette doppie (" ").

Osservazioni
Il <include> tag consente di fare riferimento ai commenti in un altro file che descrive i tipi e i membri nel codice
sorgente. eliminando la necessità di inserire i commenti relativi alla documentazione direttamente nel file del
codice sorgente. Inserendo la documentazione in un file separato, è possibile applicare alla documentazione il
controllo del codice sorgente separatamente dal codice sorgente. Una persona, ad esempio, può avere il file di
codice sorgente estratto e un'altra il file della documentazione estratto.
Il <include> tag utilizza la sintassi XPath XML. Per informazioni su come personalizzare l'uso, vedere la
documentazione di XPath <include> .

Esempio
In questo esempio vengono presi in considerazione più file. Di seguito è riportato il primo file, che utilizza
<include> .
// compile with: -doc:DocFileName.xml

/// <include file='xml_include_tag.doc' path='MyDocs/MyMembers[@name="test"]/*' />


class Test
{
static void Main()
{
}
}

/// <include file='xml_include_tag.doc' path='MyDocs/MyMembers[@name="test2"]/*' />


class Test2
{
public void Test()
{
}
}

Il secondo file, xml_include_tag.doc, contiene i commenti di documentazione seguenti.

<MyDocs>

<MyMembers name="test">
<summary>
The summary for this type.
</summary>
</MyMembers>

<MyMembers name="test2">
<summary>
The summary for this other type.
</summary>
</MyMembers>

</MyDocs>

Output del programma


L'output seguente viene generato quando si compilano le classi Test e Test2 con la riga di comando seguente:
-doc:DocFileName.xml. . In Visual Studio, è possibile specificare l'opzione dei commenti XML alla
documentazione nel riquadro Compilazione di Creazione progetti. Quando il compilatore C# Visualizza il
<include> tag, Cerca i commenti nella documentazione in xml_include_tag.doc invece che nel file di origine
corrente. Il compilatore genera quindi DocFileName.xmle questo è il file utilizzato dagli strumenti di
documentazione, ad esempio Sandcastle , per produrre la documentazione finale.
<?xml version="1.0"?>
<doc>
<assembly>
<name>xml_include_tag</name>
</assembly>
<members>
<member name="T:Test">
<summary>
The summary for this type.
</summary>
</member>
<member name="T:Test2">
<summary>
The summary for this other type.
</summary>
</member>
</members>
</doc>

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<inheritdoc> (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<inheritdoc/>

InheritDoc
Ereditano commenti XML da classi base, interfacce e metodi simili. In questo modo si elimina la copia
indesiderata e si incollano i commenti XML duplicati e i commenti XML vengono sincronizzati automaticamente.

Osservazioni
Aggiungere i commenti XML in classi o interfacce di base e consentire a InheritDoc di copiare i commenti alle
classi di implementazione.
Aggiungere i commenti XML ai metodi sincroni e consentire a InheritDoc di copiare i commenti nelle versioni
asincrone degli stessi metodi.
Se si desidera copiare i commenti da un membro specifico, è possibile utilizzare l' cref attributo per specificare
il membro.

Esempi
// compile with: -doc:DocFileName.xml

/// <summary>
/// You may have some primary information about this class.
/// </summary>
public class MainClass
{
}

///<inheritdoc/>
public class TestClass: MainClass
{
}

// compile with: -doc:DocFileName.xml

/// <summary>
/// You may have some primary information about this interface.
/// </summary>
public interface ITestInterface
{
}

///<inheritdoc cref="ITestInterface"/>
public class TestClass : ITestInterface
{
}
Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<list>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<list type="bullet|number|table">
<listheader>
<term>term</term>
<description>description</description>
</listheader>
<item>
<term>term</term>
<description>description</description>
</item>
</list>

Parametri
term

Termine da definire, che verrà definito in description .


description

Elemento di un elenco puntato o numerato oppure la definizione di un term .

Commenti
Il <listheader> blocco viene utilizzato per definire la riga di intestazione di un elenco di tabelle o definizioni. Per
definire una tabella, è sufficiente specificare una voce per il termine nell'intestazione.
Ogni elemento nell'elenco viene specificato con un <item> blocco. Quando si crea un elenco di definizioni, è
necessario specificare sia term che description . Per le tabelle e gli elenchi puntati o numerati, tuttavia, è
sufficiente fornire una voce per description .
Un elenco o una tabella può includere un numero di <item> blocchi sufficiente.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <summary>Here is an example of a bulleted list:
/// <list type="bullet">
/// <item>
/// <description>Item 1.</description>
/// </item>
/// <item>
/// <description>Item 2.</description>
/// </item>
/// </list>
/// </summary>
static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<para>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<para>content</para>

Parametri
content

Testo del paragrafo.

Commenti
Il <para> tag è da usare all'interno di un tag, ad esempio <summary> , <remarks> o <returns> , e consente di
aggiungere la struttura al testo.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
<summary>Per un esempio dell'utilizzo di <para> , vedere.

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<param>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<param name="name">description</param>

Parametri
name

Nome di un parametro di metodo. Racchiudere il nome tra virgolette doppie (" ").
description

Descrizione del parametro.

Commenti
Il <param> tag deve essere usato nel commento per una dichiarazione di metodo per descrivere uno dei
parametri per il metodo. Per documentare più parametri, usare più <param> tag.
Il testo per il <param> tag viene visualizzato in IntelliSense, il Visualizzatore oggetti e il report Web di commenti
sul codice.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
// Single parameter.
/// <param name="Int1">Used to indicate status.</param>
public static void DoWork(int Int1)
{
}

// Multiple parameters.
/// <param name="Int1">Used to indicate status.</param>
/// <param name="Float1">Used to specify context.</param>
public static void DoWork(int Int1, float Float1)
{
}

/// text for Main


static void Main()
{
}
}
Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<paramref>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<paramref name="name"/>

Parametri
name

Nome del parametro a cui fare riferimento. Racchiudere il nome tra virgolette doppie (" ").

Commenti
Il tag fornisce un modo per indicare che una parola nei commenti del codice, ad esempio in un
<paramref>
<summary> blocco o <remarks> fa riferimento a un parametro. È possibile elaborare il file XML in modo da
formattare la parola in modo specifico, ad esempio in grassetto o in corsivo.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <summary>DoWork is a method in the TestClass class.
/// The <paramref name="int1"/> parameter takes a number.
/// </summary>
public static void DoWork(int int1)
{
}

/// text for Main


static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<permission>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<permission cref="member">description</permission>

Parametri
cref = " member "
Riferimento a un membro o a un campo disponibile per essere chiamato dall'ambiente di compilazione
corrente. Il compilatore verifica l'esistenza dell'elemento di codice specificato e converte member nel
nome canonico dell'elemento nel file XML di output. member deve essere racchiuso tra virgolette doppie
(" ").
Per informazioni su come creare un riferimento cref a un tipo generico, vedere l' attributo cref.
description

Descrizione dell'accesso al membro.

Commenti
Il <permission> tag consente di documentare l'accesso di un membro. La classe PermissionSet consente di
specificare l'accesso a un membro.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

class TestClass
{
/// <permission cref="System.Security.PermissionSet">Everyone can access this method.</permission>
public static void Test()
{
}

static void Main()


{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<remarks>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<remarks>description</remarks>

Parametri
Description

Descrizione del membro.

Commenti
Il <remarks> tag viene usato per aggiungere informazioni su un tipo, integrando le informazioni specificate con
<summary> . Queste informazioni vengono visualizzate nella finestra Visualizzatore oggetti.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// <summary>
/// You may have some primary information about this class.
/// </summary>
/// <remarks>
/// You may have some additional information about this class.
/// </remarks>
public class TestClass
{
/// text for Main
static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<returns>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<returns>description</returns>

Parametri
description

Descrizione del valore restituito.

Osservazioni
Il <returns> tag deve essere usato nel commento per una dichiarazione di metodo per descrivere il valore
restituito.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <returns>Returns zero.</returns>
public static int GetZero()
{
return 0;
}

/// text for Main


static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<see>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<see cref="member"/>

Parametri
cref = " member "
Riferimento a un membro o a un campo disponibile per essere chiamato dall'ambiente di compilazione
corrente. Il compilatore verifica l'esistenza dell'elemento di codice specificato e passa member al nome
dell'elemento nel file XML di output. Racchiudere member tra virgolette doppie (" ").

Commenti
Il <see> tag consente di specificare un collegamento dall'interno del testo. Usare <seealso> per indicare che il
testo deve essere inserito in una sezione vedere anche. Usare l'attributo cref per creare collegamenti ipertestuali
interni alle pagine della documentazione per gli elementi di codice. Inoltre, href è un attributo valido che
funzionerà come collegamento ipertestuale.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.
Nell'esempio seguente viene illustrato un <see> tag all'interno di una sezione di riepilogo.

// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <summary>DoWork is a method in the TestClass class.
/// <para>Here's how you could make a second paragraph in a description. <see
cref="System.Console.WriteLine(System.String)"/> for information about output statements.</para>
/// <seealso cref="TestClass.Main"/>
/// </summary>
public static void DoWork(int Int1)
{
}

/// text for Main


static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<seealso>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<seealso cref="member"/>

Parametri
cref = " member "
Riferimento a un membro o a un campo disponibile per essere chiamato dall'ambiente di compilazione
corrente. Il compilatore verifica l'esistenza dell'elemento di codice specificato e passa member al nome
dell'elemento nel file XML di output. member deve essere racchiuso tra virgolette doppie (" ").
Per informazioni su come creare un riferimento cref a un tipo generico, vedere l' attributo cref.

Osservazioni
Il <seealso> tag consente di specificare il testo che potrebbe essere necessario visualizzare in una sezione
vedere anche. Usare <see> per specificare un collegamento dall'interno del testo.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
<summary>Per un esempio dell'utilizzo di <seealso> , vedere.

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<summary>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<summary>description</summary>

Parametri
description

Un riepilogo dell'oggetto.

Commenti
Il <summary> tag deve essere usato per descrivere un tipo o un membro del tipo. Utilizzare <remarks> per
aggiungere informazioni aggiuntive a una descrizione del tipo. Usare l'attributo cref per abilitare strumenti della
documentazione come DocFX e Sandcastle per creare collegamenti ipertestuali interni alle pagine della
documentazione per gli elementi di codice.
Il testo per il <summary> tag è l'unica fonte di informazioni sul tipo in IntelliSense e viene visualizzato anche nella
finestra di Visualizzatore oggetti.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file. Per creare la
documentazione finale basata sul file generato dal compilatore, è possibile creare uno strumento personalizzato
o usare uno strumento come DocFX o Sandcastle.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class TestClass


public class TestClass
{
/// <summary>DoWork is a method in the TestClass class.
/// <para>Here's how you could make a second paragraph in a description. <see
cref="System.Console.WriteLine(System.String)"/> for information about output statements.</para>
/// <seealso cref="TestClass.Main"/>
/// </summary>
public static void DoWork(int Int1)
{
}

/// text for Main


static void Main()
{
}
}

L'esempio precedente produce il seguente XML.


<?xml version="1.0"?>
<doc>
<assembly>
<name>YourNamespace</name>
</assembly>
<members>
<member name="T:TestClass">
text for class TestClass
</member>
<member name="M:TestClass.DoWork(System.Int32)">
<summary>DoWork is a method in the TestClass class.
<para>Here's how you could make a second paragraph in a description. <see
cref="M:System.Console.WriteLine(System.String)"/> for information about output statements.</para>
</summary>
<seealso cref="M:TestClass.Main"/>
</member>
<member name="M:TestClass.Main">
text for Main
</member>
</members>
</doc>

Esempio
Nell'esempio riportato di seguito viene illustrato come effettuare un riferimento cref a un tipo generico.

// compile with: -doc:DocFileName.xml

// the following cref shows how to specify the reference, such that,
// the compiler will resolve the reference
/// <summary cref="C{T}">
/// </summary>
class A { }

// the following cref shows another way to specify the reference,


// such that, the compiler will resolve the reference
// <summary cref="C &lt; T &gt;">

// the following cref shows how to hard-code the reference


/// <summary cref="T:C`1">
/// </summary>
class B { }

/// <summary cref="A">


/// </summary>
/// <typeparam name="T"></typeparam>
class C<T> { }

class Program
{
static void Main() { }
}

L'esempio precedente produce il seguente XML.


<?xml version="1.0"?>
<doc>
<assembly>
<name>CRefTest</name>
</assembly>
<members>
<member name="T:A">
<summary cref="T:C`1">
</summary>
</member>
<member name="T:B">
<summary cref="T:C`1">
</summary>
</member>
<member name="T:C`1">
<summary cref="T:A">
</summary>
<typeparam name="T"></typeparam>
</member>
</members>
</doc>

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<typeparam>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<typeparam name="name">description</typeparam>

Parametri
name

Nome del parametro di tipo. Racchiudere il nome tra virgolette doppie (" ").
description

Descrizione del parametro di tipo.

Commenti
Il tag <typeparam> deve essere usato nel commento per una dichiarazione di tipo o metodo generico per
descrivere un parametro di tipo. Aggiungere un tag per ogni parametro di tipo del tipo o del metodo generico.
Per altre informazioni, vedere Generics.
Il testo del tag <typeparam> verrà visualizzato in IntelliSense, nella finestra Visualizzatore oggetti e nel report
Web sui commenti del codice.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// comment for class


public class TestClass
{
/// <summary>
/// Creates a new array of arbitrary type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The element type of the array</typeparam>
public static T[] mkArray<T>(int n)
{
return new T[n];
}
}

Vedere anche
Informazioni di riferimento su C#
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<typeparamref>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<typeparamref name="name"/>

Parametri
name

Nome del parametro di tipo. Racchiudere il nome tra virgolette doppie (" ").

Commenti
Per altre informazioni sui parametri di tipo in tipi e metodi generici, vedere Generics.
Usare questo tag per consentire ai consumer del file di documentazione di formattare la parola in un modo
specifico, ad esempio in corsivo.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// comment for class


public class TestClass
{
/// <summary>
/// Creates a new array of arbitrary type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The element type of the array</typeparam>
public static T[] mkArray<T>(int n)
{
return new T[n];
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
<value>(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Sintassi
<value>property-description</value>

Parametri
property-description

Descrizione della proprietà.

Commenti
Il <value> tag consente di descrivere il valore rappresentato da una proprietà. Quando si aggiunge una
proprietà tramite la creazione guidata codice nell'ambiente di sviluppo Visual Studio .NET, viene aggiunto un
<summary> tag per la nuova proprietà. È quindi necessario aggiungere manualmente un <value> tag per
descrivere il valore rappresentato dalla proprietà.
Compilare con -doc per elaborare i commenti relativi alla documentazione in un file.

Esempio
// compile with: -doc:DocFileName.xml

/// text for class Employee


public class Employee
{
private string _name;

/// <summary>The Name property represents the employee's name.</summary>


/// <value>The Name property gets/sets the value of the string field, _name.</value>

public string Name


{
get
{
return _name;
}
set
{
_name = value;
}
}
}

/// text for class MainClass


public class MainClass
{
/// text for Main
static void Main()
{
}
}

Vedere anche
Guida per programmatori C#
Tag consigliati per i commenti relativi alla documentazione
Eccezioni e gestione delle eccezioni (Guida per
programmatori C#)
28/01/2021 • 5 minutes to read • Edit Online

Le funzionalità di gestione delle eccezioni del linguaggio C# sono utili per gestire qualsiasi situazione imprevista
o eccezionale che può verificarsi durante l'esecuzione di un programma. La gestione delle eccezioni usa le try
catch finally parole chiave, e per provare le azioni che potrebbero non riuscire, per gestire gli errori quando
si decide che è ragionevole farlo e per pulire le risorse in seguito. Le eccezioni possono essere generate dal
Common Language Runtime (CLR), da librerie .NET o di terze parti o dal codice dell'applicazione. Per creare le
eccezioni viene usata la parola chiave throw .
In molti casi, un'eccezione può essere generata non da un metodo che chiamato direttamente dal codice, ma da
qualsiasi metodo più in basso nello stack di chiamate. Quando viene generata un'eccezione, il CLR rimuove lo
stack, Cerca un metodo con un catch blocco per il tipo di eccezione specifico e ne esegue il primo catch
blocco che se individua. Se non trova un blocco catch appropriato nello stack di chiamate, terminerà il
processo e visualizzerà un messaggio all'utente.
In questo esempio, un metodo verifica la presenza di divisioni per zero e intercetta l'errore. Senza la gestione
delle eccezioni, il programma verrebbe chiuso con un errore DivideByZeroException non è stata gestita .

public class ExceptionTest


{
static double SafeDivision(double x, double y)
{
if (y == 0)
throw new DivideByZeroException();
return x / y;
}

public static void Main()


{
// Input for test purposes. Change the values to see
// exception handling behavior.
double a = 98, b = 0;
double result;

try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}

Panoramica delle eccezioni


Le eccezioni hanno le proprietà seguenti:
Le eccezioni sono tipi che derivano fondamentalmente tutti da System.Exception .
Racchiudere all'interno di un blocco try le istruzioni che potrebbero generare un'eccezione.
Quando si verifica un'eccezione nel blocco try , il flusso di controllo passa al primo gestore delle eccezioni
associato presente in qualsiasi punto nello stack di chiamate. In C#, per definire un gestore di eccezioni viene
usata la parola chiave catch .
Se non è presente alcun gestore di eccezioni per una determinata eccezione, il programma interrompe
l'esecuzione con un messaggio di errore.
Non intercettare un'eccezione a meno che non sia possibile gestirla e lasciare l'applicazione in uno stato
noto. Se si intercetta System.Exception , generare di nuovo l'eccezione tramite la parola chiave throw alla
fine del blocco catch .
Se un blocco catch definisce una variabile di eccezione, è possibile usarla per ottenere altre informazioni sul
tipo di eccezione che si è verificato.
Le eccezioni possono essere generate in modo esplicito da un programma usando la parola chiave throw .
Gli oggetti eccezione contengono informazioni dettagliate sull'errore, ad esempio lo stato dello stack di
chiamate e una descrizione testuale dell'errore.
Il codice in un blocco finally viene eseguito anche se viene generata un'eccezione. Usare un blocco
finally per rilasciare le risorse, ad esempio per chiudere eventuali flussi o file aperti nel blocco try .
Le eccezioni gestite in .NET vengono implementate sul meccanismo di gestione delle eccezioni strutturate
Win32. Per altre informazioni, vedere Gestione strutturata delle eccezioni (C/C++) e A Crash Course on the
Depths of Win32 Structured Exception Handling (Corso intensivo su tutti i concetti fondamentali della
gestione delle eccezioni strutturata in Win32).

Specifiche del linguaggio C#


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

Vedere anche
SystemException
Parole chiave di C#
generare
try-catch
try-finally
try-catch-finally
Eccezioni
Usare le eccezioni (Guida per programmatori C#)
28/01/2021 • 6 minutes to read • Edit Online

In C# gli errori del programma in fase di esecuzione vengono propagati attraverso il programma usando il
meccanismo delle eccezioni. Le eccezioni vengono generate dal codice che rileva un errore e intercettate dal
codice in grado di correggere l'errore. Le eccezioni possono essere generate dal runtime .NET o dal codice in un
programma. Quando viene generata un'eccezione, si propaga nello stack di chiamate finché non viene trovata
un'istruzione catch per l'eccezione. Le eccezioni non rilevate vengono gestite da un gestore di eccezioni
generico del sistema che visualizza una finestra di dialogo.
Le eccezioni sono rappresentate dalle classi derivate da Exception. Questa classe identifica il tipo di eccezione e
contiene proprietà con informazioni sull'eccezione. Generare un'eccezione implica la creazione un'istanza di una
classe derivata dall'eccezione, la configurazione, se necessaria, delle proprietà dell'eccezione e la generazione
dell'oggetto usando la parola chiave throw . Ad esempio:

class CustomException : Exception


{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}

Dopo la generazione di un'eccezione, il runtime controlla l'istruzione corrente per verificare se si trova
all'interno di un blocco try . In questo caso tutti i blocchi catch associati al blocco try vengono controllati
per verificare se è possibile intercettare l'eccezione. I blocchi Catch normalmente specificano i tipi di eccezione.
Se il tipo del blocco catch è lo stesso tipo dell'eccezione o di una classe di base dell'eccezione, il blocco catch
può gestire il metodo. Ad esempio:

try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}

Se l'istruzione che genera un'eccezione non si trova all'interno di un try blocco o se il try blocco che lo
racchiude non ha un catch blocco corrispondente, il runtime controlla il metodo chiamante per un' try
istruzione e i catch blocchi. Il runtime continua a risalire lo stack di chiamate alla ricerca di un blocco catch
compatibile. Quando il blocco catch viene trovato ed eseguito, il controllo passa all'istruzione successiva dopo
quel blocco catch .
Un'istruzione try può contenere più di un blocco catch . La prima catch istruzione in grado di gestire
l'eccezione viene eseguita. tutte le catch istruzioni seguenti, anche se sono compatibili, vengono ignorate. I
blocchi catch devono sempre essere ordinati da quelli più specifici (o più derivati) a meno specifici. Ad esempio:
using System;
using System.IO;

namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}

Prima dell'esecuzione del blocco catch il runtime cerca i blocchi finally . Finally i blocchi consentono al
programmatore di pulire qualsiasi stato ambiguo che può essere lasciato da un blocco interrotto try o di
rilasciare eventuali risorse esterne (ad esempio, handle di grafica, connessioni di database o flussi di file) senza
attendere che il Garbage Collector nel runtime completi gli oggetti. Ad esempio:
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}

try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}

Se viene WriteByte() generata un'eccezione, il codice nel secondo try blocco che tenta di riaprire il file avrà
esito negativo se file.Close() non viene chiamato e il file rimarrà bloccato. Poiché i blocchi finally vengono
eseguiti anche se viene generata un'eccezione, il blocco finally dell'esempio precedente consente di chiudere
correttamente il file e di evitare un errore.
Se non viene trovato alcun blocco catch compatibile nello stack di chiamate dopo la generazione di
un'eccezione, si verifica una delle tre situazioni seguenti:
Se l'eccezione è all'interno di un finalizzatore, il finalizzatore viene interrotto e viene chiamato il finalizzatore
di base, se presente.
Se lo stack di chiamate contiene un costruttore statico o un inizializzatore di campo statico, viene generata
TypeInitializationException con l'eccezione originale assegnata alla proprietà InnerException della nuova
eccezione.
Se viene raggiunto l'inizio del thread, il thread viene terminato.
Gestione delle eccezioni (Guida per programmatori
C#)
28/01/2021 • 8 minutes to read • Edit Online

I programmatori C# usano un blocco try per creare partizioni di codice in cui potrebbe essere rilevata
un'eccezione. I blocchi catch associati vengono usati per gestire tutte le eccezioni risultanti. Un blocco finally
contiene codice che viene eseguito indipendentemente dal fatto che venga generata un'eccezione nel try
blocco, ad esempio il rilascio di risorse allocate nel try blocco. Un blocco try richiede uno o più blocchi
catch associati, un blocco finally o entrambi.

Gli esempi seguenti illustrano un'istruzione try-catch , un'istruzione try-finally e un'istruzione


try-catch-finally .

try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}

try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}

try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}

Un blocco try senza un blocco catch o finally genera un errore del compilatore.

Blocchi catch
Un blocco catch consente di specificare il tipo di eccezione da intercettare. La specifica del tipo è chiamata filtro
eccezioni. Il tipo di eccezione deve essere derivato da Exception. In generale, non specificare Exception come
filtro eccezioni, a meno che non si sia in grado di gestire tutte le eccezioni che potrebbero essere generate nel
try blocco o se è stata inclusa un'istruzione throw alla fine del catch blocco.

catch È possibile concatenare più blocchi con classi di eccezioni diverse. I blocchi catch vengono valutati
dall'alto verso il basso nel codice, ma viene eseguito un solo blocco catch per ogni eccezione generata, in
particolare il primo blocco catch che specifica il tipo esatto o una classe di base dell'eccezione generata. Se
nessun catch blocco specifica una classe Exception corrispondente, catch viene selezionato un blocco che non
dispone di alcun tipo, se presente nell'istruzione. È importante posizionare catch prima i blocchi con le classi di
eccezioni più specifiche, ovvero le più derivate.
Rilevare le eccezioni quando sono soddisfatte le condizioni seguenti:
Si è compreso il motivo per cui è stata generata l'eccezione ed è possibile implementare un recupero
specifico, ad esempio chiedendo all'utente di immettere un nuovo nome di file quando si intercetta un
oggetto FileNotFoundException.
È possibile creare e generare una nuova eccezione più specifica.

int GetInt(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}

Si vuole gestire parzialmente un'eccezione prima di passarla a funzioni di gestione aggiuntive. Nell'esempio
seguente catch viene utilizzato un blocco per aggiungere una voce a un log degli errori prima di rigenerare
l'eccezione.

try
{
// Try to access a resource.
}
catch (UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}

È anche possibile specificare filtri eccezioni per aggiungere un'espressione booleana a una clausola catch. Ciò
indica che una clausola catch specifica corrisponde solo quando la condizione è true. Nell'esempio seguente,
entrambe le clausole catch utilizzano la stessa classe Exception, ma viene verificata una condizione aggiuntiva
per creare un messaggio di errore diverso:
int GetInt(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException e) when (index < 0)
{
throw new ArgumentOutOfRangeException(
"Parameter index cannot be negative.", e);
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index cannot be greater than the array size.", e);
}
}

Un filtro eccezioni che restituisce sempre false può essere utilizzato per esaminare tutte le eccezioni ma non
elaborarle. Un uso tipico consiste nel registrare le eccezioni:

public static void Main()


{
try
{
string? s = null;
Console.WriteLine(s.Length);
}
catch (Exception e) when (LogException(e))
{
}
Console.WriteLine("Exception must have been handled");
}

private static bool LogException(Exception e)


{
Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
Console.WriteLine($"\tMessage: {e.Message}");
return false;
}

Il LogException metodo restituisce sempre false , nessuna catch clausola che utilizza questo filtro delle
eccezioni corrisponde a. La clausola catch può essere generale, usando System.Exception e le clausole successive
possono elaborare classi di eccezioni più specifiche.

Blocchi finally
Un blocco finally consente la pulizia delle azioni eseguite in un blocco try . Se presente, il blocco finally
viene eseguito per ultimo, dopo il blocco try e i blocchi catch corrispondenti. Un finally blocco viene
sempre eseguito, indipendentemente dal fatto che venga generata un'eccezione o che catch venga trovato un
blocco corrispondente al tipo di eccezione.
Il blocco finally può essere usato per rilasciare risorse quali flussi di file, connessioni di database e handle di
elementi grafici, senza attendere che il Garbage Collector del runtime finalizzi gli oggetti. Per ulteriori
informazioni, vedere l' istruzione using.
Nell'esempio riportato di seguito viene usato il blocco finally per chiudere un file aperto nel blocco try . Si
noti che prima della chiusura viene controllato lo stato dell'handle del file. Se il try blocco non è in grado di
aprire il file, l'handle di file ha ancora il valore null e il finally blocco non tenta di chiuderlo. Al contrario, se il
file viene aperto correttamente nel try blocco, il finally blocco chiude il file aperto.

FileStream? file = null;


FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Check for null because OpenWrite might have failed.
file?.Close();
}

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
try-catch
try-finally
try-catch-finally
Istruzione using
Creazione e generazione di eccezioni (Guida per
programmatori C#)
28/01/2021 • 6 minutes to read • Edit Online

Le eccezioni vengono usate per indicare che si è verificato un errore durante l'esecuzione del programma.
Vengono creati oggetti eccezione che descrivono un errore e quindi generati con la parola chiave throw. Il
runtime cerca quindi il gestore di eccezioni più compatibile.
I programmatori devono generare eccezioni quando una o più delle condizioni seguenti sono true:
Il metodo non può completare la funzionalità definita. Ad esempio, se un parametro verso un metodo ha un
valore non valido:

static void CopyObject(SampleClass original)


{
_ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
}

Viene eseguita una chiamata non appropriata a un oggetto, in base allo stato dell'oggetto. Un esempio
potrebbe essere il tentativo di scrivere su un file di sola lettura. Nei casi in cui lo stato di un oggetto non
consente un'operazione, generare un'istanza di InvalidOperationException o un oggetto basato su una
derivazione di questa classe. Il codice seguente è un esempio di un metodo che genera un
InvalidOperationException oggetto:

public class ProgramLog


{
FileStream logFile = null!;
public void OpenLog(FileInfo fileName, FileMode mode) { }

public void WriteLog()


{
if (!logFile.CanWrite)
{
throw new InvalidOperationException("Logfile cannot be read-only");
}
// Else write data to the log and return.
}
}

Quando un argomento verso un metodo genera un'eccezione. In questo caso, è necessario intercettare
l'eccezione originale e creare un'istanza di ArgumentException. L'eccezione originale deve essere passata al
costruttore di ArgumentException come parametro InnerException:
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException ex)
{
throw new ArgumentException("Index is out of range", nameof(index), ex);
}
}

Le eccezioni contengono una proprietà denominata StackTrace. Questa stringa contiene il nome dei metodi nello
stack di chiamate corrente, insieme al numero di riga e al nome del file in cui è stata generata l'eccezione per
ogni metodo. Viene creato in automatico un oggetto StackTrace da Common Language Runtime (CLR) dal punto
dell'istruzione throw , in modo tale che le eccezioni debbano essere generate dal punto in cui deve iniziare
l'analisi dello stack.
Tutte le eccezioni contengono una proprietà denominata Message. Questa stringa deve essere impostata per
spiegare il motivo dell'eccezione. Le informazioni sensibili alla sicurezza non devono essere inserite nel testo del
messaggio. Oltre a Message, ArgumentException contiene una proprietà denominata ParamName che deve
essere impostata sul nome dell'argomento che ha causato la generazione dell'eccezione. In un setter di
proprietà ParamName deve essere impostato su value .
I metodi Public e protected generano eccezioni ogni volta che non riescono a completare le funzioni desiderate.
La classe Exception generata è l'eccezione più specifica disponibile che soddisfa le condizioni di errore. Queste
eccezioni devono essere documentate come parte delle funzionalità della classe e le classi derivate o gli
aggiornamenti per la classe originale devono conservare lo stesso comportamento per la compatibilità con le
versioni precedenti.

Comportamenti da evitare per la generazione di eccezioni


L'elenco seguente include operazioni da evitare durante la generazione di eccezioni:
Non usare eccezioni per modificare il flusso di un programma come parte dell'esecuzione normale. Utilizzare
le eccezioni per segnalare e gestire le condizioni di errore.
Le eccezioni non devono essere restituite come valore restituito o parametro anziché essere generate.
Non generare System.Exception , System.SystemException , System.NullReferenceException o
System.IndexOutOfRangeException intenzionalmente dal codice sorgente.
Non creare eccezioni che possono essere generate in modalità di debug, ma non in modalità di rilascio. Per
identificare gli errori di run-time durante la fase di sviluppo, usare il metodo di asserzione di debug.

Definizione delle classi di eccezioni


I programmi possono generare una classe di eccezione predefinita nello spazio dei nomi System, tranne nei casi
indicati in precedenza, oppure creare le proprie classi di eccezione derivando da Exception. Le classi derivate
devono definire almeno quattro costruttori: un costruttore senza parametri, uno che imposta la proprietà del
messaggio e uno che imposta entrambe le proprietà Message e InnerException. Il quarto costruttore viene usato
per serializzare l'eccezione. Le nuove classi di eccezione devono essere serializzabili. Ad esempio:
[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }

// A constructor is needed for serialization when an


// exception propagates from a remoting server to the client.
protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}

Aggiungere nuove proprietà alla classe Exception quando i dati forniti sono utili per risolvere l'eccezione. Se
vengono aggiunte nuove proprietà alla classe di eccezioni derivata, ToString() deve essere sottoposto a
override per restituire le informazioni aggiunte.

Specifiche del linguaggio C#


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

Vedere anche
Gerarchia delle eccezioni
Eccezioni generate dal compilatore (Guida per
programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Alcune eccezioni vengono generate automaticamente dal runtime .NET quando le operazioni di base hanno
esito negativo. Nella tabella seguente sono elencate queste eccezioni e le relative condizioni di errore.

EC C EZ IO N E DESC RIZ IO N E

ArithmeticException Classe di base per le eccezioni che si verificano durante


operazioni aritmetiche, quali DivideByZeroException e
OverflowException.

ArrayTypeMismatchException Generata quando una matrice non può archiviare un dato


elemento perché il tipo effettivo dell'elemento non è
compatibile con il tipo effettivo della matrice.

DivideByZeroException Generata quando viene eseguito un tentativo di dividere un


valore integrale per zero.

IndexOutOfRangeException Generata quando viene eseguito un tentativo di indicizzare


una matrice, quando l'indice è minore di zero o supera i limiti
della matrice.

InvalidCastException Generata quando una conversione esplicita dal tipo di base a


un'interfaccia o a un tipo derivato ha esito negativo in fase di
runtime.

NullReferenceException Generata quando viene eseguito un tentativo di fare


riferimento a un oggetto il cui valore è null.

OutOfMemoryException Generata quando un tentativo di allocazione della memoria


tramite l'operatore new ha esito negativo. Questa eccezione
indica che la memoria disponibile per il Common Language
Runtime è stata esaurita.

OverflowException Generata quando si verifica un overflow di un'operazione


aritmetica in un contesto checked .

StackOverflowException Generata quando viene esaurito lo stack di esecuzione da un


numero eccessivo di chiamate in sospeso verso il metodo; in
genere indica un problema di ricorsione molto profondo o
infinito.

TypeInitializationException Generata quando un costruttore statico genera un'eccezione


e non è presente una clausola catch compatibile per
intercettarla.

Vedere anche
try-catch
try-finally
try-catch-finally
Come gestire un'eccezione usando try/catch (Guida
per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Lo scopo di un blocco try-catch è quello di rilevare e gestire un'eccezione generata da codice in esecuzione.
Alcune eccezioni possono essere gestite in un catch blocco e il problema è stato risolto senza che sia stata
rigenerata l'eccezione. Tuttavia, più spesso l'unica cosa che è possibile fare è verificare che venga generata
l'eccezione appropriata.

Esempio
In questo esempio IndexOutOfRangeException non è l'eccezione più appropriata:
ArgumentOutOfRangeException è più utile per il metodo perché l'errore è causato dall' index argomento
passato dal chiamante.

static int GetInt(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e) // CS0168
{
Console.WriteLine(e.Message);
// Set IndexOutOfRangeException to the new exception's InnerException.
throw new ArgumentOutOfRangeException("index parameter is out of range.", e);
}
}

Commenti
Il codice che genera un'eccezione è racchiuso nel blocco try . Subito dopo viene aggiunta un'istruzione catch
per gestire IndexOutOfRangeException , se si verifica. Il blocco catch gestisce l'eccezione
IndexOutOfRangeException e genera al suo posto l'eccezione più appropriata ArgumentOutOfRangeException . Per
offrire al chiamante quante più informazioni possibili, specificare l'eccezione originale come InnerException della
nuova eccezione. Poiché la InnerException proprietà è di sola lettura, è necessario assegnarla nel costruttore
della nuova eccezione.
Come eseguire il codice di pulizia con finally (Guida
per programmatori C#)
28/01/2021 • 2 minutes to read • Edit Online

Lo scopo di un'istruzione finally consiste nel garantire che la pulizia necessaria di oggetti, in genere oggetti
che contengono risorse esterne, venga eseguita immediatamente, anche se viene generata un'eccezione. Un
esempio di questo tipo di pulizia è la chiamata di Close in un oggetto FileStream immediatamente dopo l'uso,
invece di aspettare che l'oggetto venga sottoposto a Garbage Collection da Common Language Runtime, come
illustrato di seguito:

static void CodeWithoutCleanup()


{
FileStream? file = null;
FileInfo fileInfo = new FileInfo("./file.txt");

file = fileInfo.OpenWrite();
file.WriteByte(0xF);

file.Close();
}

Esempio
Per trasformare il codice precedente in un'istruzione try-catch-finally , il codice di pulitura viene separato dal
codice di lavoro, come indicato di seguito.

static void CodeWithCleanup()


{
FileStream? file = null;
FileInfo? fileInfo = null;

try
{
fileInfo = new FileInfo("./file.txt");

file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
finally
{
file?.Close();
}
}

Poiché un'eccezione può verificarsi in qualsiasi momento all'interno del try blocco prima della OpenWrite()
chiamata o la OpenWrite() chiamata stessa potrebbe non riuscire, non è garantito che il file sia aperto quando si
tenta di chiuderlo. Il finally blocco aggiunge un controllo per assicurarsi che l' FileStream oggetto non sia
null prima di chiamare il Close metodo. Senza il null controllo, il finally blocco potrebbe generare la
propria NullReferenceException , ma la generazione di eccezioni nei finally blocchi deve essere evitata, se
possibile.
Una connessione di database è un altro elemento ideale da chiudere in un blocco finally . Poiché il numero di
connessioni consentite in un server di database è talvolta limitato, è necessario chiudere le connessioni di
database il più rapidamente possibile. Se viene generata un'eccezione prima che sia possibile chiudere la
connessione, l'utilizzo del finally blocco è preferibile rispetto all'attesa Garbage Collection.

Vedere anche
Istruzione using
try-catch
try-finally
try-catch-finally
Come intercettare un'eccezione non CLS
02/11/2020 • 2 minutes to read • Edit Online

Alcuni linguaggi .NET, inclusi C++ /CLI, consentono agli oggetti di generare eccezioni che non derivano da
Exception. Tali eccezioni sono chiamate eccezioni non CLS o non eccezioni. In C# non è possibile generare
eccezioni non CLS, ma è possibile rilevarle in due modi:
All'interno di un blocco catch (RuntimeWrappedException e) .
Per impostazione predefinita, un assembly Visual C# rileva le eccezioni non CLS come eccezioni con
wrapper. Usare questo metodo se è necessario accedere all'eccezione originale, accessibile tramite la
proprietà RuntimeWrappedException.WrappedException. La procedura illustrata più avanti in questo
argomento spiega come rilevare le eccezioni in questo modo.
All'interno di un blocco catch generale, ovvero un blocco catch senza un tipo di eccezione specificato,
posizionato dopo tutti i blocchi catch .
Usare questo metodo quando si vuole eseguire un'azione, come ad esempio la scrittura in un file di log,
in risposta alle eccezioni non CLS e non è necessario accedere alle informazioni dell'eccezione. Per
impostazione predefinita, Common Language Runtime esegue il wrapping di tutte le eccezioni. Per
disabilitare questo comportamento, aggiungere l'attributo a livello di assembly seguente al codice, in
genere nel file AssemblyInfo.cs:
[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)] .

Per rilevare un'eccezione non CLS


All'interno di un blocco catch(RuntimeWrappedException e) accedere all'eccezione originale tramite la proprietà
RuntimeWrappedException.WrappedException.

Esempio
L'esempio seguente illustra come rilevare un'eccezione non CLS generata da una libreria di classi scritta in
C++/CLI. Si noti che in questo esempio il codice client C# sa in anticipo che il tipo di eccezione generato è
System.String. È possibile eseguire il cast della proprietà RuntimeWrappedException.WrappedException al tipo
originale, a condizione che tale tipo sia accessibile dal codice.

// Class library written in C++/CLI.


var myClass = new ThrowNonCLS.Class1();

try
{
// throws gcnew System::String(
// "I do not derive from System.Exception!");
myClass.TestThrow();
}
catch (RuntimeWrappedException e)
{
String s = e.WrappedException as String;
if (s != null)
{
Console.WriteLine(s);
}
}
Vedere anche
RuntimeWrappedException
Eccezioni e gestione delle eccezioni
File System e registro di sistema (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Gli articoli seguenti illustrano come usare C# e .NET per eseguire varie operazioni di base su file, cartelle e
registro di sistema.

Contenuto della sezione


T IT L E DESC RIZ IO N E

Come scorrere un albero di directory Viene mostrato come scorrere manualmente una struttura
ad albero di directory.

Come ottenere informazioni relative a file, cartelle e unità Viene mostrato come recuperare informazioni relative a file,
cartelle e unità, ad esempio la data di creazione e la
dimensione.

Come creare un file o una cartella Viene mostrato come creare un nuovo file o una nuova
cartella.

Come copiare, eliminare e spostare file e cartelle (Guida per Viene mostrato come copiare, eliminare e spostare file e
programmatori C#) cartelle.

Come fornire una finestra di dialogo dello stato di Viene mostrato come visualizzare una finestra di stato
avanzamento per operazioni su file Windows standard per determinate operazioni sui file.

Come scrivere in un file di testo Viene mostrato come scrivere in un file di testo.

Come leggere da un file di testo Viene mostrato come leggere da un file di testo.

Come leggere un file di testo una riga alla volta Viene mostrato come recuperare testo da un file una riga
alla volta.

Come creare una chiave nel Registro di sistema Viene mostrato come scrivere una chiave nel Registro di
sistema.

Sezioni correlate
I/O di file e di flussi
Come copiare, eliminare e spostare file e cartelle (Guida per programmatori C#)
Guida per programmatori C#
System.IO
Come scorrere un albero di directory (Guida per
programmatori C#)
02/11/2020 • 10 minutes to read • Edit Online

Eseguire l'iterazione in un albero di directory significa accedere a ogni file in ogni sottodirectory annidata in una
cartella radice specificata, a qualsiasi livello. Non è necessario aprire ogni file. È possibile recuperare
semplicemente il nome del file o della sottodirectory come string oppure è possibile recuperare informazioni
aggiuntive sotto forma di oggetto System.IO.FileInfo o System.IO.DirectoryInfo.

NOTE
In Windows i termini "directory" e "cartella" vengono usati indifferentemente. La maggior parte della documentazione e
del testo dell'interfaccia utente usa il termine "cartella", ma le librerie di classi .NET usano il termine "directory".

Nel caso più semplice in cui si è certi di avere autorizzazioni di accesso per tutte le directory di una radice
specificata, è possibile usare il flag System.IO.SearchOption.AllDirectories . Questo flag restituisce tutte le
sottodirectory annidate che corrispondono al modello specificato. L'esempio seguente illustra come usare
questo flag.

root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);

Il problema di questo approccio è che se una delle sottodirectory della radice specificata genera un'eccezione
DirectoryNotFoundException o UnauthorizedAccessException, l'intero metodo ha esito negativo e non
restituisce alcuna directory. Lo stesso vale quando si usa il metodo GetFiles. Se è necessario gestire queste
eccezioni in sottocartelle specifiche, procedere manualmente nell'albero di directory, come illustrato negli
esempi seguenti.
Quando si procede manualmente in un albero di directory, è possibile gestire prima le sottodirectory
(attraversamento pre-ordine), o prima i file (attraversamento post-ordine). Se si esegue un attraversamento pre-
ordine, è necessario procedere lungo l'intero albero della cartella corrente prima di eseguire l'iterazione nei file
direttamente in quella cartella. Gli esempi riportati più avanti nel documento eseguono l'attraversamento post-
ordine ma è possibile modificarli per eseguire l'attraversamento pre-ordine.
Un'altra opzione consiste nell'usare la ricorsione o l'attraversamento basato su stack. Gli esempi più avanti in
questo documento illustrano entrambi gli approcci.
Se è necessario eseguire una serie di operazioni su file e cartelle, è possibile modularizzare questi esempi
effettuando il refactoring dell'operazione in funzioni separate che è possibile richiamare tramite un delegato
unico.

NOTE
I file system NTFS possono contenere reparse point sotto forma di punti di giunzione, collegamenti simbolici e
collegamenti reali. I metodi .NET, ad esempio GetFiles e, GetDirectories non restituiscono alcuna sottodirectory in un
reparse point. Questo comportamento protegge dal rischio di entrare in un ciclo infinito quando due reparse point fanno
riferimento uno all'altro. In generale, è necessario usare estrema cautela quando si usano i reparse point per evitare di
modificare o eliminare file involontariamente. Se è necessario un controllo preciso dei reparse point, usare platform invoke
o codice nativo per chiamare direttamente i metodi del file system Win32 appropriato.
Esempio
L'esempio seguente illustra come procedere in un albero di directory usando la ricursione. L'approccio ricorsivo
è elegante ma può provocare un'eccezione di overflow dello stack se l'albero di directory è grande ed
eccessivamente annidato.
Le eccezioni specifiche che vengono gestite e le azioni specifiche eseguite su ogni file o cartella vengono
illustrate solo a titolo esemplificativo. Per soddisfare esigenze specifiche, è necessario modificare il codice. Per
altre informazioni, vedere i commenti nel codice.

public class RecursiveFileSearch


{
static System.Collections.Specialized.StringCollection log = new
System.Collections.Specialized.StringCollection();

static void Main()


{
// Start with drives if you have to search the entire computer.
string[] drives = System.Environment.GetLogicalDrives();

foreach (string dr in drives)


{
System.IO.DriveInfo di = new System.IO.DriveInfo(dr);

// Here we skip the drive if it is not ready to be read. This


// is not necessarily the appropriate action in all scenarios.
if (!di.IsReady)
{
Console.WriteLine("The drive {0} could not be read", di.Name);
continue;
}
System.IO.DirectoryInfo rootDir = di.RootDirectory;
WalkDirectoryTree(rootDir);
}

// Write out all the files that could not be processed.


Console.WriteLine("Files with restricted access:");
foreach (string s in log)
{
Console.WriteLine(s);
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key");
Console.ReadKey();
}

static void WalkDirectoryTree(System.IO.DirectoryInfo root)


{
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;

// First, process all the files directly under this folder


try
{
files = root.GetFiles("*.*");
}
// This is thrown if even one of the files requires permissions greater
// than the application provides.
catch (UnauthorizedAccessException e)
{
// This code just writes out the message and continues to recurse.
// You may decide to do something different here. For example, you
// can try to elevate your privileges and access the file again.
log.Add(e.Message);
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
}

if (files != null)
{
foreach (System.IO.FileInfo fi in files)
{
// In this example, we only access the existing FileInfo object. If we
// want to open, delete or modify the file, then
// a try-catch block is required here to handle the case
// where the file has been deleted since the call to TraverseTree().
Console.WriteLine(fi.FullName);
}

// Now find all the subdirectories under this directory.


subDirs = root.GetDirectories();

foreach (System.IO.DirectoryInfo dirInfo in subDirs)


{
// Resursive call for each subdirectory.
WalkDirectoryTree(dirInfo);
}
}
}
}

Esempio
Nell'esempio seguente viene illustrato come eseguire l'iterazione nei file e nelle cartelle in un albero di directory
senza usare la ricorsione. Questa tecnica usa il tipo di raccolta generico Stack<T>, che è uno stack LIFO (Last In
First Out).
Le eccezioni specifiche che vengono gestite e le azioni specifiche eseguite su ogni file o cartella vengono
illustrate solo a titolo esemplificativo. Per soddisfare esigenze specifiche, è necessario modificare il codice. Per
altre informazioni, vedere i commenti nel codice.

public class StackBasedIteration


{
static void Main(string[] args)
{
// Specify the starting folder on the command line, or in
// Visual Studio in the Project > Properties > Debug pane.
TraverseTree(args[0]);

Console.WriteLine("Press any key");


Console.ReadKey();
}

public static void TraverseTree(string root)


{
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);

if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);

while (dirs.Count > 0)


{
string currentDir = dirs.Pop();
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = System.IO.Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable
// to ignore the exception and continue enumerating the remaining files and
// folders. It is also possible (but unlikely) that a DirectoryNotFound exception
// will be raised. This will happen if currentDir has been deleted by
// another application or thread after our call to Directory.Exists. The
// choice of which exceptions to catch depends entirely on the specific task
// you are intending to perform and also on how much you know with certainty
// about the systems on which this code will run.
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}

string[] files = null;


try
{
files = System.IO.Directory.GetFiles(currentDir);
}

catch (UnauthorizedAccessException e)
{

Console.WriteLine(e.Message);
continue;
}

catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
}
catch (System.IO.FileNotFoundException e)
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
Console.WriteLine(e.Message);
continue;
}
}

// Push the subdirectories onto the stack for traversal.


// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
}
}
}

In genere testare ogni cartella per determinare se l'applicazione ha l'autorizzazione per aprirla richiede troppo
tempo. Pertanto, l'esempio di codice racchiude quella parte dell'operazione in un blocco try/catch . È possibile
modificare il blocco catch in modo che quando l'accesso a una cartella viene negato, si tenti di innalzare il
livello delle autorizzazioni e quindi di accedere nuovamente. Come regola, rilevare solo le eccezioni che è
possibile gestire senza lasciare l'applicazione in uno stato sconosciuto.
Se è necessario archiviare il contenuto di un albero di directory, in memoria o su disco, l'opzione migliore
consiste nell'archiviare solo la proprietà FullName (di tipo string ) per ogni file. È quindi possibile usare questa
stringa per creare un nuovo oggetto FileInfo o DirectoryInfo in base alle esigenze o aprire i file che richiedono
un'elaborazione ulteriore.

Programmazione efficiente
Un codice di iterazione file efficiente deve prendere in considerazione diversi aspetti complessi del file system.
Per altre informazioni sul file system di Windows, vedere NTFS Technical Reference (Informazioni tecniche di
riferimento su NTFS).

Vedere anche
System.IO
Directory di file e LINQ
File system e Registro di sistema (Guida per programmatori C#)
Come ottenere informazioni su file, cartelle e unità
(Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

In .NET è possibile accedere alle informazioni file system usando le classi seguenti:
System.IO.FileInfo
System.IO.DirectoryInfo
System.IO.DriveInfo
System.IO.Directory
System.IO.File
Le classi FileInfo e DirectoryInfo rappresentano un file o una directory e contengono proprietà che espongono
molti degli attributi di file supportati dal file system NTFS. Contengono anche i metodi per l'apertura, la
chiusura, lo spostamento e l'eliminazione di file e cartelle. È possibile creare istanze di queste classi passando al
costruttore una stringa che rappresenta il nome del file, della cartella o dell'unità:

System.IO.DriveInfo di = new System.IO.DriveInfo(@"C:\");

È anche possibile ottenere i nomi di file, cartelle o unità tramite chiamate a DirectoryInfo.GetDirectories,
DirectoryInfo.GetFiles e DriveInfo.RootDirectory.
Le classi System.IO.Directory e System.IO.File forniscono metodi statici per il recupero di informazioni su file e
directory.

Esempio
L'esempio seguente illustra vari modi per accedere alle informazioni su file e cartelle.

class FileSysInfo
{
static void Main()
{
// You can also use System.Environment.GetLogicalDrives to
// obtain names of all logical drives on the computer.
System.IO.DriveInfo di = new System.IO.DriveInfo(@"C:\");
Console.WriteLine(di.TotalFreeSpace);
Console.WriteLine(di.VolumeLabel);

// Get the root directory and print out some information about it.
System.IO.DirectoryInfo dirInfo = di.RootDirectory;
Console.WriteLine(dirInfo.Attributes.ToString());

// Get the files in the directory and print out some information about them.
System.IO.FileInfo[] fileNames = dirInfo.GetFiles("*.*");

foreach (System.IO.FileInfo fi in fileNames)


{
Console.WriteLine("{0}: {1}: {2}", fi.Name, fi.LastAccessTime, fi.Length);
}

// Get the subdirectories directly that is under the root.


// See "How to: Iterate Through a Directory Tree" for an example of how to
// iterate through an entire tree.
System.IO.DirectoryInfo[] dirInfos = dirInfo.GetDirectories("*.*");

foreach (System.IO.DirectoryInfo d in dirInfos)


{
Console.WriteLine(d.Name);
}

// The Directory and File classes provide several static methods


// for accessing files and directories.

// Get the current application directory.


string currentDirName = System.IO.Directory.GetCurrentDirectory();
Console.WriteLine(currentDirName);

// Get an array of file names as strings rather than FileInfo objects.


// Use this method when storage space is an issue, and when you might
// hold on to the file name reference for a while before you try to access
// the file.
string[] files = System.IO.Directory.GetFiles(currentDirName, "*.txt");

foreach (string s in files)


{
// Create the FileInfo object only when needed to ensure
// the information is as current as possible.
System.IO.FileInfo fi = null;
try
{
fi = new System.IO.FileInfo(s);
}
catch (System.IO.FileNotFoundException e)
{
// To inform the user and continue is
// sufficient for this demonstration.
// Your application may require different behavior.
Console.WriteLine(e.Message);
continue;
}
Console.WriteLine("{0} : {1}",fi.Name, fi.Directory);
}

// Change the directory. In this case, first check to see


// whether it already exists, and create it if it does not.
// If this is not appropriate for your application, you can
// handle the System.IO.IOException that will be raised if the
// directory cannot be found.
if (!System.IO.Directory.Exists(@"C:\Users\Public\TestFolder\"))
{
System.IO.Directory.CreateDirectory(@"C:\Users\Public\TestFolder\");
}

System.IO.Directory.SetCurrentDirectory(@"C:\Users\Public\TestFolder\");

currentDirName = System.IO.Directory.GetCurrentDirectory();
Console.WriteLine(currentDirName);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

Programmazione efficiente
Quando si elaborano stringhe di percorso specificate dall'utente, occorre gestire anche le eccezioni per le
condizioni seguenti:
Il nome file non è valido. Ad esempio, contiene caratteri non validi o solo spazi vuoti.
Il nome del file è Null.
La lunghezza del nome del file è maggiore della lunghezza massima definita nel sistema.
Il nome del file contiene i due punti (:).
Se l'applicazione non ha autorizzazioni sufficienti per leggere il file specificato, il metodo Exists restituisce
false indipendentemente dal fatto che il percorso esista. Il metodo non genera un'eccezione.

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come creare un file o una cartella (Guida per
programmatori C#)
02/11/2020 • 5 minutes to read • Edit Online

A livello di codice è possibile creare una cartella, una sottocartella e un file nella sottocartella e quindi scrivere
dati nel file.

Esempio
public class CreateFileOrFolder
{
static void Main()
{
// Specify a name for your top-level folder.
string folderName = @"c:\Top-Level Folder";

// To create a string that specifies the path to a subfolder under your


// top-level folder, add a name for the subfolder to folderName.
string pathString = System.IO.Path.Combine(folderName, "SubFolder");

// You can write out the path name directly instead of using the Combine
// method. Combine just makes the process easier.
string pathString2 = @"c:\Top-Level Folder\SubFolder2";

// You can extend the depth of your path if you want to.
//pathString = System.IO.Path.Combine(pathString, "SubSubFolder");

// Create the subfolder. You can verify in File Explorer that you have this
// structure in the C: drive.
// Local Disk (C:)
// Top-Level Folder
// SubFolder
System.IO.Directory.CreateDirectory(pathString);

// Create a file name for the file you want to create.


string fileName = System.IO.Path.GetRandomFileName();

// This example uses a random string for the name, but you also can specify
// a particular name.
//string fileName = "MyNewFile.txt";

// Use Combine again to add the file name to the path.


pathString = System.IO.Path.Combine(pathString, fileName);

// Verify the path that you have constructed.


Console.WriteLine("Path to my file: {0}\n", pathString);

// Check that the file doesn't already exist. If it doesn't exist, create
// the file and write integers 0 - 99 to it.
// DANGER: System.IO.File.Create will overwrite the file if it already exists.
// This could happen even with random file names, although it is unlikely.
if (!System.IO.File.Exists(pathString))
{
using (System.IO.FileStream fs = System.IO.File.Create(pathString))
{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
}
}
}
}
else
{
Console.WriteLine("File \"{0}\" already exists.", fileName);
return;
}

// Read and display the data from your file.


try
{
byte[] readBuffer = System.IO.File.ReadAllBytes(pathString);
foreach (byte b in readBuffer)
{
Console.Write(b + " ");
}
Console.WriteLine();
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
// Sample output:

// Path to my file: c:\Top-Level Folder\SubFolder\ttxvauxe.vv0

//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
//30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
// 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 8
//3 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
}

Se la cartella esiste già, CreateDirectory non esegue alcuna operazione e non viene generata alcuna eccezione.
File.Create, tuttavia, sostituisce un file esistente con un nuovo file. Nell'esempio viene usata un'istruzione if -
else per evitare la sostituzione di un file esistente.

Apportando le modifiche seguenti nell'esempio, è possibile specificare risultati diversi in base all'esistenza o
meno di un file con un determinato nome. Se il file non esiste, il codice ne crea uno. Se il file esiste, il codice
aggiunge i dati a questo.
Specificare un nome file non casuale.

// Comment out the following line.


//string fileName = System.IO.Path.GetRandomFileName();

// Replace that line with the following assignment.


string fileName = "MyNewFile.txt";

Sostituire l'istruzione if - else con l'istruzione using riportata nel codice seguente.

using (System.IO.FileStream fs = new System.IO.FileStream(pathString, FileMode.Append))


{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
}
}
Eseguire l'esempio più volte per verificare che i dati vengano aggiunti al file ogni volta.
Per altri valori FileMode da provare, vedere FileMode.
Le seguenti condizioni possono generare un'eccezione:
Il formato del nome della cartella non è valido. Ad esempio, contiene caratteri non validi o è costituito
solo da spazi (classe ArgumentException). Per creare nomi di percorso validi, usare la classe Path.
La cartella padre della cartella da creare è di sola lettura (classe IOException).
Il nome della cartella è null (classe ArgumentNullException).
Il nome della cartella è troppo lungo (classe PathTooLongException).
Il nome della cartella è composto solo da un carattere due punti, ":" (classe PathTooLongException).

Sicurezza .NET
In situazioni di attendibilità parziale può essere generata un'istanza della classe SecurityException.
Se non si dispone dell'autorizzazione per creare la cartella, l'esempio genera un'istanza della
UnauthorizedAccessException classe.

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come copiare, eliminare e spostare file e cartelle
(Guida per programmatori C#)
02/11/2020 • 4 minutes to read • Edit Online

Gli esempi seguenti mostrano come copiare, spostare ed eliminare file e cartelle in modo sincrono usando le
classi System.IO.File, System.IO.Directory, System.IO.FileInfo e System.IO.DirectoryInfo dello spazio dei nomi
System.IO. Questi esempi non forniscono un indicatore di stato o altri elementi di interfaccia utente. Se si
desidera fornire una finestra di dialogo di stato standard, vedere come fornire una finestra di dialogo di stato
per le operazioni sui file.
Usare System.IO.FileSystemWatcher per fornire eventi che consentano di calcolare lo stato quando si opera su
più file. Un altro approccio consiste nell'usare pInvoke per chiamare i metodi correlati ai file pertinenti nella shell
di Windows. Per informazioni su come eseguire queste operazioni sui file in modo asincrono, vedere I/O di file
asincrono.

Esempio
L'esempio seguente illustra come copiare file e directory.
// Simple synchronous file copy operations with no user interface.
// To run this sample, first create the following directories and files:
// C:\Users\Public\TestFolder
// C:\Users\Public\TestFolder\test.txt
// C:\Users\Public\TestFolder\SubDir\test.txt
public class SimpleFileCopy
{
static void Main()
{
string fileName = "test.txt";
string sourcePath = @"C:\Users\Public\TestFolder";
string targetPath = @"C:\Users\Public\TestFolder\SubDir";

// Use Path class to manipulate file and directory paths.


string sourceFile = System.IO.Path.Combine(sourcePath, fileName);
string destFile = System.IO.Path.Combine(targetPath, fileName);

// To copy a folder's contents to a new location:


// Create a new target folder.
// If the directory already exists, this method does not create a new directory.
System.IO.Directory.CreateDirectory(targetPath);

// To copy a file to another location and


// overwrite the destination file if it already exists.
System.IO.File.Copy(sourceFile, destFile, true);

// To copy all the files in one directory to another directory.


// Get the files in the source folder. (To recursively iterate through
// all subfolders under the current directory, see
// "How to: Iterate Through a Directory Tree.")
// Note: Check for target path was performed previously
// in this code example.
if (System.IO.Directory.Exists(sourcePath))
{
string[] files = System.IO.Directory.GetFiles(sourcePath);

// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
fileName = System.IO.Path.GetFileName(s);
destFile = System.IO.Path.Combine(targetPath, fileName);
System.IO.File.Copy(s, destFile, true);
}
}
else
{
Console.WriteLine("Source path does not exist!");
}

// Keep console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

Esempio
L'esempio seguente illustra come spostare file e directory.
// Simple synchronous file move operations with no user interface.
public class SimpleFileMove
{
static void Main()
{
string sourceFile = @"C:\Users\Public\public\test.txt";
string destinationFile = @"C:\Users\Public\private\test.txt";

// To move a file or folder to a new location:


System.IO.File.Move(sourceFile, destinationFile);

// To move an entire directory. To programmatically modify or combine


// path strings, use the System.IO.Path class.
System.IO.Directory.Move(@"C:\Users\Public\public\test\", @"C:\Users\Public\private");
}
}

Esempio
L'esempio seguente illustra come eliminare file e directory.

// Simple synchronous file deletion operations with no user interface.


// To run this sample, create the following files on your drive:
// C:\Users\Public\DeleteTest\test1.txt
// C:\Users\Public\DeleteTest\test2.txt
// C:\Users\Public\DeleteTest\SubDir\test2.txt

public class SimpleFileDelete


{
static void Main()
{
// Delete a file by using File class static method...
if(System.IO.File.Exists(@"C:\Users\Public\DeleteTest\test.txt"))
{
// Use a try block to catch IOExceptions, to
// handle the case of the file already being
// opened by another process.
try
{
System.IO.File.Delete(@"C:\Users\Public\DeleteTest\test.txt");
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
return;
}
}

// ...or by using FileInfo instance method.


System.IO.FileInfo fi = new System.IO.FileInfo(@"C:\Users\Public\DeleteTest\test2.txt");
try
{
fi.Delete();
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}

// Delete a directory. Must be writable or empty.


try
{
System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest");
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}
// Delete a directory and all subdirectories with Directory static method...
if(System.IO.Directory.Exists(@"C:\Users\Public\DeleteTest"))
{
try
{
System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest", true);
}

catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}
}

// ...or with DirectoryInfo instance method.


System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(@"C:\Users\Public\public");
// Delete this dir and all subdirs.
try
{
di.Delete(true);
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}
}
}

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come fornire una finestra di dialogo dello stato di avanzamento per operazioni su file
I/O di file e di flussi
Attività di I/O comuni
Come fornire una finestra di dialogo di
avanzamento per operazioni su file (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile fornire una finestra di dialogo standard che mostra lo stato di avanzamento delle operazioni sui file
in Windows se si usa il metodo CopyFile(String, String, UIOption) nello spazio dei nomi Microsoft.VisualBasic.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Per aggiungere un riferimento in Visual Studio


1. Nella barra dei menu scegliere Progetto , Aggiungi riferimento .
Verrà visualizzata la finestra di dialogo Gestione riferimenti .
2. Nell'area Assembly scegliere Framework se non è già selezionato.
3. Nell'elenco di nomi selezionare la casella di controllo Microsoft.VisualBasic e scegliere il pulsante OK
per chiudere la finestra di dialogo.

Esempio
Il codice seguente consente di copiare la directory che viene specificata da sourcePath nella directory
specificata da destinationPath . Questo codice specifica anche una finestra di dialogo standard che indica la
quantità stimata di tempo rimanente per il completamento dell'operazione.

// The following using directive requires a project reference to Microsoft.VisualBasic.


using Microsoft.VisualBasic.FileIO;

class FileProgress
{
static void Main()
{
// Specify the path to a folder that you want to copy. If the folder is small,
// you won't have time to see the progress dialog box.
string sourcePath = @"C:\Windows\symbols\";
// Choose a destination for the copied files.
string destinationPath = @"C:\TestFolder";

FileSystem.CopyDirectory(sourcePath, destinationPath,
UIOption.AllDialogs);
}
}

Vedere anche
File system e Registro di sistema (Guida per programmatori C#)
Come scrivere in un file di testo (Guida per
programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

In questi esempi vengono mostrati vari modi per scrivere testo in un file. I primi due esempi usano metodi
pratici statici nella classe System.IO.File per scrivere ogni elemento di qualsiasi oggetto IEnumerable<string> e
una stringa in un file di testo. Nell'esempio 3 viene illustrato come aggiungere testo a un file quando è
necessario elaborare individualmente ogni riga mentre si scrive nel file. Gli esempi da 1 a 3 sovrascrivono tutto
il contenuto esistente nel file, ma l'esempio 4 mostra come aggiungere il testo a un file esistente.
Tutti gli esempi scrivono valori letterali stringa nei file. Per formattare il testo scritto in un file, usare il metodo
Format o la funzionalità di interpolazione di stringhe di C#.

Esempio
class WriteTextFile
{
static void Main()
{

// These examples assume a "C:\Users\Public\TestFolder" folder on your machine.


// You can modify the path if necessary.

// Example #1: Write an array of strings to a file.


// Create a string array that consists of three lines.
string[] lines = { "First line", "Second line", "Third line" };
// WriteAllLines creates a file, writes a collection of strings to the file,
// and then closes the file. You do NOT need to call Flush() or Close().
System.IO.File.WriteAllLines(@"C:\Users\Public\TestFolder\WriteLines.txt", lines);

// Example #2: Write one string to a text file.


string text = "A class is the most powerful data type in C#. Like a structure, " +
"a class defines the data and behavior of the data type. ";
// WriteAllText creates a file, writes the specified string to the file,
// and then closes the file. You do NOT need to call Flush() or Close().
System.IO.File.WriteAllText(@"C:\Users\Public\TestFolder\WriteText.txt", text);

// Example #3: Write only some strings in an array to a file.


// The using statement automatically flushes AND CLOSES the stream and calls
// IDisposable.Dispose on the stream object.
// NOTE: do not use FileStream for text files because it writes bytes, but StreamWriter
// encodes the output as text.
using (System.IO.StreamWriter file =
new System.IO.StreamWriter(@"C:\Users\Public\TestFolder\WriteLines2.txt"))
{
foreach (string line in lines)
{
// If the line doesn't contain the word 'Second', write the line to the file.
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
}
}

// Example #4: Append new text to an existing file.


// The using statement automatically flushes AND CLOSES the stream and calls
// IDisposable.Dispose on the stream object.
using (System.IO.StreamWriter file =
using (System.IO.StreamWriter file =
new System.IO.StreamWriter(@"C:\Users\Public\TestFolder\WriteLines2.txt", true))
{
file.WriteLine("Fourth line");
}
}
}
//Output (to WriteLines.txt):
// First line
// Second line
// Third line

//Output (to WriteText.txt):


// A class is the most powerful data type in C#. Like a structure, a class defines the data and behavior
of the data type.

//Output to WriteLines2.txt after Example #3:


// First line
// Third line

//Output to WriteLines2.txt after Example #4:


// First line
// Third line
// Fourth line

Programmazione efficiente
Le seguenti condizioni possono generare un'eccezione:
Il file esiste ed è di sola lettura.
Il nome del percorso è troppo lungo.
Il disco è pieno.

Vedere anche
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come salvare una raccolta di oggetti personalizzati nella memoria locale
Come leggere da un file di testo (Guida per
programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio legge il contenuto di un file di testo usando i metodi statici ReadAllText e ReadAllLines della
classe System.IO.File.
Per un esempio che usa StreamReader , vedere come leggere un file di testo una riga alla volta.

NOTE
I file usati in questo esempio vengono creati nell'argomento come scrivere in un file di testo.

Esempio
class ReadFromFile
{
static void Main()
{
// The files used in this example are created in the topic
// How to: Write to a Text File. You can change the path and
// file name to substitute text files of your own.

// Example #1
// Read the file as one string.
string text = System.IO.File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");

// Display the file contents to the console. Variable text is a string.


System.Console.WriteLine("Contents of WriteText.txt = {0}", text);

// Example #2
// Read each line of the file into a string array. Each element
// of the array is one line of the file.
string[] lines = System.IO.File.ReadAllLines(@"C:\Users\Public\TestFolder\WriteLines2.txt");

// Display the file contents by using a foreach loop.


System.Console.WriteLine("Contents of WriteLines2.txt = ");
foreach (string line in lines)
{
// Use a tab to indent each line of the file.
Console.WriteLine("\t" + line);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}

Compilazione del codice


Copiare il codice e incollarlo in un'applicazione console C#.
Se non si utilizzano i file di testo da come scrivere in un file di testo, sostituire l'argomento ReadAllText con e
ReadAllLines con il percorso e il nome file appropriati nel computer.
Programmazione efficiente
Le seguenti condizioni possono generare un'eccezione:
Il file non esiste o non esiste nella posizione specificata. Controllare il percorso e la digitazione del nome file.

Sicurezza .NET
Non basarsi sul nome di un file per determinare il contenuto del file. Ad esempio, il file myFile.cs potrebbe non
essere un file di origine C#.

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come leggere un file di testo una riga alla volta
(Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

Questo esempio legge il contenuto di un file di testo, una riga alla volta, in una stringa usando il metodo
ReadLine della classe StreamReader . Ogni riga di testo è memorizzata nella stringa line e visualizzata nella
schermata.

Esempio
int counter = 0;
string line;

// Read the file and display it line by line.


System.IO.StreamReader file =
new System.IO.StreamReader(@"c:\test.txt");
while((line = file.ReadLine()) != null)
{
System.Console.WriteLine(line);
counter++;
}

file.Close();
System.Console.WriteLine("There were {0} lines.", counter);
// Suspend the screen.
System.Console.ReadLine();

Compilazione del codice


Copiare il codice e incollarlo nel metodo Main di un'applicazione console.
Sostituire "c:\test.txt" con il nome effettivo del file.

Programmazione efficiente
Le seguenti condizioni possono generare un'eccezione:
È possibile che il file non esista.

Sicurezza .NET
Non basarsi sul nome del file per prendere decisioni in merito al relativo contenuto. Ad esempio, il file
myFile.cs potrebbe non essere un file di origine C#.

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Come creare una chiave nel registro di sistema
(Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Nell'esempio riportato di seguito viene aggiunta la coppia di valori "Name" e "Isabella" alla chiave "Names"del
Registro di sistema dell'utente corrente.

Esempio
Microsoft.Win32.RegistryKey key;
key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Names");
key.SetValue("Name", "Isabella");
key.Close();

Compilazione del codice


Copiare il codice e incollarlo nel metodo Main di un'applicazione console.
Sostituire il parametro Names con il nome di una chiave esistente direttamente nel nodo
HKEY_CURRENT_USER del Registro di sistema.
Sostituire il parametro Name con il nome di un valore esistente direttamente nel nodo Names.

Programmazione efficiente
Esaminare la struttura del Registro di sistema per individuare un percorso adatto per la chiave. Può essere
necessario, ad esempio, aprire la chiave Software dell'utente corrente e creare una chiave con il nome della
propria società, quindi aggiungere i valori del Registro di sistema alla chiave della società stessa.
Le seguenti condizioni possono generare un'eccezione:
Il nome della chiave è null.
L'utente non dispone di autorizzazioni per la creazione di chiavi del Registro di sistema.
Il nome della chiave supera il limite di 255 caratteri.
La chiave è chiusa.
La chiave del Registro di sistema è di sola lettura.

Sicurezza .NET
È più sicuro scrivere i dati nella cartella dell'utente, ovvero Microsoft.Win32.Registry.CurrentUser , anziché nel
computer locale, ovvero Microsoft.Win32.Registry.LocalMachine .
Quando si crea un valore del Registro di sistema, è necessario decidere come procedere nel caso in cui tale
valore esista già. È possibile che un altro processo, forse dannoso, abbia già creato il valore e possa accedervi. I
dati inseriti nel valore del Registro di sistema sono disponibili per altri processi. Per evitare che ciò accada, usare
il metodo Overload:Microsoft.Win32.RegistryKey.GetValue ProcessOnStatus. Restituisce null se la chiave non
esiste.
Archiviare come testo nel Registro di sistema informazioni riservate, quali le password, può presentare dei
rischi, anche se la chiave del Registro di sistema è protetta da elenchi di controllo di accesso (ACL, Access
Control List).

Vedere anche
System.IO
Guida per programmatori C#
File system e Registro di sistema (Guida per programmatori C#)
Read, write and delete from the registry with C# (Leggere, scrivere ed eliminare dal Registro di sistema con
C#)
Interoperabilità (Guida per programmatori C#)
02/11/2020 • 2 minutes to read • Edit Online

L'interoperabilità consente di preservare e sfruttare appieno gli investimenti effettuati in codice non gestito. Il
codice eseguito sotto il controllo del Common Language Runtime (CLR) è detto codice gestito, mentre quello
eseguito all'esterno è detto codice non gestito. Esempi di codice non gestito sono i componenti COM, COM+,
C++, i componenti ActiveX e le API Microsoft Windows.
.NET consente l'interoperabilità con codice non gestito tramite platform invoke servizi, lo
System.Runtime.InteropServices spazio dei nomi, l'interoperabilità C++ e l'interoperabilità COM
(interoperabilità COM).

Contenuto della sezione


Cenni preliminari sull'interoperabilità
Vengono descritti i metodi per l'interoperabilità tra codice C# gestito e non gestito.
Come accedere agli oggetti di interoperabilità di Office usando le funzionalità di C#
Vengono descritte le funzionalità introdotte in Visual C# per facilitare la programmazione di Office.
Come usare proprietà indicizzate nella programmazione dell'interoperabilità COM
Viene illustrato come usare le proprietà indicizzate per accedere alle proprietà COM con parametri.
Come usare platform invoke per riprodurre un file WAV
Viene descritto come usare i servizi platform invoke per riprodurre un file audio con estensione wav nel sistema
operativo Windows.
Procedura dettagliata: Programmazione di Office
Viene illustrato come creare una cartella di lavoro di Excel o un documento di Word contenente un
collegamento alla cartella di lavoro.
Esempio di classe COM
Viene illustrato come esporre una classe C# come oggetto COM.

Specifiche del linguaggio C#


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

Vedere anche
Marshal.ReleaseComObject
Guida per programmatori C#
Interoperabilità con codice non gestito
Procedura dettagliata: Programmazione di Office
Cenni preliminari sull'interoperabilità (Guida per
programmatori C#)
02/11/2020 • 6 minutes to read • Edit Online

In questo argomento vengono descritti i metodi per consentire l'interoperabilità tra il codice gestito C# e il
codice non gestito.

Platform invoke
Platform invoke è un servizio che consente al codice gestito di chiamare funzioni non gestite implementate in
librerie di collegamento dinamico (DLL), come quelle nell'API Microsoft Windows. Individua e richiama una
funzione esportata ed esegue il marshalling degli argomenti (Integer, stringhe, matrici, strutture e così via) nel
limite dell'interazione, in base alle necessità.
Per ulteriori informazioni, vedere Utilizzo di funzioni dll non gestite e come utilizzare Platform Invoke per
riprodurre un file WAV.

NOTE
Il Common Language Runtime (CLR) gestisce l'accesso alle risorse di sistema. La chiamata di codice non gestito esterno al
CLR ignora questo meccanismo di sicurezza e presenta pertanto un rischio per la sicurezza. Ad esempio, il codice non
gestito può chiamare direttamente le risorse nel codice non gestito, ignorando i meccanismi di sicurezza CLR. Per altre
informazioni, vedere Sicurezza in .NET.

interoperabilità C++
È possibile utilizzare l'interoperabilità C++, nota anche come funzione (IJW), per eseguire il wrapping di una
classe C++ nativa in modo che possa essere utilizzata dal codice creato in C# o in un altro linguaggio .NET. A tale
scopo, scrivere codice C++ per eseguire il wrapping di un componente COM o DLL nativo. A differenza di altri
linguaggi .NET, Visual C++ dispone di supporto per l'interoperabilità che consente di trovare codice gestito e
non gestito nella stessa applicazione e persino nello stesso file. Compilare quindi il codice C++ mediante
l'opzione del compilatore /clr per produrre un assembly gestito. Infine, aggiungere un riferimento all'assembly
nel progetto C# e usare gli oggetti con wrapping esattamente come si userebbero altre classi gestite.

Esposizione di componenti COM a C#


È possibile usare un componente COM da un progetto C#. La procedura generale è la seguente:
1. Individuare un componente COM da usare e registrarlo. Usare regsvr32.exe per registrare o annullare la
registrazione di una DLL COM.
2. Aggiungere al progetto un riferimento alla libreria dei componenti o dei tipi COM.
Quando si aggiunge il riferimento, Visual Studio usa il Tlbimp.exe (utilità di importazione della libreria dei
tipi), che accetta una libreria dei tipi come input, per generare un assembly di interoperabilità .NET.
L'assembly, denominato anche Runtime Callable Wrapper (RCW), contiene le classi gestite e le interfacce
che eseguono il wrapping delle classi e interfacce COM presenti nella libreria dei tipi. Visual Studio
aggiunge al progetto un riferimento all'assembly generato.
3. Creare un'istanza di una classe definita nell'RCW. Essa, a sua volta, crea un'istanza dell'oggetto COM.
4. Usare l'oggetto allo stesso modo di altri oggetti gestiti. Quando l'oggetto viene recuperato da Garbage
Collection, anche l'istanza dell'oggetto COM viene rilasciata dalla memoria.
Per altre informazioni, vedere Esposizione di componenti COM a .NET Framework.

Esposizione di C# a COM
I client COM possono usare i tipi di C# che sono stati esposti in modo corretto. I passaggi di base per esporre
tipi di C# sono i seguenti:
1. Aggiungere gli attributi di interoperabilità nel progetto C#.
È possibile rendere un assembly visibile a COM modificando le proprietà del progetto Visual C#. Per altre
informazioni, vedere Finestra di dialogo Informazioni assembly.
2. Generare una libreria dei tipi COM e registrarla per l'uso di COM.
È possibile modificare le proprietà del progetto Visual C# per registrare automaticamente l'assembly C#
per l'interoperabilità COM. Visual Studio usa Regasm.exe (strumento di registrazione di assembly),
tramite l'opzione della riga di comando /tlb , che accetta un assembly gestito come input, per generare
una libreria dei tipi. Questa libreria dei tipi descrive i tipi public nell'assembly e aggiunge le voci del
Registro di sistema in modo che i client COM possano creare classi gestite.
Per altre informazioni, vedere Esposizione di componenti .NET Framework a COM e Esempio di classe COM.

Vedere anche
Improving Interop Performance (Miglioramento delle prestazioni di interoperabilità)
Introduction to Interoperability between COM and .NET (Introduzione all'interoperabilità tra COM e .NET)
Introduzione all'interoperabilità COM (Visual Basic)
Marshaling between Managed and Unmanaged Code (Marshalling tra codice gestito e non gestito)
Interoperabilità con codice non gestito
Guida per programmatori C#
Come accedere agli oggetti di interoperabilità di
Office (Guida per programmatori C#)
02/11/2020 • 19 minutes to read • Edit Online

C# offre funzionalità che semplificano l'accesso agli oggetti API di Office. Le nuove funzionalità includono
argomenti denominati e facoltativi, un nuovo tipo chiamato dynamic e la possibilità di passare argomenti a
parametri di riferimento nei metodi COM come se fossero parametri di valore.
In questo argomento si useranno le nuove funzionalità per scrivere codice che consente di creare e visualizzare
un foglio di lavoro di Microsoft Office Excel. Quindi si scriverà il codice per aggiungere un documento di Office
Word contenente un'icona che è collegata al foglio di lavoro di Excel.
Per completare questa procedura dettagliata, è necessario aver installato nel computer Microsoft Office Excel
2007 e Microsoft Office Word 2007 o versioni successive.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Per creare un nuovo progetto di applicazione console


1. Avviare Visual Studio.
2. Scegliere Nuovo dal menu File e quindi fare clic su Progetto . Verrà visualizzata la finestra di dialogo
Nuovo progetto .
3. Nel riquadro Modelli installati espandere Visual C# e fare clic su Windows .
4. Nella parte superiore della finestra di dialogo Nuovo progetto assicurarsi che .NET Framework 4 , o
versione successiva, sia selezionato come framework di destinazione.
5. Nel riquadro Modelli fare clic su Applicazione console .
6. Digitare un nome per il progetto nel campo Nome .
7. Fare clic su OK .
Il nuovo progetto verrà visualizzato in Esplora soluzioni .

Per aggiungere riferimenti


1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e quindi scegliere
Aggiungi riferimento . Verrà visualizzata la finestra di dialogo Aggiungi riferimento .
2. Nella pagina Assembly selezionare Microsoft.Office.Interop.Word nell'elenco Nome componente
e, tenendo premuto CTRL, selezionare Microsoft.Office.Interop.Excel . Se gli assembly non sono visibili,
potrebbe essere necessario assicurarsi che siano installati e visualizzati. Vedere procedura: installare gli
assembly di interoperabilità primari di Office.
3. Fare clic su OK .
Per aggiungere le direttive using necessarie
1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul file Program.cs e quindi fare clic su
Visualizza codice .
2. Aggiungere le seguenti using direttive all'inizio del file di codice:

using Excel = Microsoft.Office.Interop.Excel;


using Word = Microsoft.Office.Interop.Word;

Per creare un elenco di conti correnti bancari


1. Incollare la seguente definizione di classe in Program.cs , sotto la classe Program .

public class Account


{
public int ID { get; set; }
public double Balance { get; set; }
}

2. Aggiungere il seguente codice al metodo Main per creare un elenco bankAccounts contenente due conti.

// Create a list of accounts.


var bankAccounts = new List<Account> {
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
}
};

Per dichiarare un metodo che consente di esportare informazioni sul


conto in Excel
1. Aggiungere il seguente metodo alla classe Program per impostare un foglio di lavoro di Excel.
Il metodo Add usa un parametro facoltativo per specificare un modello particolare. I parametri facoltativi,
una novità di C# 4, consentono di omettere l'argomento per il parametro se si vuole usare il valore
predefinito del parametro. Poiché nessun argomento viene inviato nel codice seguente, Add usa il
modello predefinito e crea una nuova cartella di lavoro. L'istruzione equivalente nelle precedenti versioni
di C# richiede un argomento segnaposto: ExcelApp.Workbooks.Add(Type.Missing) .
static void DisplayInExcel(IEnumerable<Account> accounts)
{
var excelApp = new Excel.Application();
// Make the object visible.
excelApp.Visible = true;

// Create a new, empty workbook and add it to the collection returned


// by property Workbooks. The new workbook becomes the active workbook.
// Add has an optional parameter for specifying a praticular template.
// Because no argument is sent in this example, Add creates a new workbook.
excelApp.Workbooks.Add();

// This example uses a single workSheet. The explicit type casting is


// removed in a later procedure.
Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;
}

2. Aggiungere il codice seguente alla fine di DisplayInExcel . Il codice consente di inserire valori nelle prime
due colonne della prima riga del foglio di lavoro.

// Establish column headings in cells A1 and B1.


workSheet.Cells[1, "A"] = "ID Number";
workSheet.Cells[1, "B"] = "Current Balance";

3. Aggiungere il codice seguente alla fine di DisplayInExcel . Il ciclo foreach inserisce le informazioni
dall'elenco dei conti nelle prime due colonne delle righe successive del foglio di lavoro.

var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}

4. Aggiungere il seguente codice alla fine di DisplayInExcel per regolare la larghezza delle colonne in
modo da adattarle al contenuto.

workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();

Nelle versioni precedenti di C# è necessario eseguire il cast in modo esplicito per queste operazioni
perché ExcelApp.Columns[1] restituisce Object , e AutoFit è un metodo Range di Excel. Le righe
seguenti indicano il cast.

((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();

In C# 4 e versioni successive, converte automaticamente l'oggetto restituito Object in dynamic se


all'assembly viene fatto riferimento tramite l'opzione del compilatore -link o, in modo equivalente, se la
proprietà Incorpora tipi di interoperabilità di Excel è impostata su true. True è il valore predefinito di
questa proprietà.

Per eseguire il progetto


1. Aggiungere la riga seguente alla fine di Main .

// Display the list in an Excel spreadsheet.


DisplayInExcel(bankAccounts);

2. Premere CTRL+F5.
Viene visualizzato un foglio di lavoro di Excel che contiene i dati dei due conti.

Per aggiungere un documento di Word


1. Per illustrare altri modi in cui C# 4 e versioni successive consentono di migliorare la programmazione di
Office, il codice seguente apre un'applicazione Word e crea un'icona di collegamento al foglio di lavoro di
Excel.
Incollare il metodo CreateIconInWordDoc , fornito più avanti in questo passaggio, nella classe Program .
CreateIconInWordDoc usa argomenti denominati e facoltativi per ridurre la complessità delle chiamate ai
metodi di Add e PasteSpecial. Queste chiamate incorporano altre due nuove funzionalità introdotte in C#
4 che semplificano le chiamate ai metodi COM con parametri di riferimento. In primo luogo, è possibile
inviare argomenti ai parametri di riferimento come se fossero parametri di valore. In altre parole, è
possibile inviare i valori direttamente, senza creare una variabile per ogni parametro referenziato. Il
compilatore genera variabili temporanee per conservare i valori degli argomenti ed elimina le variabili
restituite dalla chiamata. In secondo luogo, è possibile omettere la parola chiave ref nell'elenco di
argomenti.
Il metodo Add ha quattro parametri di riferimento, tutti facoltativi. In C# 4.0 e versioni successive è
possibile omettere gli argomenti per alcuni o tutti i parametri se si vogliono usare i valori predefiniti. In
C# 3.0 e versioni precedenti, è necessario fornire un argomento per ogni parametro e l'argomento deve
essere una variabile perché i parametri sono di riferimento.
Il metodo PasteSpecial inserisce il contenuto degli Appunti. Il metodo ha sette parametri di riferimento,
tutti facoltativi. Il codice seguente consente di specificare gli argomenti per due di essi: Link , per creare
un collegamento all'origine del contenuto degli Appunti e DisplayAsIcon , per visualizzare il collegamento
come icona. In C# 4.0 e versioni successive è possibile usare argomenti denominati per questi due
parametri e omettere gli altri. Anche se si tratta di parametri di riferimento, non è necessario usare la
parola chiave ref o creare variabili per inviarle come argomenti. È possibile inviare direttamente i valori.
In C# 3.0 e versioni precedenti, è necessario specificare un argomento variabile per ogni parametro di
riferimento.

static void CreateIconInWordDoc()


{
var wordApp = new Word.Application();
wordApp.Visible = true;

// The Add method has four reference parameters, all of which are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();

// PasteSpecial has seven reference parameters, all of which are


// optional. This example uses named arguments to specify values
// for two of the parameters. Although these are reference
// parameters, you do not need to use the ref keyword, or to create
// variables to send in as arguments. You can send the values directly.
wordApp.Selection.PasteSpecial( Link: true, DisplayAsIcon: true);
}
In C# 3.0 e versioni precedenti del linguaggio, è necessario il codice più complesso seguente.

static void CreateIconInWordDoc2008()


{
var wordApp = new Word.Application();
wordApp.Visible = true;

// The Add method has four parameters, all of which are optional.
// In Visual C# 2008 and earlier versions, an argument has to be sent
// for every parameter. Because the parameters are reference
// parameters of type object, you have to create an object variable
// for the arguments that represents 'no value'.

object useDefaultValue = Type.Missing;

wordApp.Documents.Add(ref useDefaultValue, ref useDefaultValue,


ref useDefaultValue, ref useDefaultValue);

// PasteSpecial has seven reference parameters, all of which are


// optional. In this example, only two of the parameters require
// specified values, but in Visual C# 2008 an argument must be sent
// for each parameter. Because the parameters are reference parameters,
// you have to contruct variables for the arguments.
object link = true;
object displayAsIcon = true;

wordApp.Selection.PasteSpecial( ref useDefaultValue,


ref link,
ref useDefaultValue,
ref displayAsIcon,
ref useDefaultValue,
ref useDefaultValue,
ref useDefaultValue);
}

2. Aggiungere la seguente istruzione dopo Main .

// Create a Word document that contains an icon that links to


// the spreadsheet.
CreateIconInWordDoc();

3. Aggiungere la seguente istruzione dopo DisplayInExcel . Il metodo Copy aggiunge il foglio di lavoro
negli Appunti.

// Put the spreadsheet contents on the clipboard. The Copy method has one
// optional parameter for specifying a destination. Because no argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();

4. Premere CTRL+F5.
Verrà visualizzato un documento di Word contenente un'icona. Fare doppio clic sull'icona per visualizzare
il foglio di lavoro in primo piano.

Per impostare la proprietà Incorpora tipi di interoperabilità


1. Quando si chiama un tipo COM che non richiede un assembly di interoperabilità primario (PIA) in fase di
esecuzione, sono possibili altri miglioramenti. Eliminando la dipendenza dai risultati dei PIA produce
l'indipendenza dalla versione e semplifica la distribuzione. Per altre informazioni sui vantaggi della
programmazione senza PIA, vedere Procedura dettagliata: incorporamento di tipi da assembly gestiti.
Inoltre, la programmazione è più semplice perché i tipi richiesti e restituiti dai metodi COM possono
essere rappresentati usando il tipo dynamic anziché Object . Le variabili di tipo dynamic non saranno
valutate fino al runtime, eliminando la necessità del cast esplicito. Per altre informazioni, vedere Uso del
tipo dynamic.
In C# 4 il comportamento predefinito consiste nell'incorporare le informazioni di tipo invece di usare i
PIA. A causa di tale impostazione predefinita, molti degli esempi precedenti vengono semplificati perché
non è necessario eseguire il cast esplicito. Ad esempio, la dichiarazione di worksheet in DisplayInExcel è
scritta come Excel._Worksheet workSheet = excelApp.ActiveSheet piuttosto che
Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet . Anche le chiamate a AutoFit nello
stesso metodo richiederebbero il cast esplicito senza valore predefinito, perché ExcelApp.Columns[1]
restituisce un Object e AutoFit è un metodo Excel. Nel codice seguente viene illustrato il cast.

((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();

2. Per modificare l'impostazione predefinita e usare i PIA anziché incorporare le informazioni sul tipo,
espandere il nodo Riferimenti in Esplora soluzioni e selezionare Microsoft.Office.Interop.Excel o
Microsoft.Office.Interop.Word .
3. Se non è possibile visualizzare la finestra Proprietà , premere F4 .
4. Trovare Incorpora tipi di interoperabilità nell'elenco delle proprietà e modificarne il valore in False .
In modo analogo, è possibile compilare usando l'opzione del compilatore -Reference invece di -link al
prompt dei comandi.

Per aggiungere ulteriore formattazione alla tabella


1. Sostituire le due chiamate a AutoFit in DisplayInExcel con la seguente istruzione.

// Call to AutoFormat in Visual C# 2010.


workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

Il metodo AutoFormat ha sette parametri di valore facoltativi. Gli argomenti denominati e facoltativi
consentono di specificare gli argomenti per nessuno, alcuni o tutti i parametri. Nell'istruzione precedente,
viene fornito un argomento per uno solo dei parametri, Format . Poiché Format è il primo parametro
nell'elenco, non è necessario fornire il nome del parametro. Tuttavia, l'istruzione potrebbe essere più
facile da comprendere se si include il nome del parametro, come illustrato nel codice seguente.

// Call to AutoFormat in Visual C# 2010.


workSheet.Range["A1", "B3"].AutoFormat(Format:
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

2. Premere CTRL + F5 per visualizzare il risultato. Altri formati sono elencati nell'enumerazione
XlRangeAutoFormat.
3. Confrontare l'istruzione nel passaggio 1 con il codice seguente, che illustra gli argomenti necessari in C#
3.0 e versioni precedenti.
// The AutoFormat method has seven optional value parameters. The
// following call specifies a value for the first parameter, and uses
// the default values for the other six.

// Call to AutoFormat in Visual C# 2008. This code is not part of the


// current solution.
excelApp.get_Range("A1", "B4").AutoFormat(Excel.XlRangeAutoFormat.xlRangeAutoFormatTable3,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing);

Esempio
Nel codice seguente viene illustrato l'esempio completo.

using System;
using System.Collections.Generic;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeProgramminWalkthruComplete
{
class Walkthrough
{
static void Main(string[] args)
{
// Create a list of accounts.
var bankAccounts = new List<Account>
{
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
}
};

// Display the list in an Excel spreadsheet.


DisplayInExcel(bankAccounts);

// Create a Word document that contains an icon that links to


// the spreadsheet.
CreateIconInWordDoc();
}

static void DisplayInExcel(IEnumerable<Account> accounts)


{
var excelApp = new Excel.Application();
// Make the object visible.
excelApp.Visible = true;

// Create a new, empty workbook and add it to the collection returned


// by property Workbooks. The new workbook becomes the active workbook.
// Add has an optional parameter for specifying a praticular template.
// Because no argument is sent in this example, Add creates a new workbook.
excelApp.Workbooks.Add();

// This example uses a single workSheet.


Excel._Worksheet workSheet = excelApp.ActiveSheet;

// Earlier versions of C# require explicit casting.


//Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;
// Establish column headings in cells A1 and B1.
workSheet.Cells[1, "A"] = "ID Number";
workSheet.Cells[1, "B"] = "Current Balance";

var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}

workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();

// Call to AutoFormat in Visual C#. This statement replaces the


// two calls to AutoFit.
workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

// Put the spreadsheet contents on the clipboard. The Copy method has one
// optional parameter for specifying a destination. Because no argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();
}

static void CreateIconInWordDoc()


{
var wordApp = new Word.Application();
wordApp.Visible = true;

// The Add method has four reference parameters, all of which are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();

// PasteSpecial has seven reference parameters, all of which are


// optional. This example uses named arguments to specify values
// for two of the parameters. Although these are reference
// parameters, you do not need to use the ref keyword, or to create
// variables to send in as arguments. You can send the values directly.
wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true);
}
}

public class Account


{
public int ID { get; set; }
public double Balance { get; set; }
}
}

Vedere anche
Type.Missing
dinamico
Utilizzo del tipo dinamico
Argomenti denominati e facoltativi
Come usare argomenti denominati e facoltativi nella programmazione di Office
Come usare le proprietà indicizzate nella
programmazione dell'interoperabilità COM (Guida
per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Le proprietà indicizzate migliorano l'uso delle proprietà COM dotate di parametri nella programmazione C#. Tali
proprietà operano congiuntamente ad altre funzionalità di Visual C#, ad esempio gli argomenti denominati e
facoltativi, un nuovo tipo (dynamic) e le informazioni sul tipo incorporate, per migliorare la programmazione di
Microsoft Office.
Nelle versioni precedenti di C# i metodi sono accessibili come proprietà solo se il metodo get non dispone di
parametri e il metodo set ha un unico parametro valore. Non tutte le proprietà COM soddisfano tuttavia tali
restrizioni. Ad esempio, la proprietà Range[] di Excel possiede una funzione di accesso get che richiede un
parametro per il nome dell'intervallo. In passato, poiché non era possibile accedere direttamente alla proprietà
Range , era necessario usare il metodo get_Range , come illustrato nell'esempio seguente.

// Visual C# 2008 and earlier.


var excelApp = new Excel.Application();
// . . .
Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);

Le proprietà indicizzate consentono invece di scrivere quanto segue:

// Visual C# 2010.
var excelApp = new Excel.Application();
// . . .
Excel.Range targetRange = excelApp.Range["A1"];

NOTE
Nell'esempio precedente viene usata anche la funzionalità degli argomenti facoltativi, che consente di omettere
Type.Missing .

Analogamente, per impostare il valore della proprietà Value di un oggetto Range in C# 3.0 e versioni
precedenti, sono necessari due argomenti. Uno specifica un argomento per un parametro facoltativo che
specifica il tipo del valore dell'intervallo. L'altro indica il valore per la proprietà Value . Queste tecniche vengono
illustrate negli esempi riportati di seguito. Entrambe impostano il valore della cella A1 su Name .

// Visual C# 2008.
targetRange.set_Value(Type.Missing, "Name");
// Or
targetRange.Value2 = "Name";

Le proprietà indicizzate consentono invece di scrivere il codice riportato di seguito.

// Visual C# 2010.
targetRange.Value = "Name";
Non è possibile creare proprietà indicizzate personalizzate. La funzionalità supporta solo l'utilizzo di proprietà
indicizzate esistenti.

Esempio
L'esempio seguente illustra un esempio completo. Per altre informazioni su come configurare un progetto che
accede all'API di Office, vedere come accedere agli oggetti di interoperabilità di Office usando le funzionalità di
C#.

// You must add a reference to Microsoft.Office.Interop.Excel to run


// this example.
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace IndexedProperties
{
class Program
{
static void Main(string[] args)
{
CSharp2010();
//CSharp2008();
}

static void CSharp2010()


{
var excelApp = new Excel.Application();
excelApp.Workbooks.Add();
excelApp.Visible = true;

Excel.Range targetRange = excelApp.Range["A1"];


targetRange.Value = "Name";
}

static void CSharp2008()


{
var excelApp = new Excel.Application();
excelApp.Workbooks.Add(Type.Missing);
excelApp.Visible = true;

Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);


targetRange.set_Value(Type.Missing, "Name");
// Or
//targetRange.Value2 = "Name";
}
}
}

Vedere anche
Argomenti denominati e facoltativi
dinamico
Utilizzo del tipo dinamico
Come usare argomenti denominati e facoltativi nella programmazione di Office
Come accedere agli oggetti di interoperabilità di Office usando le funzionalità di C#
Procedura dettagliata: Programmazione di Office
Come usare platform invoke per riprodurre un file
WAV (Guida per programmatori C#)
02/11/2020 • 3 minutes to read • Edit Online

Nell'esempio di codice C# riportato di seguito viene illustrato come utilizzare i servizi di platform invoke per
riprodurre un file audio WAV nel sistema operativo Windows.

Esempio
In questo esempio di codice viene usato DllImportAttribute per importare il punto di ingresso del metodo
winmm.dll di PlaySound come Form1 PlaySound() . L'esempio è costituito da un semplice Windows Form con un
pulsante. Se si fa clic sul pulsante, viene visualizzata una finestra di dialogo OpenFileDialog standard di
Windows che consente di aprire un file da riprodurre. Quando si seleziona un file Wave, questo viene riprodotto
utilizzando il PlaySound() metodo della libreria winmm.dll . Per altre informazioni sul metodo, vedere Using the
PlaySound function with Waveform-Audio Files (Uso della funzione PlaySound con file audio Waveform).
Cercare e selezionare un file con estensione wav e fare clic su Apri per riprodurre il file audio usando platform
invoke. Il percorso completo del file selezionato verrà visualizzato in una casella di testo.
La finestra di dialogo File aper ti viene filtrata in modo da visualizzare solo i file con estensione wav tramite le
impostazioni di filtro:

dialog1.Filter = "Wav Files (*.wav)|*.wav";


using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WinSound
{
public partial class Form1 : Form
{
private TextBox textBox1;
private Button button1;

public Form1() // Constructor.


{
InitializeComponent();
}

[DllImport("winmm.DLL", EntryPoint = "PlaySound", SetLastError = true, CharSet = CharSet.Unicode,


ThrowOnUnmappableChar = true)]
private static extern bool PlaySound(string szSound, System.IntPtr hMod, PlaySoundFlags flags);

[System.Flags]
public enum PlaySoundFlags : int
{
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_NODEFAULT = 0x0002,
SND_LOOP = 0x0008,
SND_NOSTOP = 0x0010,
SND_NOWAIT = 0x00002000,
SND_FILENAME = 0x00020000,
SND_RESOURCE = 0x00040004
}

private void button1_Click(object sender, System.EventArgs e)


{
var dialog1 = new OpenFileDialog();

dialog1.Title = "Browse to find sound file to play";


dialog1.InitialDirectory = @"c:\";
dialog1.Filter = "Wav Files (*.wav)|*.wav";
dialog1.FilterIndex = 2;
dialog1.RestoreDirectory = true;

if (dialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog1.FileName;
PlaySound(dialog1.FileName, new System.IntPtr(), PlaySoundFlags.SND_SYNC);
}
}

private void Form1_Load(object sender, EventArgs e)


{
// Including this empty method in the sample because in the IDE,
// when users click on the form, generates code that looks for a default method
// with this name. We add it here to prevent confusion for those using the samples.
}
}
}

Compilazione del codice


1. Creare un nuovo progetto di applicazione Windows Forms C# in Visual Studio e denominarlo winsound .
2. Copiare il codice precedente e incollarlo sul contenuto del file Form1.cs .
3. Copiare il codice seguente e incollarlo nel file Form1.designer.cs , nel InitializeComponent() metodo,
dopo il codice esistente.

this.button1 = new System.Windows.Forms.Button();


this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(192, 40);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 24);
this.button1.TabIndex = 0;
this.button1.Text = "Browse";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(8, 40);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(168, 20);
this.textBox1.TabIndex = 1;
this.textBox1.Text = "FIle path";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Platform Invoke WinSound C#";
this.ResumeLayout(false);
this.PerformLayout();

4. Compilare ed eseguire il codice.

Vedere anche
Guida per programmatori C#
Cenni preliminari sull'interoperabilità
Informazioni dettagliate su platform invoke
Marshalling dei dati con platform invoke
Procedura dettagliata: Programmazione di Office
(C# e Visual Basic)
02/11/2020 • 21 minutes to read • Edit Online

Visual Studio offre funzionalità di C# e Visual Basic che migliorano la programmazione di Microsoft Office. Tra le
utili funzionalità di C# sono disponibili gli argomenti denominati e facoltativi e i valori restituiti di tipo dynamic .
Nella programmazione COM è possibile omettere la parola chiave ref e accedere alle proprietà indicizzate. Le
funzionalità di Visual Basic includono le proprietà implementate automaticamente, le istruzioni nelle espressioni
lambda e gli inizializzatori di insieme.
Entrambi i linguaggi consentono di incorporare informazioni sul tipo, in modo da distribuire gli assembly che
interagiscono con i componenti COM senza distribuire assembly di interoperabilità primari (PIA) al computer
dell'utente. Per altre informazioni, vedere Procedura dettagliata: Incorporamento dei tipi da assembly gestiti.
In questa procedura dettagliata queste funzionalità vengono illustrate nel contesto della programmazione di
Office, ma molte di esse sono utili anche nella programmazione generale. Nella procedura dettagliata si usa un
componente aggiuntivo di Excel per creare una cartella di lavoro di Excel. A questo punto si crea un documento
di Word contenente un collegamento alla cartella di lavoro. Infine, si visualizzerà come la dipendenza
dell'assembly di interoperabilità primario può essere abilitata e disabilitata.

Prerequisiti
Per completare questa procedura dettagliata è necessario aver installato Microsoft Office Excel o Microsoft
Office Word nel computer.

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Per impostare un componente aggiuntivo di Excel


1. Avviare Visual Studio.
2. Scegliere Nuovo dal menu File e quindi fare clic su Progetto .
3. Nel riquadro Modelli installati espandere Visual Basic o Visual C# , espandere Office e scegliere
l'anno della versione del prodotto Office.
4. Nel riquadro modelli fare clic su ** <version> componente aggiuntivo di Excel**.
5. Verificare che nella parte superiore del riquadro Modelli sia visualizzato .NET Framework 4 , o una
versione successiva, nella casella Framework di destinazione .
6. Se opportuno, digitare un nome per il progetto nella casella Nome .
7. Fare clic su OK .
8. Il nuovo progetto verrà visualizzato in Esplora soluzioni .
Per aggiungere riferimenti
1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e quindi scegliere
Aggiungi riferimento . Verrà visualizzata la finestra di dialogo Aggiungi riferimento .
2. Nella scheda Assembly selezionare Microsoft.Office.Interop.Excel , versione <version>.0.0.0 (per
conoscere il numero versione del prodotto Office, vedere Versioni Microsoft) nell'elenco Nome
componente . Tenere premuto CTRL e selezionare Microsoft.Office.Interop.Word ,
version <version>.0.0.0 . Se gli assembly non sono visibili, potrebbe essere necessario assicurarsi che
siano installati e visualizzati (vedere procedura: installare gli assembly di interoperabilità primari di
Office).
3. Fare clic su OK .
Per aggiungere istruzioni Imports o le direttive using necessarie
1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul file ThisAddIn.vb o ThisAddIn.cs e
quindi scegliere Visualizza codice .
2. Aggiungere le seguenti istruzioni Imports (Visual Basic) o direttive using (C#) all'inizio del file di codice,
se non sono già presenti.

using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;

Imports Microsoft.Office.Interop

Per creare un elenco di conti correnti bancari


1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto, fare clic su
Aggiungi e quindi su Classe . Denominare la classe Account.vb se si usa Visual Basic, Account.cs se si usa
C#. Scegliere Aggiungi .
2. Sostituire la definizione della classe Account con il codice seguente. Le definizioni delle classi usano le
proprietà implementate automaticamente. Per altre informazioni, vedere Proprietà implementate
automaticamente.

class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}

Public Class Account


Property ID As Integer = -1
Property Balance As Double
End Class

3. Per creare un bankAccounts elenco contenente due account, aggiungere il codice seguente al
ThisAddIn_Startup metodo in ThisAddIn. vb o ThisAddIn.cs. Le dichiarazioni dell'elenco usano gli *
inizializzatori di insieme*. Per altre informazioni, vedere Inizializzatori di insieme.
var bankAccounts = new List<Account>
{
new Account
{
ID = 345,
Balance = 541.27
},
new Account
{
ID = 123,
Balance = -127.44
}
};

Dim bankAccounts As New List(Of Account) From {


New Account With {
.ID = 345,
.Balance = 541.27
},
New Account With {
.ID = 123,
.Balance = -127.44
}
}

Per esportare dati in Excel


1. Nello stesso file, aggiungere il metodo seguente alla classe ThisAddIn . Il metodo configura una cartella
di lavoro di Excel e vi esporta i dati.

void DisplayInExcel(IEnumerable<Account> accounts,


Action<Account, Excel.Range> DisplayFunc)
{
var excelApp = this.Application;
// Add a new Excel workbook.
excelApp.Workbooks.Add();
excelApp.Visible = true;
excelApp.Range["A1"].Value = "ID";
excelApp.Range["B1"].Value = "Balance";
excelApp.Range["A2"].Select();

foreach (var ac in accounts)


{
DisplayFunc(ac, excelApp.ActiveCell);
excelApp.ActiveCell.Offset[1, 0].Select();
}
// Copy the results to the Clipboard.
excelApp.Range["A1:B3"].Copy();
}
Sub DisplayInExcel(ByVal accounts As IEnumerable(Of Account),
ByVal DisplayAction As Action(Of Account, Excel.Range))

With Me.Application
' Add a new Excel workbook.
.Workbooks.Add()
.Visible = True
.Range("A1").Value = "ID"
.Range("B1").Value = "Balance"
.Range("A2").Select()

For Each ac In accounts


DisplayAction(ac, .ActiveCell)
.ActiveCell.Offset(1, 0).Select()
Next

' Copy the results to the Clipboard.


.Range("A1:B3").Copy()
End With
End Sub

In questo metodo vengono usati due nuove funzionalità di C#. Entrambe queste funzionalità sono già
esistenti in Visual Basic.
Il metodo Add include un parametro facoltativo per specificare un modello particolare. I parametri
facoltativi, una novità di C# 4, consentono di omettere l'argomento per il parametro se si vuole
usare il valore predefinito del parametro. Poiché nessun argomento viene inviato nell'esempio
precedente, Add usa il modello predefinito e crea una nuova cartella di lavoro. L'istruzione
equivalente nelle precedenti versioni di C# richiede un argomento segnaposto:
excelApp.Workbooks.Add(Type.Missing) .

Per ulteriori informazioni, vedere argomenti denominati e facoltativi.


Le proprietà Range e Offset dell'oggetto Range usano la funzionalità delle proprietà indicizzate.
Questa funzionalità consente di usare tali proprietà dai tipi COM con la tipica sintassi di C#
seguente. Le proprietà indicizzate consentono anche di usare la proprietà Value dell'oggetto
Range , eliminando la necessità di usare la proprietà Value2 . La proprietà Value è indicizzata, ma
l'indice è facoltativo. Nell'esempio seguente sono presenti sia argomenti facoltativi sia proprietà
indicizzate.

// Visual C# 2010 provides indexed properties for COM programming.


excelApp.Range["A1"].Value = "ID";
excelApp.ActiveCell.Offset[1, 0].Select();

Nelle versioni precedenti del linguaggio, è necessaria la seguente sintassi speciale.

// In Visual C# 2008, you cannot access the Range, Offset, and Value
// properties directly.
excelApp.get_Range("A1").Value2 = "ID";
excelApp.ActiveCell.get_Offset(1, 0).Select();

Non è possibile creare proprietà indicizzate personalizzate. La funzionalità supporta solo l'utilizzo
di proprietà indicizzate esistenti.
Per ulteriori informazioni, vedere come utilizzare le proprietà indicizzate nella programmazione
dell'interoperabilità COM.
2. Aggiungere il seguente codice alla fine di DisplayInExcel per regolare la larghezza delle colonne in
modo da adattarle al contenuto.

excelApp.Columns[1].AutoFit();
excelApp.Columns[2].AutoFit();

' Add the following two lines at the end of the With statement.
.Columns(1).AutoFit()
.Columns(2).AutoFit()

Queste aggiunte dimostrano un'altra nuova funzionalità di C#: considerare i valori Object restituiti dagli
host COM, ad esempio Office, come se il tipo fosse dynamic. Ciò avviene automaticamente quando
l'opzione Incorpora tipi di interoperabilità è impostata sul valore predefinito, True o, in modo
analogo, quando si fa riferimento all'assembly tramite l'opzione del compilatore -link . Il tipo dynamic
consente l'associazione tardiva, già disponibile in Visual Basic, ed evita il cast esplicito richiesto in C# 3.0 e
versioni precedenti del linguaggio.
Ad esempio, excelApp.Columns[1] restituisce Object e AutoFit è un metodo Range di Excel. In assenza
di dynamic , è necessario eseguire il cast dell'oggetto restituito da excelApp.Columns[1] come istanza di
Range prima di chiamare il metodo AutoFit .

// Casting is required in Visual C# 2008.


((Excel.Range)excelApp.Columns[1]).AutoFit();

// Casting is not required in Visual C# 2010.


excelApp.Columns[1].AutoFit();

Per altre informazioni sull'incorporamento di tipi di interoperabilità, vedere le procedure "Per trovare il
riferimento all'assembly di interoperabilità primari" e "Per ripristinare la dipendenza di assembly di
interoperabilità primario" più avanti in questo argomento. Per altre informazioni su dynamic , vedere
dynamic o Uso del tipo dinamico.
Per richiamare DisplayInExcel
1. Aggiungere il codice seguente alla fine del metodo ThisAddIn_StartUp . La chiamata a DisplayInExcel
contiene due argomenti. Il primo argomento è il nome dell'elenco di conti da elaborare. Il secondo
argomento è un'espressione lambda su più righe che definisce la modalità con cui i dati saranno
elaborati. I valori ID e balance per ogni conto vengono visualizzati in celle adiacenti e la riga viene
visualizzata in rosso se il saldo è inferiore a zero. Per altre informazioni, vedere espressioni lambda.

DisplayInExcel(bankAccounts, (account, cell) =>


// This multiline lambda expression sets custom processing rules
// for the bankAccounts.
{
cell.Value = account.ID;
cell.Offset[0, 1].Value = account.Balance;
if (account.Balance < 0)
{
cell.Interior.Color = 255;
cell.Offset[0, 1].Interior.Color = 255;
}
});
DisplayInExcel(bankAccounts,
Sub(account, cell)
' This multiline lambda expression sets custom
' processing rules for the bankAccounts.
cell.Value = account.ID
cell.Offset(0, 1).Value = account.Balance

If account.Balance < 0 Then


cell.Interior.Color = RGB(255, 0, 0)
cell.Offset(0, 1).Interior.Color = RGB(255, 0, 0)
End If
End Sub)

2. Premere F5 per eseguire il programma. Viene visualizzato un foglio di lavoro di Excel che contiene i dati
dei conti.
Per aggiungere un documento di Word
1. Aggiungere il seguente codice alla fine del metodo ThisAddIn_StartUp per creare un documento di Word
contenente un collegamento alla cartella di lavoro di Excel.

var wordApp = new Word.Application();


wordApp.Visible = true;
wordApp.Documents.Add();
wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true);

Dim wordApp As New Word.Application


wordApp.Visible = True
wordApp.Documents.Add()
wordApp.Selection.PasteSpecial(Link:=True, DisplayAsIcon:=True)

In questo codice vengono illustrate diverse nuove funzionalità di C#: la possibilità di omettere la parola
chiave ref nella programmazione COM, gli argomenti denominati e gli argomenti facoltativi. Queste
funzionalità sono già esistenti in Visual Basic. Il metodo PasteSpecial ha sette parametri, ognuno dei quali
è definito come parametri di riferimento facoltativi. Gli argomenti denominati e facoltativi consentono di
indicare i parametri a cui si vuol accedere per nome e di inviare argomenti solo a tali parametri. In questo
esempio gli argomenti vengono inviati per indicare che deve essere creato un collegamento alla cartella
di lavoro negli Appunti (parametro Link ), e che il collegamento deve essere visualizzato nel documento
di Word come icona (parametro DisplayAsIcon ). Visual C# consente anche di omettere la parola chiave
ref per questi argomenti.

Per eseguire l'applicazione


1. Premere F5 per eseguire l'applicazione. Excel viene avviato e viene visualizzata una tabella che contiene le
informazioni dei due conti in bankAccounts . Quindi verrà visualizzato un documento di Word contenente un
collegamento alla tabella di Excel.
Per pulire il progetto completato
1. In Visual Studio fare clic su Pulisci soluzione nel menu Compila . In caso contrario, il componente
aggiuntivo verrà eseguito ogni volta che si apre Excel nel computer.
Per trovare il riferimento all'assembly di interoperabilità primario
1. Eseguire nuovamente l'applicazione, ma non fare clic su Pulisci soluzione .
2. Selezionare Star t . Individuare **Microsoft Visual Studio <version> ** e aprire un prompt dei comandi
per gli sviluppatori.
3. Digitare ildasm nella finestra del Prompt dei comandi per gli sviluppatori di Visual Studio e premere
INVIO. Verrà visualizzata la finestra IL DASM.
4. Nel menu File della finestra IL DASM selezionare File > Apri . Fare doppio clic su **Visual <version>
Studio **, quindi fare doppio clic su progetti . Aprire la cartella per il progetto e cercare nella cartella
bin/Debug il file nome progetto.dll. Fare doppio clic su nome progetto.dll. Una nuova finestra consente di
visualizzare gli attributi del progetto, oltre ai riferimenti ad altri moduli e assembly. Notare che gli spazi
dei nomi Microsoft.Office.Interop.Excel e Microsoft.Office.Interop.Word sono inclusi nell'assembly.
Per impostazione predefinita in Visual Studio, il compilatore importa i tipi necessari da un assembly di
interoperabilità primario a cui si fa riferimento nell'assembly.
Per altre informazioni, vedere Procedura: Visualizzare il contenuto dell'assembly.
5. Fare doppio clic sull'icona MANIFESTO . Viene visualizzata una finestra che include un elenco di assembly
che contengono gli elementi a cui fa riferimento il progetto. Microsoft.Office.Interop.Excel e
Microsoft.Office.Interop.Word non sono inclusi nell'elenco. Poiché i tipi che è necessario importare
nell'assembly per il progetto, i riferimenti a un assembly di interoperabilità primario non sono necessari.
Ciò rende più semplice la distribuzione. Gli assembly di interoperabilità primari non devono essere
presenti nel computer dell'utente e, poiché un'applicazione non richiede la distribuzione di una versione
specifica di un assembly di interoperabilità primario, è possibile progettare applicazioni compatibili con
più versioni di Office, a condizione che le API necessarie siano presenti in tutte le versioni.
Poiché la distribuzione di assembly di interoperabilità primari non è più necessaria, è possibile creare
un'applicazione in scenari avanzati che sia compatibile con più versioni di Office, incluse le versioni
precedenti. Tuttavia, questo metodo funziona solo se il codice non usa alcuna API che non sia disponibile
nella versione di Office che si sta usando. Non è sempre chiaro se una particolare API fosse disponibile in
una versione precedente e per tale motivo l'utilizzo di versioni precedenti di Office non è consigliato.

NOTE
Non sono stati pubblicati PIA prima di Office 2003. Di conseguenza, l'unico modo per generare un assembly di
interoperabilità per Office 2002 o versioni precedenti consiste nell'importare il riferimento COM.

6. Chiudere la finestra del manifesto e l'assembly.


Per ripristinare la dipendenza di assembly di interoperabilità primario
1. In Esplora soluzioni fare clic sul pulsante Mostra tutti i file . Espandere la cartella Riferimenti e
scegliere Microsoft.Office.Interop.Excel . Premere F4 per visualizzare la finestra Proprietà .
2. Nella finestra Proprietà impostare la proprietà Incorpora tipi di interoperabilità da True a False .
3. Ripetere i passaggi 1 e 2 di questa procedura per Microsoft.Office.Interop.Word .
4. In C#, commentare le due chiamate Autofit alla fine del metodo DisplayInExcel .
5. Premere F5 per verificare che il progetto sia ancora eseguito correttamente.
6. Ripetere i passaggi da 1 a 3 della procedura precedente per aprire la finestra dell'assembly. Notare che
Microsoft.Office.Interop.Word e Microsoft.Office.Interop.Excel non sono più presenti nell'elenco di
assembly incorporati.
7. Fare doppio clic sull'icona MANIFESTO e scorrere l'elenco degli assembly di riferimento. Sia
Microsoft.Office.Interop.Word sia Microsoft.Office.Interop.Excel sono inclusi nell'elenco. Poiché
l'applicazione fa riferimento agli assembly di interoperabilità primari di Excel e Word e la proprietà
Incorpora tipi di interoperabilità è impostata su False , entrambi gli assembly devono esistere nel
computer dell'utente finale.
8. In Visual Studio fare clic su Pulisci soluzione nel menu Compila per pulire il progetto completato.

Vedere anche
Proprietà implementate automaticamente (Visual Basic)
Proprietà implementate automaticamente (C#)
Inizializzatori di insieme
Inizializzatori di oggetto e di raccolta
Parametri facoltativi
Passaggio di argomenti in base alla posizione e al nome
Argomenti denominati e facoltativi
Associazione anticipata e tardiva
dinamico
Utilizzo del tipo dinamico
Espressioni lambda (Visual Basic)
Espressioni lambda (C#)
Come usare proprietà indicizzate nella programmazione dell'interoperabilità COM
Procedura dettagliata: Incorporamento delle informazioni sui tipi da assembly di Microsoft Office in Visual
Studio
Procedura dettagliata: incorporamento dei tipi da assembly gestiti
Procedura dettagliata: creazione del primo componente aggiuntivo VSTO per Excel
Interoperabilità COM
Interoperabilità
Esempio di classe COM (Guida per programmatori
C#)
02/11/2020 • 3 minutes to read • Edit Online

Di seguito è riportato un esempio di una classe esposta come oggetto COM. Dopo aver inserito questo codice in
un file con estensione cs e averlo aggiunto al progetto, impostare la proprietà Registra per interoperabilità
COM su True . Per altre informazioni, vedere Procedura: Registrare un componente per l'interoperabilità COM.
Per esporre gli oggetti Visual C# a COM è necessario dichiarare un'interfaccia di classe e un'interfaccia di eventi,
se necessaria, oltre alla classe stessa. Per essere visibili per COM i membri della classe devono rispettare le
regole seguenti:
La classe deve essere public.
Le proprietà, i metodi e gli eventi devono essere public.
Le proprietà e i metodi devono essere dichiarati nell'interfaccia di classe.
Gli eventi devono essere dichiarati nell'interfaccia eventi.
Gli altri membri pubblici della classe che non sono dichiarati in queste interfacce non saranno visibili a COM, ma
saranno visibili ad altri oggetti .NET.
Per esporre le proprietà e i metodi a COM, è necessario dichiararli nell'interfaccia di classe e contrassegnarli con
un attributo DispId , nonché implementarli nella classe stessa. L'ordine con cui i membri vengono dichiarati
nell'interfaccia è quello usato per la vtable COM.
Per esporre gli eventi all'esterno della classe, è necessario dichiararli nell'interfaccia eventi e contrassegnarli con
un attributo DispId . La classe non deve implementare questa interfaccia.
La classe implementa l'interfaccia di classe. È in grado di implementare più interfacce, ma la prima
implementazione sarà quella dell'interfaccia di classe predefinita. A questo punto, implementare i metodi e le
proprietà esposte a COM, che devono essere contrassegnati come public e corrispondere alle dichiarazioni
presenti nell'interfaccia di classe. Dichiarare anche gli eventi generati dalla classe, che devono essere
contrassegnati come public e corrispondere alle dichiarazioni presenti nell'interfaccia eventi.

Esempio
using System.Runtime.InteropServices;

namespace project_name
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface ComClass1Interface
{
}

[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ComClass1Events
{
}

[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(ComClass1Events))]
public class ComClass1 : ComClass1Interface
{
}
}

Vedere anche
Guida per programmatori C#
Interoperabilità
Pagina Compilazione, Progettazione progetti (C#)
Informazioni di riferimento su C#
28/01/2021 • 7 minutes to read • Edit Online

Questa sezione offre informazioni di riferimento su parole chiave, operatori, caratteri speciali, direttive del
preprocessore, opzioni del compilatore ed errori e avvisi del compilatore in C#.

Contenuto della sezione


Parole chiave di C#
Fornisce collegamenti a informazioni su sintassi e parole chiave di C#.
Operatori C#
Fornisce collegamenti a informazioni su sintassi e operatori di C#.
Caratteri speciali di C#
Fornisce collegamenti a informazioni sui caratteri speciali contestuali di C# e sul relativo utilizzo.
Direttive per il preprocessore C#
Fornisce collegamenti a informazioni sui comandi del compilatore per l'incorporamento nel codice sorgente C#.
Opzioni del compilatore C#
Include informazioni sulle opzioni del compilatore e su come usarle.
Errori del compilatore C#
Include frammenti di codice che illustrano la causa e la correzione per gli errori del compilatore e gli avvisi di C#.
Specifiche del linguaggio C#
Specifica del linguaggio C# 6.0. Si tratta di una proposta bozza per il linguaggio C# 6.0. Questo documento verrà
perfezionato tramite l'utilizzo del Comitato degli standard ECMA per C#. La versione 5.0 è stata rilasciata a
dicembre 2017 come documento Standard ECMA-334 5th Edition.
Le funzionalità che sono state implementate nelle versioni di C# successive alla 6.0 sono rappresentate in
proposte di specifica del linguaggio. Questi documenti descrivono i progetti per la specifica del linguaggio per
l'aggiunta di queste nuove funzionalità. Si trovano nel modulo bozza proposta. Queste specifiche verranno
perfezionate e inviate al Comitato degli standard ECMA per la revisione formale e l'integrazione in una versione
futura dello standard C#.
Proposte specifiche per C# 7,0
Esistono varie nuove funzionalità implementate in C# 7.0, tra le quali criteri di ricerca, funzioni locali,
dichiarazioni di variabili out, espressioni throw, valori letterali binari e separatori di cifre. Questa cartella
contiene le specifiche per ognuna di tali funzionalità.
Proposte specifiche per C# 7,1
In C# 7.1 sono state aggiunte nuove funzionalità. Prima di tutto, è possibile scrivere un metodo Main che
restituisce Task o Task<int> . In questo modo è possibile aggiungere il modificatore async a Main .
L'espressione default può essere usata senza un tipo nelle posizioni in cui il tipo può essere dedotto. Possono
essere dedotti anche i nomi dei membri delle tuple. Infine, è possibile usare criteri di ricerca con generics.
Proposte specifiche per C# 7,2
In C# 7.2 sono state aggiunte varie piccole funzionalità. È possibile passare argomenti per riferimento di sola
lettura tramite la parola chiave in . Sono state introdotte numerose modifiche di basso livello per supportare la
sicurezza in fase di compilazione per Span e i tipi correlati. È possibile usare argomenti denominati in cui gli
argomenti successivi sono posizionali, in alcune situazioni. Il modificatore di accesso private protected
consente di specificare che i chiamanti sono limitati ai tipi derivati implementati nello stesso assembly. L'
operatore ?: può essere risolto come riferimento a una variabile. È anche possibile formattare i numeri
esadecimali e binari usando un separatore di cifra iniziale.
Proposte specifiche per C# 7,3
C# 7.3 è un'altra versione secondaria che include numerosi piccoli aggiornamenti. È possibile usare nuovi vincoli
per i parametri di tipo generico. Altre modifiche semplificano l' fixed utilizzo dei campi, incluso l'utilizzo delle
stackalloc allocazioni. È possibile riassegnare le variabili locali dichiarate con la ref parola chiave per fare
riferimento a una nuova risorsa di archiviazione. È possibile posizionare attributi in proprietà implementate
automaticamente destinate al campo di supporto generato dal compilatore. È possibile usare variabili di
espressione negli inizializzatori. Le tuple possono essere confrontate per verificarne l'uguaglianza (o la
disuguaglianza). Sono stati introdotti anche alcuni miglioramenti per la risoluzione dell'overload.
Proposte specifiche per C# 8,0
C# 8,0 è disponibile con .NET Core 3,0. Le funzionalità includono i tipi di riferimento Nullable, i criteri di ricerca
ricorsivi, i metodi di interfaccia predefiniti, i flussi asincroni, gli intervalli e gli indici, il modello basato sull'utilizzo
di dichiarazioni, l'assegnazione di Unione di valori null e i membri dell'istanza di sola lettura.
Proposte specifiche per C# 9,0
C# 9,0 è disponibile con .NET 5,0. Le funzionalità includono i record, le istruzioni di primo livello, i miglioramenti
dei criteri di ricerca, setter solo init, nuove espressioni tipizzate di destinazione, inizializzatori di modulo,
estensione di metodi parziali, funzioni anonime statiche, espressioni condizionali tipizzate come destinazione,
tipi restituiti covarianti, GetEnumerator di estensione in cicli foreach, parametri di eliminazione lambda, attributi
su funzioni locali, interi con dimensione nativa, puntatori a funzione, non eliminano il flag localsinit e
annotazioni di

Sezioni correlate
Uso dell'ambiente di sviluppo di Visual Studio per C#
Fornisce i collegamenti ad argomenti relativi a concetti e attività che descrivono IDE e l'editor.
Guida per programmatori C#
Include informazioni su come usare il linguaggio di programmazione C#.
Controllo delle versioni del linguaggio C#
28/01/2021 • 9 minutes to read • Edit Online

Il compilatore C# più recente determina la versione di un linguaggio predefinito in base ai framework di


destinazione del progetto. Visual Studio non fornisce un'interfaccia utente per modificare il valore, ma è
possibile modificarlo modificando il file csproj . La scelta del valore predefinito consente di usare la versione più
recente del linguaggio compatibile con il Framework di destinazione. È possibile trarre vantaggio dall'accesso
alle funzionalità più recenti del linguaggio compatibili con la destinazione del progetto. Questa scelta predefinita
garantisce anche di non usare un linguaggio che richiede tipi o comportamenti di runtime non disponibili nel
Framework di destinazione. La scelta di una versione della lingua più recente di quella predefinita può causare
difficoltà nella diagnosi degli errori di runtime e della fase di compilazione.
Le regole in questo articolo si applicano al compilatore fornito con Visual Studio 2019 o .NET SDK. I compilatori
C# che fanno parte dell'installazione di Visual Studio 2017 o di versioni precedenti di .NET Core SDK usano C#
7.0 come destinazione per impostazione predefinita.
C# 8,0 è supportato solo in .NET Core 3. x e versioni successive. Molte delle funzionalità più recenti richiedono la
libreria e le funzionalità di runtime introdotte in .NET Core 3. x:
L' implementazione dell'interfaccia predefinita richiede nuove funzionalità in CLR di .net core 3,0.
I flussi asincroni richiedono i nuovi tipi System.IAsyncDisposable ,
System.Collections.Generic.IAsyncEnumerable<T> e System.Collections.Generic.IAsyncEnumerator<T> .
Per gli indici e gli intervalli sono necessari i nuovi tipi System.Index e System.Range .
I tipi di riferimento Nullable fanno uso di diversi attributi per fornire avvisi migliori. Questi attributi sono stati
aggiunti in .NET Core 3,0. Altri Framework di destinazione non sono stati annotati con nessuno di questi
attributi. Questo significa che gli avvisi Nullable potrebbero non riflettere accuratamente i potenziali
problemi.
C# 9,0 è supportato solo in .NET 5 e versioni più recenti.

Valori predefiniti
Il compilatore determina un'impostazione predefinita in base a queste regole:

IM P O STA Z IO N E P REDEF IN ITA DEL L A


F RA M EW O RK DI DEST IN A Z IO N E VERSIO N VERSIO N E DEL L IN GUA GGIO C #

.NET 5. x C# 9.0

.NET Core 3.x C# 8.0

.NET Core 2.x C# 7.3

.NET Standard 2.1 C# 8.0

.NET Standard 2.0 C# 7.3

.NET Standard 1.x C# 7.3

.NET Framework all C# 7.3


Quando il progetto ha come destinazione un framework in anteprima con una versione del linguaggio in
anteprima corrispondente, la versione del linguaggio usata è la versione del linguaggio in anteprima. Si usano le
funzionalità più recenti con questa anteprima in qualsiasi ambiente, senza influire sui progetti destinati a una
versione di .NET Core rilasciata.

IMPORTANT
Visual Studio 2017 ha aggiunto una <LangVersion>latest</LangVersion> voce a tutti i file di progetto creati. Ciò
significava C# 7,0 quando è stato aggiunto. Tuttavia, una volta eseguito l'aggiornamento a Visual Studio 2019, significa la
versione rilasciata più recente, indipendentemente dal framework di destinazione. Questi progetti ora sostituiscono il
comportamento predefinito. È necessario modificare il file di progetto e rimuovere il nodo. Il progetto utilizzerà quindi la
versione del compilatore consigliata per il Framework di destinazione.

Sostituire un valore predefinito


Se è necessario specificare in modo esplicito la versione di C#, è possibile farlo in diversi modi:
Modificare manualmente il file di progetto.
Impostare la versione del linguaggio per più progetti in una sottodirectory.
Configurare l' -langversion opzione del compilatore.

TIP
Per conoscere la versione del linguaggio attualmente in uso, inserire (maiuscole/minuscole #error version ) nel codice.
In questo modo il compilatore genera una diagnostica, CS8304, con un messaggio contenente la versione del compilatore
usata e la versione corrente della lingua selezionata.

Modificare il file di progetto


È possibile impostare la versione del linguaggio nel file di progetto. Ad esempio, se si vuole accedere
esplicitamente alle funzionalità di anteprima, aggiungere un elemento simile a questo:

<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>

Il valore preview usa la versione del linguaggio C# in anteprima disponibile più recente supportata dal
compilatore.
Configurare più progetti
Per configurare più progetti, è possibile creare un file Director y. Build. props che contiene l' <LangVersion>
elemento. Questa operazione viene in genere eseguita nella directory della soluzione. Aggiungere il codice
seguente a un file Director y. Build. props nella directory della soluzione:

<Project>
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>

Compilazioni in tutte le sottodirectory della directory che contiene il file utilizzerà la versione di anteprima C#.
Per altre informazioni, vedere l'articolo Personalizzare la compilazione.
Informazioni di riferimento sulle versioni del linguaggio C#
La tabella seguente illustra tutte le versioni del linguaggio C# correnti. Il compilatore potrebbe non
comprendere necessariamente ogni valore se è meno recente. Se si installa la versione più recente di .NET SDK,
sarà possibile accedere a tutti gli elementi elencati.

VA LO RE SIGN IF IC ATO

preview Il compilatore accetta tutte le sintassi di linguaggio valide


dalla versione di anteprima più recente.

latest Il compilatore accetta la sintassi dalla versione rilasciata più


recente del compilatore (inclusa la versione secondaria).

latestMajor ( default ) Il compilatore accetta la sintassi dalla versione principale più


recente rilasciata del compilatore.

9.0 Il compilatore accetta solo la sintassi inclusa in C# 9,0 o


versioni precedenti.

8.0 Il compilatore accetta solo la sintassi inclusa in C# 8.0 o


versione precedente.

7.3 Il compilatore accetta solo la sintassi inclusa in C# 7.3 o


versione precedente.

7.2 Il compilatore accetta solo la sintassi inclusa in C# 7.2 o


versione precedente.

7.1 Il compilatore accetta solo la sintassi inclusa in C# 7.1 o


versione precedente.

7 Il compilatore accetta solo la sintassi inclusa in C# 7.0 o


versione precedente.

6 Il compilatore accetta solo la sintassi inclusa in C# 6.0 o


versione precedente.

5 Il compilatore accetta solo la sintassi inclusa in C# 5.0 o


versione precedente.

4 Il compilatore accetta solo la sintassi inclusa in C# 4.0 o


versione precedente.

3 Il compilatore accetta solo la sintassi inclusa in C# 3.0 o


versione precedente.

ISO-2 (o 2 ) Il compilatore accetta solo la sintassi inclusa in ISO/IEC


23270:2006 C# (2,0).

ISO-1 (o 1 ) Il compilatore accetta solo la sintassi inclusa in ISO/IEC


23270:2003 C# (1.0/1.2).
TIP
Aprire il prompt dei comandi per gli sviluppatori per Visual Studioed eseguire il comando seguente per visualizzare l'elenco
delle versioni della lingua disponibili nel computer.

csc -langversion:?

Se si sta interrogando l'opzione di compilazione -langversion , verrà stampato un valore simile al seguente:

Supported language versions:


default
1
2
3
4
5
6
7.0
7.1
7.2
7.3
8.0
9.0 (default)
latestmajor
preview
latest
Tipi di valore (riferimenti per C#)
28/01/2021 • 6 minutes to read • Edit Online

I tipi di valore e i tipi di riferimento sono le due categorie principali di tipi C#. Una variabile di un tipo di valore
contiene un'istanza del tipo. Questo comportamento è diverso da una variabile di un tipo riferimento, che
contiene un riferimento a un'istanza del tipo. Per impostazione predefinita, durante l' assegnazione, passando un
argomento a un metodo e restituendo il risultato di un metodo, vengono copiati i valori delle variabili. Nel caso
di variabili di tipo valore, vengono copiate le istanze del tipo corrispondenti. L'esempio seguente illustra questo
comportamento:

using System;

public struct MutablePoint


{
public int X;
public int Y;

public MutablePoint(int x, int y) => (X, Y) = (x, y);

public override string ToString() => $"({X}, {Y})";


}

public class Program


{
public static void Main()
{
var p1 = new MutablePoint(1, 2);
var p2 = p1;
p2.Y = 200;
Console.WriteLine($"{nameof(p1)} after {nameof(p2)} is modified: {p1}");
Console.WriteLine($"{nameof(p2)}: {p2}");

MutateAndDisplay(p2);
Console.WriteLine($"{nameof(p2)} after passing to a method: {p2}");
}

private static void MutateAndDisplay(MutablePoint p)


{
p.X = 100;
Console.WriteLine($"Point mutated in a method: {p}");
}
}
// Expected output:
// p1 after p2 is modified: (1, 2)
// p2: (1, 200)
// Point mutated in a method: (100, 200)
// p2 after passing to a method: (1, 200)

Come illustrato nell'esempio precedente, le operazioni su una variabile di tipo valore influiscono solo su tale
istanza del tipo di valore, archiviata nella variabile.
Se un tipo di valore contiene un membro dati di un tipo di riferimento, quando viene copiata un'istanza del tipo
di valore viene copiato solo il riferimento all'istanza del tipo di riferimento. Sia la copia che l'istanza del tipo di
valore originale hanno accesso alla stessa istanza del tipo di riferimento. L'esempio seguente illustra questo
comportamento:
using System;
using System.Collections.Generic;

public struct TaggedInteger


{
public int Number;
private List<string> tags;

public TaggedInteger(int n)
{
Number = n;
tags = new List<string>();
}

public void AddTag(string tag) => tags.Add(tag);

public override string ToString() => $"{Number} [{string.Join(", ", tags)}]";


}

public class Program


{
public static void Main()
{
var n1 = new TaggedInteger(0);
n1.AddTag("A");
Console.WriteLine(n1); // output: 0 [A]

var n2 = n1;
n2.Number = 7;
n2.AddTag("B");

Console.WriteLine(n1); // output: 0 [A, B]


Console.WriteLine(n2); // output: 7 [A, B]
}
}

NOTE
Per rendere il codice meno soggetto a errori e più affidabile, definire e utilizzare tipi di valore non modificabili. Questo
articolo usa tipi di valore modificabili solo a scopo dimostrativo.

Tipi di tipi di valore e vincoli di tipo


Un tipo di valore può essere uno dei due tipi seguenti:
tipo di strutturache incapsula i dati e la funzionalità correlata
un tipo di enumerazione, definito da un set di costanti denominate e rappresenta una scelta o una
combinazione di scelte
Un tipo di valore Nullable T? rappresenta tutti i valori del tipo di valore sottostante T e un valore null
aggiuntivo. Non è possibile assegnare null a una variabile di un tipo di valore, a meno che non si tratti di un
tipo di valore Nullable.
È possibile utilizzare il struct vincolo per specificare che un parametro di tipo è un tipo di valore non nullable. I
tipi di struttura e di enumerazione soddisfano il struct vincolo. A partire da C# 7,3, è possibile usare
System.Enum in un vincolo della classe di base (noto come vincolo enum) per specificare che un parametro di
tipo è un tipo di enumerazione.

Tipi di valore predefiniti


In C# sono disponibili i seguenti tipi di valore predefiniti, noti anche come tipi semplici:
Tipi numerici integrali
Tipi numerici a virgola mobile
bool che rappresenta un valore booleano
char che rappresenta un carattere UTF-16 Unicode
Tutti i tipi semplici sono tipi di struttura e differiscono da quelli di altri tipi di struttura in quanto consentono
determinate operazioni aggiuntive:
È possibile utilizzare valori letterali per fornire un valore di un tipo semplice. Ad esempio, 'A' è un
valore letterale del tipo char e 2001 è un valore letterale del tipo int .
È possibile dichiarare costanti dei tipi semplici con la parola chiave const. Non è possibile avere costanti
di altri tipi di struttura.
Le espressioni costanti, i cui operandi sono tutte costanti dei tipi semplici, vengono valutate in fase di
compilazione.
A partire da C# 7,0, C# supporta le Tuple di valori. Una tupla di valori è un tipo di valore, ma non un tipo
semplice.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Tipi valore
Tipi semplici
Variabili

Vedi anche
Informazioni di riferimento su C#
System.ValueType
Tipi riferimento
Tipi numerici integrali (Riferimenti per C#)
21/04/2020 • 6 minutes to read • Edit Online

I tipi numerici integrali rappresentano numeri interi. Tutti i tipi numerici integrali sono tipi di valore. Sono anche
tipi semplici e possono essere inizializzati con valori letterali. Tutti i tipi numerici integrali supportano gli
operatori aritmetici, logici bit per bit, di confrontoe di uguaglianza.

Caratteristiche dei tipi integrali


C# supporta i tipi integrali predefiniti seguenti:

T IP O / PA RO L A C H IAVE C # RA N GE DIM EN SIO N E T IP O . N ET

sbyte Da -128 a 127 Valore intero con segno a 8 System.SByte


bit

byte da 0 a 255 Intero senza segno a 8 bit System.Byte

short Da -32.768 a 32.767 Valore intero a 16 bit con System.Int16


segno

ushort Da 0 a 65.535 Intero senza segno a 16 bit System.UInt16

int Da -2.147.483.648 a Valore intero a 32 bit con System.Int32


2.147.483.647 segno

uint Da 0 a 4.294.967.295 Intero senza segno a 32 bit System.UInt32

long Da - Valore intero a 64 bit con System.Int64


9.223.372.036.854.775.808 segno
a
9.223.372.036.854.775.807

ulong Da 0 a Intero senza segno a 64 bit System.UInt64


18.446.744.073.709.551.61
5

Nella tabella precedente ogni tipo/parola chiave C# nella colonna più a sinistra è un alias per il tipo .NET
corrispondente. Sono intercambiabili. Ad esempio, le dichiarazioni seguenti dichiarano variabili dello stesso tipo:

int a = 123;
System.Int32 b = 123;

Il valore predefinito di ogni tipo integrale è zero 0 . Ogni tipo integrale ha costanti MinValue e MaxValue che
specificano il valore minimo e massimo del tipo.
Usare la struttura System.Numerics.BigInteger per rappresentare un intero con segno senza limiti inferiori o
superiori.

Valori letterali Integer


I valori letterali integer possono essere
decimal: senza prefisso
esadecimale: con 0x il 0X prefisso o
binary: 0b con 0B il prefisso o (disponibile in C , 7.0 e versioni successive)
Il codice seguente illustra un esempio di ogni codice:The following code demonstrates an example of each:
var decimalLiteral = 42;
var hexLiteral = 0x2A;
var binaryLiteral = 0b_0010_1010;

Nell'esempio precedente viene illustrato _ anche l'utilizzo di come separatore di cifre, che è supportato a
partire da C . È possibile utilizzare il separatore di cifre con tutti i tipi di valori letterali numerici.
Il tipo di un valore letterale integer è determinato dal relativo suffisso come segue:
Se il valore letterale non ha alcun suffisso, il tipo è int uint il long ulong primo dei seguenti tipi in cui
il relativo valore può essere rappresentato: , , , .
Se il valore U letterale u è suffisso da o , il tipo è uint il ulong primo dei seguenti tipi in cui il relativo
valore può essere rappresentato: , .
Se il valore L letterale l è suffisso da o , il tipo è long il ulong primo dei seguenti tipi in cui il relativo
valore può essere rappresentato: , .

NOTE
È possibile utilizzare la l lettera minuscola come suffisso. Tuttavia, questo genera un l avviso del compilatore
1 perché la lettera può essere confusa con la cifra . Utilizzare L per chiarezza.

Se il valore letterale uL è ul LU suffisso lU da lu UL , Ul , ulong ,,, Lu , o , il tipo è .

Se il valore rappresentato da un valore letterale Integer supera UInt64.MaxValue, si verifica un errore di


compilazione CS1021.
Se il tipo determinato di int un valore letterale integer è e il valore rappresentato dal valore sbyte byte
letterale è compreso nell'intervallo del tipo di destinazione, il valore può essere convertito in modo implicito
short in , , ushort , , uint o ulong :

byte a = 17;
byte b = 300; // CS0031: Constant value '300' cannot be converted to a 'byte'

Come illustrato nell'esempio precedente, se il valore letterale non è compreso nell'intervallo del tipo di
destinazione, si verifica un errore del compilatore CS0031.
È inoltre possibile utilizzare un cast per convertire il valore rappresentato da un valore letterale integer nel tipo
diverso dal tipo determinato del valore letterale:

var signedByte = (sbyte)42;


var longVariable = (long)42;

Conversioni
È possibile convertire qualsiasi tipo numerico integrale in qualsiasi altro tipo numerico integrale. Se il tipo di
destinazione può archiviare tutti i valori del tipo di origine, la conversione è implicita. In caso contrario, è
necessario utilizzare un'espressione cast per eseguire una conversione esplicita. Per ulteriori informazioni,
consultate Conversioni numeriche predefinite.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Tipi integrali
Valori letterali Integer

Vedere anche
Informazioni di riferimento su C#
Tipi valore
Tipi a virgola mobile
Stringhe di formato numerico standard
Valori numerici in .NET
Tipi numerici a virgola mobile (riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

I tipi numerici a virgola mobile rappresentano i numeri reali. Tutti i tipi numerici a virgola mobile sono tipi di
valore. Sono anche tipi semplici e possono essere inizializzati con valori letterali. Tutti i tipi numerici a virgola
mobile supportano gli operatori aritmetici, di confrontoe di uguaglianza .

Caratteristiche dei tipi a virgola mobile


C# supporta i tipi a virgola mobile predefiniti seguenti:

T IP O / PA RO L A IN T ERVA L LO
C H IAVE C # A P P RO SSIM AT IVO P REC ISIO N DIM EN SIO N E T IP O . N ET

float Compreso tra ±1.5 x ~6-9 cifre 4 byte System.Single


10− 45 e ±3.4 x 1038

double Compreso tra ±5,0 × ~15-17 cifre 8 byte System.Double


10− 324 e ±1,7 ×
10308

decimal Compreso tra ±1.0 x 28-29 cifre 16 byte System.Decimal


10-28 e ±7.9228 x
1028

Nella tabella precedente ogni tipo/parola chiave C# nella colonna più a sinistra è un alias per il tipo .NET
corrispondente. Sono intercambiabili. Ad esempio, le dichiarazioni seguenti dichiarano variabili dello stesso tipo:

double a = 12.3;
System.Double b = 12.3;

Il valore predefinito di ogni tipo a virgola mobile è zero 0 . Ogni tipo a virgola mobile ha costanti MinValue e
MaxValue che specificano il valore finito minimo e massimo del tipo. I tipi float e double forniscono anche
costanti che rappresentano valori Not-a-Number (NaN) e infiniti. Ad esempio, il tipo double fornisce le costanti
seguenti: Double.NaN, Double.NegativeInfinity e Double.PositiveInfinity.
Dato che il tipo decimal è caratterizzato da una maggiore precisione e da un intervallo più piccolo rispetto a
float e double , è appropriato per calcoli finanziari e monetari.

È possibile combinare i tipi integrali e float i double tipi e in un'espressione. In questo caso, i tipi integrali
vengono convertiti in modo implicito in uno dei tipi a virgola mobile e, se necessario, il float tipo viene
convertito in modo implicito in double . L'espressione viene valutata nel modo seguente:
Se è presente double un tipo nell'espressione, l'espressione restituisce double o a bool nei confronti
relazionali e di uguaglianza.
Se non è presente alcun double tipo nell'espressione, l'espressione restituisce float o a bool nei confronti
relazionali e di uguaglianza.
È anche possibile combinare tipi integrali e il decimal tipo in un'espressione. In questo caso, i tipi integrali
vengono convertiti in modo implicito nel decimal tipo e l'espressione restituisce decimal oppure a bool nei
confronti relazionali e di uguaglianza.
Non è possibile combinare il decimal tipo con float i double tipi e in un'espressione. In questo caso, se si
desidera eseguire operazioni aritmetiche, di confronto o di uguaglianza, è necessario convertire in modo
esplicito gli operandi dal o al decimal tipo, come illustrato nell'esempio seguente:

double a = 1.0;
decimal b = 2.1m;
Console.WriteLine(a + (double)b);
Console.WriteLine((decimal)a + b);

È possibile usare stringhe di formato numerico standard oppure stringhe di formato numerico personalizzato
per formattare un valore a virgola mobile.

Valori letterali reali


Il tipo di un valore letterale reale è determinato dal suffisso, come indicato di seguito:
Il valore letterale senza suffisso o con il d D suffisso o è di tipo double
Il valore letterale con il f F suffisso o è di tipo float
Il valore letterale con il m M suffisso o è di tipo decimal
Il codice seguente illustra un esempio di ogni:

double d = 3D;
d = 4d;
d = 3.934_001;

float f = 3_000.5F;
f = 5.4f;

decimal myMoney = 3_000.5m;


myMoney = 400.75M;

Nell'esempio precedente viene inoltre illustrato l'utilizzo di _ come separatore di cifre, supportato a partire da
C# 7,0. È possibile usare il separatore di cifre con tutti i tipi di valori letterali numerici.
È anche possibile usare la notazione scientifica, ovvero specificare una parte dell'esponente di un valore letterale
reale, come illustrato nell'esempio seguente:

double d = 0.42e2;
Console.WriteLine(d); // output 42

float f = 134.45E-2f;
Console.WriteLine(f); // output: 1.3445

decimal m = 1.5E6m;
Console.WriteLine(m); // output: 1500000

Conversioni
Esiste una sola conversione implicita tra tipi numerici a virgola mobile: da float a double . Tuttavia, è possibile
convertire qualsiasi tipo a virgola mobile in qualsiasi altro tipo a virgola mobile con il cast esplicito. Per altre
informazioni, vedere conversioni numeriche predefinite.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Tipi a virgola mobile
Tipo decimale
Valori letterali reali

Vedere anche
Informazioni di riferimento su C#
Tipi valore
Tipi integrali
Stringhe di formato numerico standard
Valori numerici in .NET
System.Numerics.Complex
Conversioni numeriche predefinite (riferimenti per
C#)
02/11/2020 • 8 minutes to read • Edit Online

C# fornisce un set di tipi numerici integrali e a virgola mobile . Esiste una conversione tra due tipi numerici,
impliciti o espliciti. Per eseguire una conversione esplicita, è necessario utilizzare un' espressione cast .

Conversioni numeriche implicite


Nella tabella seguente vengono illustrate le conversioni implicite predefinite tra i tipi numerici incorporati:

DA TO

sbyte short , int , long , float , double o decimal

byte short , ushort , int , uint , long , ulong , float ,


double o decimal

short int , long , float , double o decimal

ushort int , uint , long , ulong , float , double o


decimal

int long , float , double o decimal

uint long , ulong , float , double o decimal

long float , double o decimal

ULONG float , double o decimal

float double

NOTE
Le conversioni implicite da int , uint , long o ulong a float e da long o ulong a double possono causare
una perdita di precisione, ma mai una perdita di un ordine di grandezza. Le altre conversioni numeriche implicite non
perdono mai le informazioni.

Si noti anche che


Qualsiasi tipo numerico integrale è implicitamente convertibile in qualsiasi tipo numerico a virgola
mobile.
Non vi sono conversioni implicite nei byte tipi e sbyte . Non esiste alcuna conversione implicita dai tipi
double e decimal .

Non esiste alcuna conversione implicita tra il tipo decimal e i tipi float o double .
Il valore di un'espressione costante di tipo int (ad esempio, un valore rappresentato da un valore
letterale integer) può essere convertito in modo implicito in sbyte , byte , short , ushort , uint o
ulong , se è compreso nell'intervallo del tipo di destinazione:

byte a = 13;
byte b = 300; // CS0031: Constant value '300' cannot be converted to a 'byte'

Come illustrato nell'esempio precedente, se il valore della costante non è compreso nell'intervallo del
tipo di destinazione, si verifica un errore del compilatore CS0031 .

Conversioni numeriche esplicite


Nella tabella seguente vengono illustrate le conversioni esplicite predefinite tra i tipi numerici incorporati per i
quali non esiste alcuna conversione implicita:

DA TO

sbyte byte , ushort , uint o ulong

byte sbyte

short sbyte , byte , ushort , uint o ulong

ushort sbyte , byte o short

int sbyte , byte , short , ushort , uint o ulong

uint sbyte , byte , short , ushort o int

long sbyte , byte , short , ushort , int , uint o ulong

ULONG sbyte , byte , short , ushort , int , uint o long

float sbyte , byte , short , ushort , int , uint , long ,


ulong o decimal

double sbyte , byte , short , ushort , int , uint , long ,


ulong , float o decimal

decimal sbyte , byte , short , ushort , int , uint , long ,


ulong , float o double

NOTE
Una conversione numerica esplicita potrebbe causare la perdita di dati o generare un'eccezione, in genere un
OverflowException .

Si noti anche che


Quando si converte un valore di tipo integrale a un altro tipo integrale, il risultato dipende dal contesto di
controllo dell'overflow. In un contesto controllato, la conversione ha esito positivo se il valore di origine è
compreso nell'intervallo del tipo di destinazione. In caso contrario, verrà generata un'eccezione
OverflowException. In un contesto non controllato, la conversione ha sempre esito positivo e procede nel
modo seguente:
Se il tipo di origine è maggiore del tipo di destinazione, il valore di origine viene troncato
rimuovendo i relativi "extra" bit più rilevanti. Il risultato viene quindi trattato come un valore del
tipo di destinazione.
Se il tipo di origine è inferiore al tipo di destinazione, il valore di origine è con segno esteso o con
estensione zero, in modo che sia della stessa dimensione del tipo di destinazione. L'estensione
firma viene usata se il tipo di origine dispone della firma; l'estensione zero viene usata se il tipo di
origine è privo di firma. Il risultato viene quindi trattato come un valore del tipo di destinazione.
Se il tipo di origine ha le stesse dimensioni del tipo di destinazione, il valore di origine viene
considerato un valore del tipo di destinazione.
Quando si esegue una conversione da un valore decimal a un tipo integrale, il valore viene arrotondato
per difetto al valore integrale più vicino. Se il valore integrale risultante non rientra nell'intervallo del tipo
di destinazione, viene generata un'eccezione OverflowException.
Quando si esegue una conversione da un valore double o float a un tipo integrale, il valore viene
arrotondato per difetto al valore integrale più vicino. Se il valore integrale risultante non rientra
nell'intervallo del tipo di destinazione, il risultato dipende dal contesto di controllo dell'overflow. In un
contesto controllato (checked) viene generata un'eccezione OverflowException, mentre in un contesto
non controllato (unckecked) il risultato è un valore non specificato del tipo di destinazione.
Per una conversione da double a float , il valore double viene arrotondato al valore float più vicino.
Se il double valore è troppo piccolo o troppo grande per adattarsi al float tipo, il risultato è zero o
infinito.
Quando si esegue una conversione da float o double a decimal , il valore di origine viene convertito
nella rappresentazione decimal e arrotondato al numero più vicino successivo alla ventottesima
posizione decimale, se necessario. A seconda dell'entità del valore di origine, è possibile che si verifichi
uno dei risultati seguenti:
Se il valore di origine è troppo piccolo per essere rappresentato come decimal , il risultato diventa
zero.
Se il valore di origine è NaN (non un numero), infinito o troppo grande per essere rappresentato
come decimal , viene generata un'eccezione OverflowException.
Quando si esegue decimal la conversione in float o double , il valore di origine viene arrotondato al
float valore o più vicino double , rispettivamente.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Conversioni numeriche implicite
Conversioni numeriche esplicite

Vedere anche
Informazioni di riferimento su C#
Cast e conversioni di tipi
bool (riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La bool parola chiave Type è un alias per il System.Boolean tipo di struttura .NET che rappresenta un valore
booleano, che può essere true o false .
Per eseguire operazioni logiche con valori di bool tipo, utilizzare operatori logici booleani . Il bool tipo è il tipo
di risultato degli operatori di confronto e di uguaglianza . Un' bool espressione può essere un'espressione
condizionale di controllo nelle istruzioni if, do, whilee for e nell' operatore ?: condizionale .
Il valore predefinito del bool tipo è false .

Valori letterali
È possibile usare i true false valori letterali e per inizializzare una bool variabile o per passare un bool
valore:

bool check = true;


Console.WriteLine(check ? "Checked" : "Not checked"); // output: Checked

Console.WriteLine(false ? "Checked" : "Not checked"); // output: Not checked

Logica booleana a tre valori


Usare il bool? tipo nullable, se è necessario supportare la logica a tre valori, ad esempio quando si lavora con
database che supportano un tipo booleano a tre valori. Per gli operandi bool? , gli operatori & e | predefiniti
supportano la logica a tre valori. Per altre informazioni, vedere la sezione Operatori logici booleani nullable
dell'articolo Operatori logici booleani.
Per ulteriori informazioni sui tipi di valore Nullable, vedere tipi di valore Nullable.

Conversioni
In C# sono disponibili solo due conversioni che coinvolgono il bool tipo. Si tratta di una conversione implicita
nel tipo nullable corrispondente bool? e di una conversione esplicita dal bool? tipo. .NET fornisce tuttavia
metodi aggiuntivi che è possibile usare per eseguire la conversione da o verso il bool tipo. Per ulteriori
informazioni, vedere la sezione conversione da e verso valori booleani della System.Boolean pagina di
riferimento all'API.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione tipo bool della specifica del linguaggio C#.

Vedi anche
Informazioni di riferimento su C#
Tipi valore
Operatori true e false
char (riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

La char parola chiave Type è un alias per il System.Char tipo di struttura .NET che rappresenta un carattere
Unicode UTF-16.

T IP O RA N GE DIM EN SIO N E T IP O . N ET

char U+0000 a U+FFFF 16 bit System.Char

Il valore predefinito del char tipo è \0 , ovvero U + 0000.


Il char tipo supporta operatori di confronto, uguaglianza, incrementoe decremento . Inoltre, per gli char
operandi, gli operatori logici aritmetici e bit per bit eseguono un'operazione sui codici carattere corrispondenti e
producono il risultato del int tipo.
Il tipo stringa rappresenta il testo come una sequenza di char valori.

Valori letterali
È possibile specificare un char valore con:
valore letterale carattere.
sequenza di escape Unicode, \u seguita dalla rappresentazione esadecimale a quattro simboli di un codice
carattere.
sequenza di escape esadecimale, \x seguita dalla rappresentazione esadecimale di un codice carattere.

var chars = new[]


{
'j',
'\u006A',
'\x006A',
(char)106,
};
Console.WriteLine(string.Join(" ", chars)); // output: j j j j

Come illustrato nell'esempio precedente, è anche possibile eseguire il cast del valore di un codice carattere nel
char valore corrispondente.

NOTE
Nel caso di una sequenza di escape Unicode, è necessario specificare tutte e quattro le cifre esadecimali. Ovvero, \u006A
è una sequenza di escape valida, mentre \u06A e \u6A non sono validi.
Nel caso di una sequenza di escape esadecimale, è possibile omettere gli zeri iniziali. Ovvero le \x006A \x06A \x6A
sequenze di escape, e sono valide e corrispondono allo stesso carattere.

Conversioni
Il char tipo è convertibile in modo implicito nei tipi integrali seguenti: ushort ,, int uint , long e ulong . È
anche convertibile in modo implicito nei tipi numerici a virgola mobile predefiniti: float , double e decimal . È
convertibile in modo esplicito in sbyte byte short tipi integrali, e.
Non esistono conversioni implicite da altri tipi al char tipo. Tuttavia, qualsiasi tipo numerico integrale o a
virgola mobile è convertibile in modo esplicito in char .

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione tipi integrali della specifica del linguaggio C#.

Vedi anche
Informazioni di riferimento su C#
Tipi valore
Stringhe
System.Text.Rune
Codifica dei caratteri in .NET
Tipi di enumerazione (riferimenti per C#)
28/01/2021 • 6 minutes to read • Edit Online

Un tipo di enumerazione o un tipo enum è un tipo di valore definito da un set di costanti denominate del tipo
numerico integrale sottostante. Per definire un tipo di enumerazione, usare la enum parola chiave e specificare i
nomi dei membri enum:

enum Season
{
Spring,
Summer,
Autumn,
Winter
}

Per impostazione predefinita, i valori costanti associati dei membri enum sono di tipo int , mentre iniziano con
zero e aumentano di uno dopo l'ordine di testo della definizione. È possibile specificare in modo esplicito
qualsiasi altro tipo numerico integrale come tipo sottostante di un tipo di enumerazione. È anche possibile
specificare in modo esplicito i valori costanti associati, come illustrato nell'esempio seguente:

enum ErrorCode : ushort


{
None = 0,
Unknown = 1,
ConnectionLost = 100,
OutlierReading = 200
}

Non è possibile definire un metodo all'interno della definizione di un tipo di enumerazione. Per aggiungere
funzionalità a un tipo di enumerazione, creare un metodo di estensione.
Il valore predefinito di un tipo di enumerazione E è il valore prodotto dall'espressione (E)0 , anche se zero
non ha il membro enum corrispondente.
Usare un tipo di enumerazione per rappresentare una scelta da un set di valori che si escludono a vicenda o da
una combinazione di opzioni. Per rappresentare una combinazione di scelte, definire un tipo di enumerazione
come flag di bit.

Tipi di enumerazione come flag di bit


Se si vuole che un tipo di enumerazione rappresenti una combinazione di scelte, definire i membri enum per tali
scelte in modo che una singola scelta sia un campo di bit. Ovvero i valori associati di tali membri enum
dovrebbero essere le potenze di due. Quindi, è possibile usare gli operatori logici bit per bit | o & per
combinare le scelte o le combinazioni Intersect delle scelte, rispettivamente. Per indicare che un tipo di
enumerazione dichiara campi di bit, applicarvi l'attributo Flags . Come illustrato nell'esempio seguente, è anche
possibile includere alcune combinazioni tipiche nella definizione di un tipo di enumerazione.
[Flags]
public enum Days
{
None = 0b_0000_0000, // 0
Monday = 0b_0000_0001, // 1
Tuesday = 0b_0000_0010, // 2
Wednesday = 0b_0000_0100, // 4
Thursday = 0b_0000_1000, // 8
Friday = 0b_0001_0000, // 16
Saturday = 0b_0010_0000, // 32
Sunday = 0b_0100_0000, // 64
Weekend = Saturday | Sunday
}

public class FlagsEnumExample


{
public static void Main()
{
Days meetingDays = Days.Monday | Days.Wednesday | Days.Friday;
Console.WriteLine(meetingDays);
// Output:
// Monday, Wednesday, Friday

Days workingFromHomeDays = Days.Thursday | Days.Friday;


Console.WriteLine($"Join a meeting by phone on {meetingDays & workingFromHomeDays}");
// Output:
// Join a meeting by phone on Friday

bool isMeetingOnTuesday = (meetingDays & Days.Tuesday) == Days.Tuesday;


Console.WriteLine($"Is there a meeting on Tuesday: {isMeetingOnTuesday}");
// Output:
// Is there a meeting on Tuesday: False

var a = (Days)37;
Console.WriteLine(a);
// Output:
// Monday, Wednesday, Saturday
}
}

Per altre informazioni ed esempi, vedere la System.FlagsAttribute pagina di riferimento dell'API e i membri non
esclusivi e la sezione attributo Flags della System.Enum pagina di riferimento dell'API.

Il tipo System. Enum e il vincolo enum


Il System.Enum tipo è la classe di base astratta di tutti i tipi di enumerazione. Sono disponibili diversi metodi per
ottenere informazioni su un tipo di enumerazione e i relativi valori. Per ulteriori informazioni ed esempi, vedere
la System.Enum pagina di riferimento dell'API.
A partire da C# 7,3, è possibile usare System.Enum in un vincolo della classe di base (noto come vincolo enum)
per specificare che un parametro di tipo è un tipo di enumerazione. Qualsiasi tipo di enumerazione soddisfa
anche il struct vincolo, che viene usato per specificare che un parametro di tipo è un tipo di valore non
nullable.

Conversioni
Per qualsiasi tipo di enumerazione esistono conversioni esplicite tra il tipo di enumerazione e il tipo integrale
sottostante. Se si esegue il cast di un valore enum al relativo tipo sottostante, il risultato è il valore integrale
associato di un membro enum.
public enum Season
{
Spring,
Summer,
Autumn,
Winter
}

public class EnumConversionExample


{
public static void Main()
{
Season a = Season.Autumn;
Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2

var b = (Season)1;
Console.WriteLine(b); // output: Summer

var c = (Season)4;
Console.WriteLine(c); // output: 4
}
}

Utilizzare il Enum.IsDefined metodo per determinare se un tipo di enumerazione contiene un membro enum
con il determinato valore associato.
Per qualsiasi tipo di enumerazione esistono rispettivamente le conversioni Boxing e unboxing da e verso il
System.Enum tipo.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Enumerazioni
Valori e operazioni di enumerazione
Operatori logici di enumerazione
Operatori di confronto di enumerazione
Conversioni esplicite dell'enumerazione
Conversioni di enumerazione implicite

Vedi anche
Informazioni di riferimento su C#
Stringhe di formato di enumerazione
Linee guida di progettazione-progettazione enum
Linee guida di progettazione-convenzioni di denominazione enum
istruzione switch
Tipi di struttura (riferimenti per C#)
28/01/2021 • 14 minutes to read • Edit Online

Un tipo di struttura o un tipo di struct è un tipo valore che può incapsulare dati e funzionalità correlate. Usare la
struct parola chiave per definire un tipo di struttura:

public struct Coords


{
public Coords(double x, double y)
{
X = x;
Y = y;
}

public double X { get; }


public double Y { get; }

public override string ToString() => $"({X}, {Y})";


}

I tipi di struttura hanno una semantica di valori. Ovvero una variabile di un tipo di struttura contiene un'istanza
del tipo. Per impostazione predefinita, i valori delle variabili vengono copiati durante l'assegnazione, passando
un argomento a un metodo e restituendo il risultato di un metodo. Nel caso di una variabile di tipo struttura,
viene copiata un'istanza del tipo. Per ulteriori informazioni, vedere tipi di valore.
In genere, i tipi di struttura vengono utilizzati per progettare piccoli tipi incentrati sui dati che forniscono un
comportamento minimo o nullo. .NET, ad esempio, USA i tipi di struttura per rappresentare un numero (sia
Integer che Real), un valore booleano, un carattere Unicode, un' istanza temporale. Se si è interessati al
comportamento di un tipo, è consigliabile definire una classe. I tipi di classe hanno una semantica di riferimento.
Ovvero una variabile di un tipo di classe contiene un riferimento a un'istanza del tipo, non l'istanza stessa.
Poiché i tipi di struttura hanno una semantica di valori, è consigliabile definire tipi di struttura non modificabili .

readonly struct
A partire da C# 7,2, è possibile usare il readonly modificatore per dichiarare che un tipo di struttura non è
modificabile. Tutti i membri dati di uno readonly struct devono essere di sola lettura, come indicato di seguito:
Qualsiasi dichiarazione di campo deve avere il readonly modificatore
Qualsiasi proprietà, inclusa quella implementata automaticamente, deve essere di sola lettura. In C# 9,0 e
versioni successive, una proprietà può avere una init funzione di accesso.

Ciò garantisce che nessun membro di uno readonly struct modifichi lo stato dello struct. In C# 8,0 e versioni
successive, ciò significa che altri membri di istanza eccetto i costruttori sono implicitamente readonly .

NOTE
In uno readonly struct un membro dati di un tipo di riferimento modificabile può ancora mutare il proprio stato. Ad
esempio, non è possibile sostituire un' List<T> istanza, ma è possibile aggiungervi nuovi elementi.

Il codice seguente definisce uno readonly struct con Setter di proprietà solo init, disponibile in C# 9,0 e versioni
successive:
public readonly struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}

public double X { get; init; }


public double Y { get; init; }

public override string ToString() => $"({X}, {Y})";


}

readonly membri di istanza


A partire da C# 8,0, è anche possibile usare il readonly modificatore per dichiarare che un membro di istanza
non modifica lo stato di uno struct. Se non è possibile dichiarare l'intero tipo di struttura come readonly , usare
il readonly modificatore per contrassegnare i membri di istanza che non modificano lo stato dello struct.
All'interno di un readonly membro di istanza non è possibile assegnare ai campi di istanza della struttura.
Tuttavia, un readonly membro può chiamare un membro non readonly . In tal caso, il compilatore crea una
copia dell'istanza della struttura e chiama il non readonly membro sulla copia. Di conseguenza, l'istanza della
struttura originale non viene modificata.
In genere, il readonly modificatore viene applicato ai tipi di membri di istanza seguenti:
Metodi

public readonly double Sum()


{
return X + Y;
}

È anche possibile applicare il readonly modificatore ai metodi che eseguono l'override dei metodi
dichiarati in System.Object :

public readonly override string ToString() => $"({X}, {Y})";

Proprietà e indicizzatori:

private int counter;


public int Counter
{
readonly get => counter;
set => counter = value;
}

Se è necessario applicare il readonly modificatore a entrambe le funzioni di accesso di una proprietà o


un indicizzatore, applicarlo nella dichiarazione della proprietà o dell'indicizzatore.

NOTE
Il compilatore dichiara una get funzione di accesso di una proprietà implementata automaticamente come
readonly , indipendentemente dalla presenza del readonly modificatore in una dichiarazione di proprietà.

In C# 9,0 e versioni successive, è possibile applicare il readonly modificatore a una proprietà o a un


indicizzatore con una init funzione di accesso:
public readonly double X { get; init; }

Non è possibile applicare il readonly modificatore ai membri statici di un tipo di struttura.


Il compilatore può utilizzare il readonly modificatore per le ottimizzazioni delle prestazioni. Per altre
informazioni, vedere scrivere codice C# sicuro ed efficiente.

Limitazioni con la progettazione di un tipo di struttura


Quando si progetta un tipo di struttura, si hanno le stesse funzionalità di un tipo di classe , con le eccezioni
seguenti:
Non è possibile dichiarare un costruttore senza parametri. Ogni tipo di struttura fornisce già un
costruttore senza parametri implicito che produce il valore predefinito del tipo.
Non è possibile inizializzare una proprietà o un campo di istanza in corrispondenza della relativa
dichiarazione. Tuttavia, è possibile inizializzare un campo statico o const o una proprietà statica alla
relativa dichiarazione.
Un costruttore di un tipo di struttura deve inizializzare tutti i campi di istanza del tipo.
Un tipo di struttura non può ereditare da un altro tipo di classe o struttura e non può essere la base di
una classe. Tuttavia, un tipo di struttura può implementare le interfacce.
Non è possibile dichiarare un finalizzatore all'interno di un tipo di struttura.

Creazione di un'istanza di un tipo di struttura


In C# è necessario inizializzare una variabile dichiarata prima di poterla usare. Poiché una variabile di tipo
struttura non può essere null (a meno che non sia una variabile di un tipo di valore Nullable), è necessario
creare un'istanza del tipo corrispondente. Esistono diversi modi per eseguire questa operazione.
In genere, si crea un'istanza di un tipo di struttura chiamando un costruttore appropriato con l' new operatore.
Ogni tipo di struttura ha almeno un costruttore. Si tratta di un costruttore senza parametri implicito che produce
il valore predefinito del tipo. È anche possibile usare un' espressione con valore predefinito per produrre il
valore predefinito di un tipo.
Se tutti i campi di istanza di un tipo di struttura sono accessibili, è anche possibile crearne un'istanza senza l'
new operatore. In tal caso, è necessario inizializzare tutti i campi di istanza prima del primo utilizzo dell'istanza.
L'esempio seguente illustra come eseguire questa operazione:

public static class StructWithoutNew


{
public struct Coords
{
public double x;
public double y;
}

public static void Main()


{
Coords p;
p.x = 3;
p.y = 4;
Console.WriteLine($"({p.x}, {p.y})"); // output: (3, 4)
}
}

Nel caso dei tipi valore predefiniti, usare i valori letterali corrispondenti per specificare un valore del tipo.

Passaggio di variabili di tipo struttura per riferimento


Quando si passa una variabile di tipo struttura a un metodo come argomento o si restituisce un valore del tipo
di struttura da un metodo, viene copiata l'intera istanza di un tipo di struttura. Che può influire sulle prestazioni
del codice in scenari a prestazioni elevate che coinvolgono tipi di struttura di grandi dimensioni. È possibile
evitare la copia dei valori passando una variabile di tipo struttura per riferimento. Usare i ref out in
modificatori di parametro del metodo, o per indicare che un argomento deve essere passato per riferimento.
Usare i riferimenti ref per restituire il risultato di un metodo in base al riferimento. Per altre informazioni, vedere
scrivere codice C# sicuro ed efficiente.

ref struct
A partire da C# 7,2, è possibile usare il ref modificatore nella dichiarazione di un tipo di struttura. Le istanze di
un ref tipo struct vengono allocate nello stack e non possono essere sottoposte a escape nell'heap gestito. Per
assicurarsi che il compilatore limiti l'utilizzo dei ref tipi struct come indicato di seguito:
Uno ref struct non può essere il tipo di elemento di una matrice.
Uno ref struct non può essere un tipo dichiarato di un campo di una classe o di un ref non struct.
Uno ref struct non può implementare le interfacce.
Uno ref struct non può essere boxed in System.ValueType o System.Object .
Uno ref struct non può essere un argomento di tipo.
Una ref variabile struct non può essere acquisita da un' espressione lambda o da una funzione locale.
Una ref variabile struct non può essere usata in un async metodo. Tuttavia, è possibile usare ref le
variabili struct nei metodi sincroni, ad esempio, in quelli che restituiscono Task o Task<TResult> .
ref Non è possibile usare una variabile struct negli iteratori.

In genere, si definisce un ref tipo di struct quando è necessario un tipo che include anche membri dati di ref
tipi struct:

public ref struct CustomRef


{
public bool IsValid;
public Span<int> Inputs;
public Span<int> Outputs;
}

Per dichiarare uno ref struct come readonly , combinare i readonly ref modificatori e nella dichiarazione
del tipo (il readonly modificatore deve essere prima del ref modificatore):

public readonly ref struct ConversionRequest


{
public ConversionRequest(double rate, ReadOnlySpan<double> values)
{
Rate = rate;
Values = values;
}

public double Rate { get; }


public ReadOnlySpan<double> Values { get; }
}

In .NET, esempi di uno ref struct sono System.Span<T> e System.ReadOnlySpan<T> .

vincolo struct
È anche possibile usare la struct parola chiave nel struct vincolo per specificare che un parametro di tipo è
un tipo di valore non nullable. I tipi di struttura e di enumerazione soddisfano il struct vincolo.

Conversioni
Per qualsiasi tipo di struttura (ad eccezione dei tipi ref struct ), esistono conversioni Boxing e unboxing da e
verso i System.ValueType System.Object tipi e. Esistono anche conversioni boxing e unboxing tra un tipo di
struttura e qualsiasi interfaccia implementata.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione structs della specifica del linguaggio C#.
Per ulteriori informazioni sulle funzionalità introdotte in C# 7,2 e versioni successive, vedere le note sulla
proposta di funzionalità seguenti:
Struct di sola lettura
Membri di istanza Readonly
Sicurezza della fase di compilazione per i tipi ref

Vedere anche
Informazioni di riferimento su C#
Linee guida di progettazione-scelta tra classi e struct
Linee guida di progettazione-progettazione struct
Classi e struct
Tipi di tupla (riferimenti per C#)
02/11/2020 • 13 minutes to read • Edit Online

Disponibile in C# 7,0 e versioni successive, la funzionalità Tuple offre una sintassi concisa per raggruppare più
elementi dati in una struttura di dati semplice. Nell'esempio seguente viene illustrato come è possibile
dichiarare una variabile di tupla, inizializzarla e accedere ai relativi membri dati:

(double, int) t1 = (4.5, 3);


Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);


Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Come illustrato nell'esempio precedente, per definire un tipo di tupla, è necessario specificare i tipi di tutti i
relativi membri dati e, facoltativamente, i nomi dei campi. Non è possibile definire metodi in un tipo di tupla, ma
è possibile usare i metodi forniti da .NET, come illustrato nell'esempio seguente:

(double, int) t = (4.5, 3);


Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

A partire da C# 7,3, i tipi di tupla supportano gli operatori di uguaglianza == e != . Per ulteriori informazioni,
vedere la sezione relativa all' uguaglianza delle tuple .
I tipi di tupla sono tipi valore; gli elementi della tupla sono campi pubblici. Che rende i tipi di valore modificabili
delle tuple.

NOTE
La funzionalità Tuple richiede il System.ValueTuple tipo e i tipi generici correlati, ad esempio, System.ValueTuple<T1,T2> che
sono disponibili in .NET Core e .NET Framework 4,7 e versioni successive. Per usare le tuple in un progetto che ha come
destinazione .NET Framework 4.6.2 o versione precedente, aggiungere il pacchetto NuGet System.ValueTuple al
progetto.

È possibile definire tuple con un numero elevato arbitrario di elementi:

var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26); // output: 26

Casi d'uso delle tuple


Uno dei casi d'uso più comuni delle tuple è come tipo restituito del metodo. Ovvero, anziché definire i out
parametri del metodo, è possibile raggruppare i risultati del metodo in un tipo restituito della tupla, come
illustrato nell'esempio seguente:

var xs = new[] { 4, 7, 9 };
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

var ys = new[] { -9, 0, 67, 100 };


var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)


{
if (input is null || input.Length == 0)
{
throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
}

var min = int.MaxValue;


var max = int.MinValue;
foreach (var i in input)
{
if (i < min)
{
min = i;
}
if (i > max)
{
max = i;
}
}
return (min, max);
}

Come illustrato nell'esempio precedente, è possibile usare direttamente l'istanza di tupla restituita o decostruirla
in variabili separate.
È inoltre possibile utilizzare tipi di tupla anziché tipi anonimi; ad esempio, nelle query LINQ. Per ulteriori
informazioni, vedere scelta tra tipi anonimi e Tuple.
In genere, le tuple vengono utilizzate per raggruppare elementi di dati debolmente correlati. Questo è in genere
utile all'interno di metodi di utilità privati e interni. Nel caso di un'API pubblica, è consigliabile definire una classe
o un tipo di struttura .

Nomi dei campi di tupla


È possibile specificare in modo esplicito i nomi dei campi di tupla in un'espressione di inizializzazione della tupla
o nella definizione di un tipo di tupla, come illustrato nell'esempio seguente:

var t = (Sum: 4.5, Count: 3);


Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);


Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

A partire da C# 7,1, se non si specifica un nome di campo, può essere dedotto dal nome della variabile
corrispondente in un'espressione di inizializzazione della tupla, come illustrato nell'esempio seguente:

var sum = 4.5;


var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

Noto come inizializzatori di proiezione della tupla. Il nome di una variabile non è proiettato su un nome di
campo di tupla nei casi seguenti:
Il nome candidato è un nome di membro di un tipo di tupla, ad esempio,, Item3 ToString o Rest .
Il nome candidato è un duplicato di un altro nome di campo di tupla, esplicito o implicito.
In questi casi è possibile specificare in modo esplicito il nome di un campo o accedere a un campo con il nome
predefinito.
I nomi predefiniti dei campi di tupla sono Item1 , Item2 Item3 e così via. È sempre possibile usare il nome
predefinito di un campo, anche quando un nome di campo viene specificato in modo esplicito o dedotto, come
illustrato nell'esempio seguente:

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

L' assegnazione di Tuple e i confronti di uguaglianza delle tuple non accettano nomi di campo.
In fase di compilazione, il compilatore sostituisce i nomi di campo non predefiniti con i nomi predefiniti
corrispondenti. Di conseguenza, i nomi di campo specificati in modo esplicito o dedotti non sono disponibili in
fase di esecuzione.

Assegnazione e decostruzione di Tuple


C# supporta l'assegnazione tra tipi di tupla che soddisfano entrambe le condizioni seguenti:
entrambi i tipi di tupla hanno lo stesso numero di elementi
per ogni posizione di tupla, il tipo dell'elemento di tupla a destra è uguale o convertibile in modo implicito
nel tipo dell'elemento tupla a sinistra corrispondente.
I valori degli elementi di tupla vengono assegnati in base all'ordine degli elementi della tupla. I nomi dei campi
di tupla vengono ignorati e non assegnati, come illustrato nell'esempio seguente:
(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14

(double A, double B) t3 = (2.0, 3.0);


t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14

È anche possibile usare l'operatore = di assegnazione per decostruire un'istanza di tupla in variabili separate.
Questa operazione può essere eseguita in uno dei modi seguenti:
Dichiarare in modo esplicito il tipo di ogni variabile racchiusa tra parentesi:

var t = ("post office", 3.6);


(string destination, double distance) = t;
Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
// Output:
// Distance to post office is 3.6 kilometers.

Usare la var parola chiave all'esterno delle parentesi per dichiarare le variabili tipizzate in modo
implicito e consentire al compilatore di dedurre i tipi:

var t = ("post office", 3.6);


var (destination, distance) = t;
Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
// Output:
// Distance to post office is 3.6 kilometers.

USA variabili esistenti:

var destination = string.Empty;


var distance = 0.0;

var t = ("post office", 3.6);


(destination, distance) = t;
Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
// Output:
// Distance to post office is 3.6 kilometers.

Per ulteriori informazioni sulla decostruzione di tuple e altri tipi, vedere decostruzione di Tuple e altri tipi.

Uguaglianza Tuple
A partire da C# 7.3, i tipi tupla supportano gli operatori == e != . Questi operatori confrontano i membri
dell'operando sinistro con i membri corrispondenti dell'operando destro che segue l'ordine degli elementi della
tupla.
(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right); // output: True
Console.WriteLine(left != right); // output: False

var t1 = (A: 5, B: 10);


var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2); // output: True
Console.WriteLine(t1 != t2); // output: False

Come illustrato nell'esempio precedente, le == != operazioni e non prendono in considerazione i nomi dei
campi di tupla.
Due tuple sono confrontabili quando sono soddisfatte entrambe le condizioni seguenti:
Entrambe le tuple hanno lo stesso numero di elementi. Ad esempio, t1 != t2 non compila se t1 e t2
hanno numeri di elementi diversi.
Per ogni posizione di tupla, gli elementi corrispondenti dagli operandi di tupla a sinistra e a destra sono
confrontabili con == gli != operatori e. Ad esempio, (1, (2, 3)) == ((1, 2), 3) non viene compilato
perché 1 non è confrontabile con (1, 2) .
Gli == != operatori e confrontano le tuple in modalità di corto circuito. In altre termini, un'operazione viene
arrestata non appena incontra una coppia di elementi non uguali o raggiunge le estremità delle tuple. Tuttavia,
prima di qualsiasi confronto, vengono valutati tutti gli elementi della tupla, come illustrato nell'esempio
seguente:

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
Console.WriteLine(s);
return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

Tuple come parametri out


In genere, si effettua il refactoring di un metodo che dispone di out parametri in un metodo che restituisce una
tupla. Tuttavia, esistono casi in cui un out parametro può essere di un tipo di tupla. Nell'esempio seguente
viene illustrato come utilizzare le tuple come out parametri:
var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
[2] = (4, 10),
[4] = (10, 20),
[6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))


{
Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20

Confronto tra tuple e System.Tuple


Le tuple C#, supportate dai System.ValueTuple tipi, sono diverse dalle tuple rappresentate dai System.Tuple tipi.
Di seguito sono riportate le differenze principali:
ValueTuple i tipi sono tipi di valore. Tuple i tipi sono tipi di riferimento.
ValueTuple i tipi sono modificabili. Tuple i tipi non sono modificabili.
I membri dati di ValueTuple tipo sono campi. I membri dati di Tuple tipo sono proprietà.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere le note sulla proposta di funzionalità seguenti:
Deduce i nomi delle Tuple (noti anche come inizializzatori di proiezione della tupla)
Supporto per == e != su tipi di tupla

Vedi anche
Informazioni di riferimento su C#
Tipi valore
Scelta tra tipi anonimi e di tupla
System.ValueTuple
Tipi di valore Nullable (riferimenti per C#)
02/11/2020 • 13 minutes to read • Edit Online

Un tipo di valore Nullable T? rappresenta tutti i valori del tipo di valore sottostante T e un valore null
aggiuntivo. È ad esempio possibile assegnare a una variabile uno dei tre valori seguenti bool? : true , false o
null . Un tipo di valore sottostante T non può essere un tipo di valore Nullable.

NOTE
C# 8,0 introduce la funzionalità dei tipi di riferimento Nullable. Per altre informazioni, vedere tipi di riferimento Nullable. I
tipi di valore nullable sono disponibili a partire da C# 2.

Qualsiasi tipo di valore Nullable è un'istanza della struttura generica System.Nullable<T> . È possibile fare
riferimento a un tipo di valore nullable con un tipo sottostante T in uno qualsiasi dei seguenti formati
intercambiabili: Nullable<T> o T? .
Si usa in genere un tipo di valore nullable quando è necessario rappresentare il valore non definito di un tipo di
valore sottostante. Una variabile booleana o, ad esempio, bool può essere solo true o false . Tuttavia, in
alcune applicazioni un valore di variabile può essere non definito o mancante. Ad esempio, un campo di
database può contenere o oppure non true false può contenere alcun valore, ovvero NULL . È possibile
utilizzare il bool? tipo in questo scenario.

Dichiarazione e assegnazione
Poiché un tipo valore è convertibile in modo implicito nel tipo di valore nullable corrispondente, è possibile
assegnare un valore a una variabile di un tipo di valore nullable come per il tipo di valore sottostante. È anche
possibile assegnare il null valore. Ad esempio:

double? pi = 3.14;
char? letter = 'a';

int m2 = 10;
int? m = m2;

bool? flag = null;

// An array of a nullable value type:


int?[] arr = new int?[10];

Il valore predefinito di un tipo di valore Nullable rappresenta null , ovvero un'istanza la cui
Nullable<T>.HasValue proprietà restituisce false .

Esame di un'istanza di un tipo di valore Nullable


A partire da C# 7,0, è possibile usare l' is operatore con un modello di tipo per esaminare un'istanza di un tipo
di valore Nullable per null e recuperare un valore di un tipo sottostante:
int? a = 42;
if (a is int valueOfA)
{
Console.WriteLine($"a is {valueOfA}");
}
else
{
Console.WriteLine("a does not have a value");
}
// Output:
// a is 42

Per esaminare e ottenere un valore di una variabile di tipo valore Nullable, è sempre possibile usare le proprietà
di sola lettura seguenti:
Nullable<T>.HasValue indica se un'istanza di un tipo di valore nullable ha un valore del tipo sottostante.
Nullable<T>.Value ottiene il valore di un tipo sottostante se HasValue è true . Se HasValue è false , la
proprietà Value genera un'eccezione InvalidOperationException.
Nell'esempio seguente viene usata la HasValue proprietà per verificare se la variabile contiene un valore prima
di visualizzarlo:

int? b = 10;
if (b.HasValue)
{
Console.WriteLine($"b is {b.Value}");
}
else
{
Console.WriteLine("b does not have a value");
}
// Output:
// b is 10

È anche possibile confrontare una variabile di un tipo di valore nullable con null invece di usare la HasValue
proprietà, come illustrato nell'esempio seguente:

int? c = 7;
if (c != null)
{
Console.WriteLine($"c is {c.Value}");
}
else
{
Console.WriteLine("c does not have a value");
}
// Output:
// c is 7

Conversione da un tipo di valore Nullable a un tipo sottostante


Se si desidera assegnare un valore di un tipo di valore Nullable a una variabile di tipo valore non nullable,
potrebbe essere necessario specificare il valore da assegnare al posto di null . Per eseguire questa operazione,
usare l' ?? operatore di Unione null (è anche possibile usare il Nullable<T>.GetValueOrDefault(T) metodo per
lo stesso scopo):
int? a = 28;
int b = a ?? -1;
Console.WriteLine($"b is {b}"); // output: b is 28

int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}"); // output: d is -1

Se si desidera utilizzare il valore predefinito del tipo di valore sottostante al posto di null , utilizzare il
Nullable<T>.GetValueOrDefault() metodo.
È anche possibile eseguire il cast esplicito di un tipo di valore Nullable a un tipo non nullable, come illustrato
nell'esempio seguente:

int? n = null;

//int m1 = n; // Doesn't compile


int n2 = (int)n; // Compiles, but throws an exception if n is null

In fase di esecuzione, se il valore di un tipo di valore Nullable è null , il cast esplicito genera un'eccezione
InvalidOperationException .
Un tipo valore non Nullable T è convertibile in modo implicito nel tipo di valore nullable corrispondente T? .

Operatori rimossi
Gli operatori unari e binari predefiniti o gli operatori di overload supportati da un tipo di valore T sono
supportati anche dal tipo di valore nullable corrispondente T? . Questi operatori, noti anche come operatori
Lift, producono null se uno o entrambi gli operandi sono null ; in caso contrario, l'operatore usa i valori
contenuti degli operandi per calcolare il risultato. Ad esempio:

int? a = 10;
int? b = null;
int? c = 10;

a++; // a is 11
a = a * c; // a is 110
a = a + b; // a is null

NOTE
Per il bool? tipo, gli operatori predefiniti & e | non seguono le regole descritte in questa sezione: il risultato di una
valutazione dell'operatore può essere diverso da null anche se uno degli operandi è null . Per altre informazioni, vedere
la sezione Operatori logici booleani nullable dell'articolo Operatori logici booleani.

Per gli operatori di confronto < ,, > <= e >= , se uno o entrambi gli operandi sono null , il risultato è
false ; in caso contrario, vengono confrontati i valori contenuti degli operandi. Non presupporre che poiché un
particolare confronto (ad esempio, <= ) restituisce false , il confronto opposto ( > ) restituisce true .
L'esempio seguente mostra che 10
non è maggiore o uguale a null
né minore di null
int? a = 10;
Console.WriteLine($"{a} >= null is {a >= null}");
Console.WriteLine($"{a} < null is {a < null}");
Console.WriteLine($"{a} == null is {a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is False

int? b = null;
int? c = null;
Console.WriteLine($"null >= null is {b >= c}");
Console.WriteLine($"null == null is {b == c}");
// Output:
// null >= null is False
// null == null is True

Per l' operatore di uguaglianza == , se entrambi gli operandi sono null , il risultato è true , se solo uno degli
operandi è null , il risultato è false ; in caso contrario, vengono confrontati i valori contenuti degli operandi.

Per l' operatore di disuguaglianza != , se entrambi gli operandi sono null , il risultato è false , se solo uno
degli operandi è null , il risultato è true ; in caso contrario, vengono confrontati i valori contenuti degli
operandi.
Se esiste una conversione definita dall'utente tra due tipi di valore, è possibile usare la stessa conversione anche
tra i tipi di valore nullable corrispondenti.

Boxing e unboxing
Un'istanza di un tipo di valore Nullable T? è boxed come indicato di seguito:
Se HasValue restituisce false , viene prodotto il riferimento Null.
Se HasValue restituisce true , il valore corrispondente del tipo di valore sottostante T è boxed, non
l'istanza di Nullable<T> .
È possibile eseguire l'unboxing di un valore boxed di un tipo di valore nel T tipo di valore nullable
corrispondente T? , come illustrato nell'esempio seguente:

int a = 41;
object aBoxed = a;
int? aNullable = (int?)aBoxed;
Console.WriteLine($"Value of aNullable: {aNullable}");

object aNullableBoxed = aNullable;


if (aNullableBoxed is int valueOfA)
{
Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
}
// Output:
// Value of aNullable: 41
// aNullableBoxed is boxed int: 41

Come identificare un tipo di valore Nullable


Nell'esempio seguente viene illustrato come determinare se un' System.Type istanza rappresenta un tipo di
valore Nullable costruito, ovvero il System.Nullable<T> tipo con un parametro di tipo specificato T :
Console.WriteLine($"int? is {(IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
Console.WriteLine($"int is {(IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");

bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;

// Output:
// int? is nullable value type
// int is non-nullable value type

Come illustrato nell'esempio, si usa l'operatore typeof per creare un' System.Type istanza.
Se si desidera determinare se un'istanza è di un tipo di valore Nullable, non utilizzare il Object.GetType metodo
per ottenere un' Type istanza di da testare con il codice precedente. Quando si chiama il Object.GetType metodo
su un'istanza di un tipo di valore Nullable, l'istanza viene sottoposta a Boxing a Object . Poiché la conversione
boxing di un'istanza non null di un tipo di valore Nullable equivale alla conversione boxing di un valore del tipo
sottostante, GetType restituisce un' Type istanza di che rappresenta il tipo sottostante di un tipo di valore
Nullable:

int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32

Inoltre, non usare l'operatore is per determinare se un'istanza è un tipo di valore Nullable. Come illustrato
nell'esempio seguente, non è possibile distinguere i tipi di un'istanza del tipo di valore nullable e l'istanza del
tipo sottostante con l' is operatore:

int? a = 14;
if (a is int)
{
Console.WriteLine("int? instance is compatible with int");
}

int b = 17;
if (b is int?)
{
Console.WriteLine("int instance is compatible with int?");
}
// Output:
// int? instance is compatible with int
// int instance is compatible with int?

È possibile utilizzare il codice presentato nell'esempio seguente per determinare se un'istanza è un tipo di valore
Nullable:

int? a = 14;
Console.WriteLine(IsOfNullableType(a)); // output: True

int b = 17;
Console.WriteLine(IsOfNullableType(b)); // output: False

bool IsOfNullableType<T>(T o)
{
var type = typeof(T);
return Nullable.GetUnderlyingType(type) != null;
}
NOTE
I metodi descritti in questa sezione non sono applicabili in caso di tipi di riferimento Nullable.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Tipi nullable
Operatori rimossi
Conversioni implicite Nullable
Conversioni esplicite Nullable
Operatori di conversione accurati

Vedi anche
Informazioni di riferimento su C#
Cosa significa esattamente "elevato"?
System.Nullable<T>
System.Nullable
Nullable.GetUnderlyingType
Tipi riferimento nullable
Tipi di riferimento (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

Esistono due generi di tipo in C#: tipi di riferimento e tipi di valore. Le variabili dei tipi di riferimento archiviano i
riferimenti ai relativi dati (oggetti), mentre le variabili dei tipi di valore contengono direttamente i dati. Con i tipi
di riferimento, due variabili possono fare riferimento allo stesso oggetto. Di conseguenza le operazioni su una
variabile possono influire sull'oggetto a cui fa riferimento l'altra variabile. Con i tipi valore, ogni variabile ha una
propria copia dei dati e non è possibile che le operazioni su una variabile influiscano sull'altra (tranne nel caso
delle variabili dei parametri in, ref e out, vedere Modificatore del parametro in, ref e out).
Le seguenti parole chiave vengono utilizzate per dichiarare i tipi di riferimento:
class
interface
delegate
record
In c# sono disponibili i seguenti tipi di riferimento predefiniti:
dinamico
object
string

Vedere anche
Riferimenti per C#
Parole chiave di C#
Tipi puntatore
Tipi valore
Tipi di riferimento predefiniti (riferimenti per C#)
02/11/2020 • 12 minutes to read • Edit Online

C# ha un numero di tipi di riferimento predefiniti. Hanno parole chiave o operatori che sono sinonimi per un
tipo nella libreria .NET.

Tipo di oggetto
Il tipo object è un alias per System.Object in .NET. Nel sistema di tipi unificato di C#, tutti i tipi, predefiniti e
definiti dall'utente, i tipi riferimento e i tipi valore ereditano direttamente o indirettamente da System.Object.
Alle variabili di tipo object è possibile assegnare valori di qualsiasi tipo. Qualsiasi variabile object può essere
assegnata al suo valore predefinito usando il valore letterale null . Una variabile di un tipo di valore convertita
in oggetto viene definita boxed. Quando una variabile di tipo object viene convertita in un tipo valore, viene
definito unboxed. Per altre informazioni, vedere Boxing e unboxing.

Tipo di stringa
Il tipo string rappresenta una sequenza di zero o più caratteri Unicode. string è un alias per System.String in
.NET.
Sebbene string sia un tipo riferimento, gli operatori di uguaglianza == e != vengono definiti per confrontare
i valori degli oggetti string e non dei riferimenti. In questo modo il test di uguaglianza delle stringhe è più
intuitivo. Ad esempio:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

Viene visualizzato "True" e quindi "False" perché il contenuto delle stringhe è equivalente, ma a e b non fanno
riferimento alla stessa istanza della stringa.
L'operatore + concatena le stringhe:

string a = "good " + "morning";

Questo crea un oggetto stringa contenente "good morning".


Le stringhe sono immutabili: non è possibile modificare il contenuto di un oggetto stringa dopo la creazione
dell'oggetto, sebbene la sintassi sembri indicare che è possibile apportare modifiche. Ad esempio, quando si
scrive il codice, il compilatore crea un nuovo oggetto stringa per archiviare la nuova sequenza di caratteri e il
nuovo oggetto viene assegnato a b . La memoria allocata per b (quando conteneva la stringa "h") è quindi
idonea per la garbage collection.

string b = "h";
b += "ello";

L' [] operatore può essere usato per l'accesso di sola lettura a singoli caratteri di una stringa. I valori di indice
validi iniziano da 0 e devono essere minori della lunghezza della stringa:
string str = "test";
char x = str[2]; // x = 's';

In modo analogo, l' [] operatore può essere usato anche per scorrere ogni carattere in una stringa:

string str = "test";

for (int i = 0; i < str.Length; i++)


{
Console.Write(str[i] + " ");
}
// Output: t e s t

I valori letterali della stringa sono di tipo string e possono essere scritti in due formati, tra virgolette e
delimitati da @ . I valori letterali della stringa tra virgolette sono racchiusi in virgolette doppie ("):

"good morning" // a string literal

I valori letterali della stringa possono contenere qualsiasi carattere letterale. Sono incluse le sequenze di escape.
L'esempio seguente usa una sequenza di escape \\ per la barra rovesciata, \u0066 per la lettera f e \n per la
nuova riga.

string a = "\\\u0066\n F";


Console.WriteLine(a);
// Output:
// \f
// F

NOTE
Il codice di escape \udddd (dove dddd è un numero a quattro cifre) rappresenta il carattere Unicode U+ dddd .
Vengono riconosciuti anche i codici di escape Unicode a otto cifre: \Udddddddd .

I valori letterali della stringa verbatim iniziano con @ e sono anche racchiusi tra virgolette doppie. Ad esempio:

@"good morning" // a string literal

Il vantaggio delle stringhe verbatim è che le sequenze di escape non sono elaborate, quindi rendono più
semplice scrivere ad esempio un nome file di Windows completo:

@"c:\Docs\Source\a.txt" // rather than "c:\\Docs\\Source\\a.txt"

Per includere le virgolette doppie in una stringa @-quoted, duplicarla:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

Tipo di delegato
La dichiarazione di un tipo delegato è simile alla firma di un metodo. Ha un valore restituito e una serie di
parametri di qualsiasi tipo:
public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

In .NET i tipi System.Action e System.Func offrono definizioni generiche per molti delegati comuni.
Probabilmente non è necessario definire nuovi tipi di delegato. È possibile eventualmente creare istanze dei tipi
generici disponibili.
delegate è un tipo riferimento che può essere usato per incapsulare un metodo denominato o anonimo. I
delegati sono simili ai puntatori a funzioni in C++, ma sono indipendenti dai tipi e protetti. Per le applicazioni dei
delegati, vedere Delegati e Delegati generici. I delegati sono la base degli eventi. È possibile creare un'istanza di
un delegato associandolo a un metodo denominato o anonimo.
È necessario creare un'istanza del delegato con un metodo o un'espressione lambda con tipo restituito
compatibile e parametri di input. Per altre informazioni sul grado di varianza consentito nella firma del metodo,
vedere Varianza nei delegati. Per l'uso con i metodi anonimi, è necessario dichiarare insieme il delegato e il
codice da associare ad esso.

Tipo dinamico
Il tipo dynamic indica l'uso della variabile e dei riferimenti ai relativi membri per escludere il controllo del tipo in
fase di compilazione. Queste operazioni vengono risolte in fase di esecuzione. Il tipo dynamic semplifica
l'accesso alle API COM, ad esempio le API di automazione di Office, alle API dinamiche, ad esempio le librerie di
IronPython, e al modello DOM (Document Object Model) HTML.
Il tipo dynamic si comporta come tipo object nella maggior parte dei casi. In particolare, qualsiasi espressione
non null può essere convertita nel tipo dynamic . Il tipo dynamic si comporta diversamente da object nelle
operazioni che contengono espressioni di tipo dynamic che non vengono risolte o il tipo non viene verificato dal
compilatore. Il compilatore raggruppa le informazioni sull'operazione e tali informazioni successivamente
vengono usate per valutare l'operazione in fase di esecuzione. Come parte del processo, le variabili di tipo
dynamic vengono compilate in variabili di tipo object . Di conseguenza, il tipo dynamic esiste solo in fase di
compilazione, non in fase di esecuzione.
Nell'esempio seguente vengono messe a confronto una variabile di tipo dynamic e una variabile di tipo object .
Per verificare il tipo di ogni variabile in fase di compilazione, posizionare il puntatore del mouse su dyn o obj
nelle istruzioni WriteLine . Copiare il codice seguente in un editor in cui IntelliSense è disponibile. IntelliSense
visualizza dynamic per dyn e object per obj .

class Program
{
static void Main(string[] args)
{
dynamic dyn = 1;
object obj = 1;

// Rest the mouse pointer over dyn and obj to see their
// types at compile time.
System.Console.WriteLine(dyn.GetType());
System.Console.WriteLine(obj.GetType());
}
}

Le istruzioni WriteLine visualizzano i tipi in fase di esecuzione di dyn e obj . A questo punto, entrambe hanno
lo stesso tipo, un numero intero. Viene prodotto l'output seguente:

System.Int32
System.Int32

Per vedere la differenza tra dyn e obj in fase di compilazione, aggiungere le due righe seguenti tra le
dichiarazioni e le istruzioni WriteLine dell'esempio precedente.

dyn = dyn + 3;
obj = obj + 3;

Viene segnalato un errore del compilatore per il tentativo di aggiunta di un numero intero e di un oggetto
nell'espressione obj + 3 . Tuttavia non vengono segnalati errori per dyn + 3 . L'espressione che contiene dyn
non viene controllata in fase di compilazione perché il tipo di dyn è dynamic .
Nell'esempio seguente viene usato dynamic in diverse dichiarazioni. Il metodo Main confronta anche il
controllo dei tipi in fase di compilazione con il controllo dei tipi in fase di esecuzione.

using System;

namespace DynamicExamples
{
class Program
{
static void Main(string[] args)
{
ExampleClass ec = new ExampleClass();
Console.WriteLine(ec.exampleMethod(10));
Console.WriteLine(ec.exampleMethod("value"));

// The following line causes a compiler error because exampleMethod


// takes only one argument.
//Console.WriteLine(ec.exampleMethod(10, 4));

dynamic dynamic_ec = new ExampleClass();


Console.WriteLine(dynamic_ec.exampleMethod(10));

// Because dynamic_ec is dynamic, the following call to exampleMethod


// with two arguments does not produce an error at compile time.
// However, it does cause a run-time error.
//Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
}
}

class ExampleClass
{
static dynamic field;
dynamic prop { get; set; }

public dynamic exampleMethod(dynamic d)


{
dynamic local = "Local variable";
int two = 2;

if (d is int)
{
return local;
}
else
{
return two;
}
}
}
}
// Results:
// Local variable
// 2
// Local variable

Vedere anche
Riferimenti per C#
Parole chiave di C#
Eventi
Utilizzo del tipo dinamico
Procedure consigliate per l'utilizzo di stringhe
Operazioni di base sulle stringhe
Creazione di nuove stringhe
Operatori di cast e di test del tipo
Come eseguire il cast sicuro usando i criteri di ricerca e gli operatori As e is
Procedura dettagliata: Creazione e utilizzo di oggetti dinamici
System.Object
System.String
System.Dynamic.DynamicObject
class (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Le classi vengono dichiarate usando la parola chiave class , come illustrato nell'esempio seguente:

class TestClass
{
// Methods, properties, fields, events, delegates
// and nested classes go here.
}

Commenti
In C# è consentita solo l'eredità singola. In altre parole, una classe può ereditare l'implementazione solo da una
classe di base singola. Una classe può tuttavia implementare più di un'interfaccia. La tabella seguente illustra
esempi di ereditarietà di classi e di implementazione di interfacce:

EREDITA RIETÀ ESEM P IO

Nessuno class ClassA { }

Single class DerivedClass : BaseClass { }

Nessuna, implementa due interfacce class ImplClass : IFace1, IFace2 { }

Singola, implementa una sola interfaccia class ImplDerivedClass : BaseClass, IFace1 { }

Una classe dichiarata direttamente all'interno di uno spazio dei nomi, non annidata all'interno di altre classi, può
essere public o internal. Le classi sono internal per impostazione predefinita.
I membri di classe, incluse le classi annidate, possono essere public, protected internal, protected, internal,
private o private protected. I membri sono private per impostazione predefinita.
Per altre informazioni, vedere Modificatori di accesso.
È possibile dichiarare classi generiche che hanno parametri di tipo. Per altre informazioni, vedere Generic
Classes (Classi generiche).
Una classe può contenere dichiarazioni dei membri seguenti:
Costruttori
Costanti
Fields
Finalizzatori
Metodi
Proprietà
Indicizzatori
Operatori
Eventi
Delegati
Classi
Interfacce
Tipi di struttura
Tipi di enumerazione

Esempio
L'esempio seguente illustra la dichiarazione di campi di classe, costruttori e metodi. Illustra anche la creazione di
istanze di oggetti e la stampa dei dati di istanza. In questo esempio vengono dichiarate due classi. La prima
classe Child , contiene due campi privati ( name e age ), due costruttori pubblici e un metodo pubblico. La
seconda classe, StringTest , viene usata per contenere Main .

class Child
{
private int age;
private string name;

// Default constructor:
public Child()
{
name = "N/A";
}

// Constructor:
public Child(string name, int age)
{
this.name = name;
this.age = age;
}

// Printing method:
public void PrintChild()
{
Console.WriteLine("{0}, {1} years old.", name, age);
}
}

class StringTest
{
static void Main()
{
// Create objects by using the new operator:
Child child1 = new Child("Craig", 11);
Child child2 = new Child("Sally", 10);

// Create an object using the default constructor:


Child child3 = new Child();

// Display results:
Console.Write("Child #1: ");
child1.PrintChild();
Console.Write("Child #2: ");
child2.PrintChild();
Console.Write("Child #3: ");
child3.PrintChild();
}
}
/* Output:
Child #1: Craig, 11 years old.
Child #2: Sally, 10 years old.
Child #3: N/A, 0 years old.
*/

Commenti
Si noti che, nell'esempio precedente, i campi privati ( name e age ) sono accessibili solo tramite i metodi pubblici
della classe Child . Ad esempio, non è possibile stampare il nome del figlio, dal metodo Main , usando
un'istruzione simile alla seguente:

Console.Write(child1.name); // Error

L'accesso ai membri private di Child da Main sarebbe possibile solo se Main fosse un membro della classe.
I tipi dichiarati all'interno di una classe senza un modificatore di accesso vengono impostati automaticamente a
private ; in tal modo i membri dei dati in questo esempio sarebbero sempre private , anche se la parola chiave
venisse rimossa.
Infine, si noti che per l'oggetto creato usando il costruttore senza parametri ( child3 ), il campo age è stato
inizializzato su zero per impostazione predefinita.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Tipi di riferimento
interface (Riferimenti per C#)
28/01/2021 • 5 minutes to read • Edit Online

Un'interfaccia definisce un contratto. Any class o struct che implementa il contratto deve fornire
un'implementazione dei membri definiti nell'interfaccia. A partire da C# 8,0, un'interfaccia può definire
un'implementazione predefinita per i membri. Può anche definire static i membri per fornire una singola
implementazione per la funzionalità comune.
Nell'esempio seguente, la classe ImplementationClass deve implementare un metodo denominato
SampleMethod che è privo di parametri e restituisce void .

Per altre informazioni e altri esempi, vedere Interfacce.

Esempio
interface ISampleInterface
{
void SampleMethod();
}

class ImplementationClass : ISampleInterface


{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}

static void Main()


{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();

// Call the member.


obj.SampleMethod();
}
}

Un'interfaccia può essere un membro di uno spazio dei nomi o di una classe. Una dichiarazione di interfaccia
può contenere dichiarazioni (firme senza alcuna implementazione) dei membri seguenti:
Metodi
Proprietà
Indicizzatori
Eventi
Queste dichiarazioni di membri precedenti non contengono in genere un corpo. A partire da C# 8,0, un membro
di interfaccia può dichiarare un corpo. Si tratta di un' implementazione predefinita. I membri con corpi
consentono all'interfaccia di fornire un'implementazione "predefinita" per le classi e gli struct che non
forniscono un'implementazione di override. Inoltre, a partire da C# 8,0, un'interfaccia può includere:
Costanti
Operatori
Costruttore statico.
Tipi annidati
Campi, metodi, proprietà, indicizzatori ed eventi statici
Dichiarazioni di membri che usano la sintassi di implementazione esplicita dell'interfaccia.
Modificatori di accesso espliciti (l'accesso predefinito è public ).
Le interfacce non possono contenere lo stato dell'istanza. Mentre i campi statici sono ora consentiti, i campi di
istanza non sono consentiti nelle interfacce. Le proprietà automatiche dell'istanza non sono supportate nelle
interfacce perché dichiarano in modo implicito un campo nascosto. Questa regola ha un effetto lieve sulle
dichiarazioni di proprietà. In una dichiarazione di interfaccia, il codice seguente non dichiara una proprietà
implementata automaticamente come in un oggetto class o struct . Dichiara invece una proprietà che non
dispone di un'implementazione predefinita, ma che deve essere implementata in qualsiasi tipo che implementi
l'interfaccia:

public interface INamed


{
public string Name {get; set;}
}

Un'interfaccia può ereditare da una o più interfacce di base. Quando un'interfaccia esegue l'override di un
metodo implementato in un'interfaccia di base, deve usare la sintassi di implementazione esplicita
dell'interfaccia .
Quando un elenco di tipi di base contiene interfacce e una classe di base, la classe di base deve precedere le
interfacce.
Una classe che implementa un'interfaccia può implementare in modo esplicito i membri di tale interfaccia. Non
è possibile accedere a un membro implementato in modo esplicito tramite un'istanza di classe, ma solo tramite
un'istanza dell'interfaccia. Inoltre, è possibile accedere ai membri di interfaccia predefiniti solo tramite un'istanza
dell'interfaccia.
Per altre informazioni sull'implementazione esplicita dell'interfaccia, vedere implementazione esplicita
dell'interfaccia.

Esempio
Nell'esempio seguente viene illustrata un'implementazione dell'interfaccia. In questo esempio l'interfaccia
contiene la dichiarazione di proprietà e la classe contiene l'implementazione. Qualsiasi istanza di una classe che
implementa IPoint presenta proprietà x e y di tipo Integer.
interface IPoint
{
// Property signatures:
int X
{
get;
set;
}

int Y
{
get;
set;
}

double Distance
{
get;
}
}

class Point : IPoint


{
// Constructor:
public Point(int x, int y)
{
X = x;
Y = y;
}

// Property implementation:
public int X { get; set; }

public int Y { get; set; }

// Property implementation
public double Distance =>
Math.Sqrt(X * X + Y * Y);

class MainClass
{
static void PrintPoint(IPoint p)
{
Console.WriteLine("x={0}, y={1}", p.X, p.Y);
}

static void Main()


{
IPoint p = new Point(2, 3);
Console.Write("My Point: ");
PrintPoint(p);
}
}
// Output: My Point: x=2, y=3

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione interfacce delle specifiche del linguaggio C# e la specifica della
funzionalità per i membri di interfaccia predefiniti-C# 8,0

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Tipi di riferimento
Interfacce
Utilizzo delle proprietà
Utilizzo degli indicizzatori
Tipi di riferimento Nullable (riferimenti per C#)
28/01/2021 • 9 minutes to read • Edit Online

NOTE
Questo articolo illustra i tipi di riferimento Nullable. È anche possibile dichiarare i tipi di valore Nullable.

I tipi di riferimento nullable sono disponibili a partire da C# 8,0, nel codice che ha acconsentito a un contesto in
grado di riconoscere i valori null. I tipi di riferimento Nullable, gli avvisi di analisi statica null e l' operatore con
indulgenza null sono funzionalità del linguaggio facoltative. Tutti sono spenti per impostazione predefinita. Un
contesto Nullable è controllato a livello di progetto usando le impostazioni di compilazione o nel codice che usa
pragma.
In un contesto compatibile con Nullable:
Una variabile di un tipo di riferimento T deve essere inizializzata con un valore diverso da null e non può
mai essere assegnato un valore che può essere null .
Una variabile di un tipo riferimento T? può essere inizializzata con null o assegnato null , ma è
necessario eseguire il controllo null prima di dereferenziare.
Una variabile m di tipo T? viene considerata non null quando si applica l'operatore con indulgenza null,
come in m! .
Le distinzioni tra un tipo di riferimento non Nullable T e un tipo di riferimento Nullable T? vengono applicate
dall'interpretazione del compilatore delle regole precedenti. Una variabile di tipo T e una variabile di tipo T?
sono rappresentate dallo stesso tipo .NET. Nell'esempio seguente viene dichiarata una stringa che non ammette
i valori null e una stringa Nullable, quindi viene utilizzato l'operatore che perdona i valori null per assegnare un
valore a una stringa non nullable:

string notNull = "Hello";


string? nullable = default;
notNull = nullable!; // null forgiveness

Le variabili notNull e nullable sono entrambe rappresentate dal String tipo. Poiché i tipi non nullable e
nullable sono entrambi archiviati come lo stesso tipo, esistono diverse posizioni in cui l'utilizzo di un tipo di
riferimento nullable non è consentito. In generale, non è possibile usare un tipo di riferimento nullable come
classe base o interfaccia implementata. Non è possibile usare un tipo di riferimento nullable in nessuna
creazione di oggetti o espressione di test del tipo. Un tipo di riferimento nullable non può essere il tipo di
un'espressione di accesso ai membri. Gli esempi seguenti illustrano questi costrutti:
public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed


var maybeObject = new object?(); // Not allowed
try
{
if (thing is string? nullableString) // not allowed
Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
Console.WriteLine("error");
}

Riferimenti nullable e analisi statica


Negli esempi della sezione precedente viene illustrata la natura dei tipi di riferimento Nullable. I tipi di
riferimento nullable non sono nuovi tipi di classe, ma piuttosto annotazioni sui tipi di riferimento esistenti. Il
compilatore usa tali annotazioni per individuare potenziali errori di riferimento null nel codice. Non esiste alcuna
differenza di runtime tra un tipo di riferimento non nullable e un tipo di riferimento Nullable. Il compilatore non
aggiunge alcun controllo di runtime per i tipi di riferimento che non ammettono valori null. I vantaggi si trovano
nell'analisi in fase di compilazione. Il compilatore genera avvisi che consentono di individuare e correggere gli
errori null potenziali nel codice. Si dichiara lo scopo e il compilatore avvisa l'utente quando il codice viola tale
finalità.
In un contesto abilitato Nullable, il compilatore esegue l'analisi statica su variabili di qualsiasi tipo di riferimento,
Nullable e non nullable. Il compilatore tiene traccia dello stato null di ogni variabile di riferimento come not null
o Maybe null. Lo stato predefinito di un riferimento non nullable non è null. Lo stato predefinito di un
riferimento Nullable è forse null.
I tipi di riferimento che non ammettono valori null devono essere sempre sicuri per la dereferenziazione perché
lo stato null non è null. Per applicare questa regola, il compilatore genera avvisi se un tipo di riferimento non
nullable non è inizializzato su un valore non null. Le variabili locali devono essere assegnate dove sono
dichiarate. Ogni costruttore deve assegnare ogni campo, nel corpo, un costruttore chiamato, o usando un
inizializzatore di campo. Il compilatore genera avvisi se un riferimento non nullable viene assegnato a un
riferimento il cui stato è forse null. Tuttavia, poiché un riferimento che non ammette i valori null non è null, non
viene emesso alcun avviso quando si fa riferimento a tali variabili.
I tipi di riferimento nullable possono essere inizializzati o assegnati a null . Pertanto, l'analisi statica deve
determinare che una variabile non è null prima che venga dereferenziata. Se un riferimento Nullable è
determinato come null, non può essere assegnato a una variabile di riferimento che non ammette i valori null.
La classe seguente mostra esempi di questi avvisi:
public class ProductDescription
{
private string shortDescription;
private string? detailedDescription;

public ProductDescription() // Warning! short description not initialized.


{
}

public ProductDescription(string productDescription) =>


this.shortDescription = productDescription;

public void SetDescriptions(string productDescription, string? details=null)


{
shortDescription = productDescription;
detailedDescription = details;
}

public string GetDescription()


{
if (detailedDescription.Length == 0) // Warning! dereference possible null
{
return shortDescription;
}
else
{
return $"{shortDescription}\n{detailedDescription}";
}
}

public string FullDescription()


{
if (detailedDescription == null)
{
return shortDescription;
}
else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
{
return $"{shortDescription}\n{detailedDescription}";
}
return shortDescription;
}
}

Il frammento di codice seguente mostra dove il compilatore emette avvisi quando si usa questa classe:

string shortDescription = default; // Warning! non-nullable set to null;


var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription
maybe null.

string description = "widget";


var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

Gli esempi precedenti illustrano l'analisi statica del compilatore per determinare lo stato null delle variabili di
riferimento. Il compilatore applica le regole del linguaggio per i controlli null e le assegnazioni per informare
l'analisi. Il compilatore non può fare supposizioni sulla semantica di metodi o proprietà. Se si chiamano metodi
che eseguono controlli null, il compilatore non sa che questi metodi influiscono sullo stato null di una variabile.
Sono disponibili diversi attributi che è possibile aggiungere alle API per informare il compilatore sulla semantica
di argomenti e valori restituiti. Questi attributi sono stati applicati a molte API comuni nelle librerie .NET Core.
Ad esempio, IsNullOrEmpty è stato aggiornato e il compilatore interpreta correttamente tale metodo come un
controllo null. Per ulteriori informazioni sugli attributi applicabili all'analisi statica dello stato null, vedere
l'articolo sugli attributi Nullable.

Impostazione del contesto Nullable


Esistono due modi per controllare il contesto Nullable. A livello di progetto, è possibile aggiungere l'
<Nullable>enable</Nullable> impostazione del progetto. In un singolo file di origine C# è possibile aggiungere il
#nullable enable pragma per abilitare il contesto Nullable. Vedere l'articolo sull' impostazione di una strategia
Nullable.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere le seguenti proposte per la specifica del linguaggio C#:
Tipi riferimento nullable
Bozza Specifica tipi di riferimento Nullable

Vedere anche
Informazioni di riferimento su C#
Tipi valore nullable
void (riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Usare void come tipo restituito di un Metodo o una funzione localeper specificare che il metodo non restituisce
un valore.

public static void Display(IEnumerable<int> numbers)


{
if (numbers is null)
{
return;
}

Console.WriteLine(string.Join(" ", numbers));


}

È anche possibile usare void come tipo referente per dichiarare un puntatore a un tipo sconosciuto. Per altre
informazioni, vedere Tipi di puntatori.
Non è possibile usare void come tipo di una variabile.

Vedi anche
Informazioni di riferimento su C#
System.Void
var (riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

A partire da C# 3, le variabili dichiarate nell'ambito del metodo possono avere un "tipo" implicito var . Una
variabile locale tipizzata in modo implicito è fortemente tipizzata come se si fosse dichiarato il tipo stesso, ma è
il compilatore a determinare il tipo. Le due dichiarazioni seguenti di i sono equivalenti dal punto di vista
funzionale:

var i = 10; // Implicitly typed.


int i = 10; // Explicitly typed.

IMPORTANT
Quando var viene usato con i tipi di riferimento Nullable abilitati, implica sempre un tipo di riferimento Nullable anche
se il tipo di espressione non ammette i valori null.

Un uso comune della var parola chiave è con espressioni di chiamata del costruttore. L'uso di var consente di
non ripetere un nome di tipo in una dichiarazione di variabile e un'istanza dell'oggetto, come illustrato
nell'esempio seguente:

var xs = new List<int>();

A partire da C# 9,0, è possibile usare un' new espressione tipizzata di destinazione come alternativa:

List<int> xs = new();
List<int>? ys = new();

Esempio
L'esempio seguente illustra due espressioni di query. Nella prima espressione l'uso di var è consentito, ma non
necessario, perché il tipo del risultato della query può essere dichiarato in modo esplicito come
IEnumerable<string> . Nella seconda espressione, tuttavia, var consente che il risultato sia una raccolta di tipi
anonimi e il nome di tale tipo non è accessibile tranne che al compilatore stesso. L'uso di var elimina la
necessità di creare una nuova classe per il risultato. Si noti che nel secondo esempio anche la variabile di
iterazione foreach``item deve essere tipizzata in modo implicito.
// Example #1: var is optional when
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
where word[0] == 'g'
select word;

// Because each element in the sequence is a string,


// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
Console.WriteLine(s);
}

// Example #2: var is required because


// the select clause specifies an anonymous type
var custQuery = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };

// var must be used because each item


// in the sequence is an anonymous type
foreach (var item in custQuery)
{
Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}

Vedi anche
Informazioni di riferimento su C#
Variabili locali tipizzate in modo implicito
Relazioni tra i tipi nelle operazioni di query LINQ
Tipi incorporati (riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Nella tabella seguente sono elencati i tipi di valore predefiniti C#:

PA RO L A C H IAVE DI T IP O C # T IP O . N ET

bool System.Boolean

byte System.Byte

sbyte System.SByte

char System.Char

decimal System.Decimal

double System.Double

float System.Single

int System.Int32

uint System.UInt32

long System.Int64

ulong System.UInt64

short System.Int16

ushort System.UInt16

Nella tabella seguente sono elencati i tipi di riferimento incorporati in C#:

PA RO L A C H IAVE DI T IP O C # T IP O . N ET

object System.Object

string System.String

dynamic System.Object

Nelle tabelle precedenti ogni parola chiave di tipo C# della colonna sinistra è un alias per il tipo .NET
corrispondente. Sono intercambiabili. Ad esempio, le dichiarazioni seguenti dichiarano variabili dello stesso tipo:
int a = 123;
System.Int32 b = 123;

La void parola chiave rappresenta l'assenza di un tipo. Viene usato come tipo restituito di un metodo che non
restituisce un valore.

Vedere anche
Informazioni di riferimento su C#
Valori predefiniti dei tipi C#
Tipi non gestiti (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Un tipo è un tipo non gestito se è uno dei tipi seguenti:


sbyte, byte , short , ushort , int , uint , long , ulong , char , float , double , decimal o bool
Qualsiasi tipo enum
Qualsiasi tipo puntatore
Qualsiasi tipo struct definito dall'utente che contiene campi solo di tipi non gestiti e, in C# 7,3 e versioni
precedenti, non è un tipo costruito (un tipo che include almeno un argomento di tipo)
A partire da C# 7,3, è possibile usare il unmanaged vincolo per specificare che un parametro di tipo è un tipo non
gestito che non ammette i valori null.
A partire da C# 8,0, viene anche gestito un tipo struct costruito che contiene campi di tipi non gestiti, come
illustrato nell'esempio seguente:

using System;

public struct Coords<T>


{
public T X;
public T Y;
}

public class UnmanagedTypes


{
public static void Main()
{
DisplaySize<Coords<int>>();
DisplaySize<Coords<double>>();
}

private unsafe static void DisplaySize<T>() where T : unmanaged


{
Console.WriteLine($"{typeof(T)} is unmanaged and its size is {sizeof(T)} bytes");
}
}
// Output:
// Coords`1[System.Int32] is unmanaged and its size is 8 bytes
// Coords`1[System.Double] is unmanaged and its size is 16 bytes

Uno struct generico può essere l'origine dei tipi costruiti non gestiti e non gestiti. Nell'esempio precedente viene
definito uno struct generico Coords<T> e vengono presentati gli esempi di tipi costruiti non gestiti. L'esempio di
non è un tipo non gestito è Coords<object> . Non è gestita perché contiene i campi del object tipo, che non è
gestito. Se si desidera che tutti i tipi costruiti siano tipi non gestiti, utilizzare il unmanaged vincolo nella
definizione di uno struct generico:

public struct Coords<T> where T : unmanaged


{
public T X;
public T Y;
}
Specifiche del linguaggio C#
Per altre informazioni, vedere la sezione Tipi puntatore nella specifica del linguaggio C#.

Vedi anche
Informazioni di riferimento su C#
Tipi puntatore
Tipi correlati alla memoria e agli intervalli
Operatore sizeof
stackalloc
Valori predefiniti dei tipi C .
26/03/2020 • 2 minutes to read • Edit Online

La tabella seguente mostra i valori predefiniti dei tipi C#:

TYPE VA LO RE P REDEF IN ITO

Qualsiasi tipo riferimento null

Qualsiasi tipo numerico integrale incorporato 0 (zero)

Qualsiasi tipo numerico a virgola mobile incorporato 0 (zero)

bool false

char '\0' (U+0000)

Enum Valore prodotto dall'espressione (E)0 , dove E è


l'identificatore di enumerazione.

struct Valore prodotto impostando tutti i campi dei tipi valore sui
rispettivi valori predefiniti e tutti i campi dei tipi riferimento
su null .

Qualsiasi tipo valore nullable Un'istanza per la quale la proprietà HasValue è false e la
proprietà Value non è definita. Tale valore predefinito è noto
anche come valore null di un tipo di valore nullable.

Utilizzare default l'operatore per produrre il valore predefinito di un tipo, come illustrato nell'esempio
seguente:

int a = default(int);

A partire dalla versione 7.1 di C, è possibile usare il default valore letterale per inizializzare una variabile con il
valore predefinito del relativo tipo:

int a = default;

Per un tipo valore, anche il costruttore senza parametri implicito produce il valore predefinito del tipo, come
mostrato nell'esempio seguente:

var n = new System.Numerics.Complex();


Console.WriteLine(n); // output: (0, 0)

In fase di System.Type esecuzione, se l'istanza rappresenta Activator.CreateInstance(Type) un tipo di valore, è


possibile utilizzare il metodo per richiamare il costruttore senza parametri per ottenere il valore predefinito del
tipo.
Specifiche del linguaggio C#
Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Valori predefiniti
Costruttori predefiniti

Vedere anche
Informazioni di riferimento su C#
Costruttori
Parole chiave di C#
28/01/2021 • 2 minutes to read • Edit Online

Le parole chiave sono identificatori riservati predefiniti che hanno significati particolari per il compilatore. Non
possono essere usati come identificatori nel programma a meno che non includano il prefisso @ . Ad esempio,
@if è un identificatore valido mentre if non lo è, perché if è una parola chiave.

Nella prima tabella di questo argomento vengono elencate le parole chiave che sono identificatori riservati in
qualsiasi parte di un programma C#. Nella seconda tabella di questo argomento vengono elencate le parole
chiave contestuali in C#. Le parole chiave contestuali hanno un significato speciale solo in un contesto limitato
del programma e possono essere usate come identificatori al di fuori di tale contesto. In genere, le nuove parole
chiave aggiunte al linguaggio C# vengono aggiunte come parole chiave contestuali per evitare problemi con i
programmi scritti nelle versioni precedenti.

astratta as base bool

break byte caso generazione

char selezionata class const

continuare decimal default delegate

do double else enum

event esplicita extern false

Infine fissa float for

foreach goto if implicita

in int interface interno

è blocco long namespace

Nuovo null object operator

out override params privata

protected pubblico ReadOnly ref

ritorno sbyte sealed short

sizeof stackalloc static string

struct switch this generare

true provare typeof uint


ULONG deselezionata unsafe ushort

using virtuale void volatile

mentre

Parole chiave contestuali


Una parola chiave contestuale viene usata per conferire un significato particolare nel codice, ma non è una
parola riservata in C#. Alcune parole chiave contestuali, ad esempio partial e where , hanno significati speciali
in due o più contesti.

add alias ascending

async await by

descending dinamico equals

from get globale

utenti into join

consentono nameof notnull

on OrderBy Partial (tipo)

Partial (metodo) remove select

set unmanaged (vincolo di tipo generico) value

var when (condizione di filtro) where (vincolo di tipo generico)

where (clausola query) con yield

Vedere anche
Informazioni di riferimento su C#
Modificatori di accesso (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

I modificatori di accesso sono parole chiave usate per specificare l'accessibilità dichiarata di un membro o di un
tipo. In questa sezione vengono presentati i quattro modificatori di accesso:
public
protected
internal
private

È possibile specificare i sei livelli di accessibilità seguenti usando i modificatori di accesso:


public : L'accesso non è limitato.
protected : L'accesso è limitato alla classe o ai tipi derivati dalla classe che lo contiene.
internal : L'accesso è limitato all'assembly corrente.
protected internal : L'accesso è limitato all'assembly corrente o ai tipi derivati dalla classe che lo
contiene.
private : L'accesso è limitato al tipo che lo contiene.
private protected : L'accesso è limitato alla classe o ai tipi derivati dalla classe che li contiene all'interno
dell'assembly corrente.
In questa sezione vengono presentati anche gli argomenti seguenti:
Livelli di accessibilità: uso dei quattro modificatori di accesso per dichiarare sei livelli di accessibilità.
Dominio di accessibilità: specifica in quali sezioni del programma è possibile fare riferimento a un
membro.
Restrizioni relative all'uso dei livelli di accessibilità: riepilogo delle restrizioni per l'uso dei livelli di
accessibilità dichiarati.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Parole chiave di accesso
Modificatori
Livelli di accessibilità (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Usare i modificatori di accesso public , protected , internal o private per specificare uno dei livelli seguenti
di accessibilità dichiarata per i membri.

A C C ESSIB IL ITÀ DIC H IA RATA SIGN IF IC ATO

public L'accesso non è limitato.

protected L'accesso è limitato alla classe o ai tipi derivati dalla classe


che li contiene.

internal L'accesso è limitato all'assembly corrente.

protected internal L'accesso è limitato all'assembly corrente o ai tipi derivati


dalla classe che li contiene.

private L'accesso è limitato al tipo contenitore.

private protected L'accesso è limitato alla classe o ai tipi derivati dalla classe
che li contiene all'interno dell'assembly corrente. Disponibile
da C# 7.2.

Per un membro o un tipo è consentito solo un modificatore di accesso, tranne quando si usano le combinazioni
protected internal o private protected .

I modificatori di accesso non sono consentiti negli spazi dei nomi. Gli spazi dei nomi non hanno restrizioni di
accesso.
A seconda del contesto in cui si verifica una dichiarazione di membro, sono consentite solo determinate
accessibilità dichiarate. Se non è specificato nessun modificatore di accesso in una dichiarazione di membro,
viene usata un'accessibilità predefinita.
I tipi di primo livello, che non sono annidati in altri tipi, possono avere solo l'accessibilità internal o public .
L'accessibilità predefinita per questi tipi è internal .
I tipi annidati, che sono membri di altri tipi, possono avere accessibilità dichiarate come indicato nella tabella
seguente.

A C C ESSIB IL ITÀ P REDEF IN ITA DEL A C C ESSIB IL ITÀ DIC H IA RAT E E


M EM B RI DI M EM B RO C O N SEN T IT E DEL M EM B RO

enum public nessuno


A C C ESSIB IL ITÀ P REDEF IN ITA DEL A C C ESSIB IL ITÀ DIC H IA RAT E E
M EM B RI DI M EM B RO C O N SEN T IT E DEL M EM B RO

class private public

protected

internal

private

protected internal

private protected

interface public nessuno

struct private public

internal

private

L'accessibilità di un tipo annidato dipende dal relativo dominio di accessibilità, che è determinato
dall'accessibilità dichiarata del membro e dal dominio di accessibilità del tipo che lo contiene immediatamente.
Tuttavia il dominio di accessibilità di un tipo annidato non può essere superiore a quello del tipo che lo contiene.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Dominio di accessibilità
Restrizioni relative all'uso dei livelli di accessibilità
Modificatori di accesso
public
private
protected
internal
Dominio di accessibilità (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Il dominio di accessibilità di un membro specifica in quali sezioni del programma è possibile fare riferimento a
un membro. Se il membro è annidato all'interno di un altro tipo, il suo dominio di accessibilità viene
determinato dal livello di accessibilità del membro e dal dominio di accessibilità del tipo contenitore.
Il dominio di accessibilità di un tipo di primo livello è minimo il testo di programma del progetto in cui è
dichiarato. Vale a dire che il dominio include tutti i file di origine di questo progetto. Il dominio di accessibilità di
un tipo annidato è minimo il testo del programma del tipo in cui è dichiarato. Vale a dire che il dominio è il
corpo tipo che include tutti i tipi annidati. Il dominio di accessibilità di un tipo annidato non è mai superiore a
quello del tipo contenitore. Questi concetti vengono illustrati nell'esempio seguente.

Esempio
Questo esempio include un tipo di primo livello, T1 , e due classi annidate, M1 e M2 . Le classi contengono
campi diversi con accessibilità dichiarate. Nel metodo Main un commento segue ogni istruzione per indicare il
dominio di accessibilità di ogni membro. Si noti che le istruzioni che tentano di fare riferimento ai membri
inaccessibili sono impostate come commento. Se si desidera visualizzare gli errori del compilatore causati da un
riferimento a un membro inaccessibile, rimuovere i commenti uno alla volta.

public class T1
{
public static int publicInt;
internal static int internalInt;
private static int privateInt = 0;

static T1()
{
// T1 can access public or internal members
// in a public or private (or internal) nested class.
M1.publicInt = 1;
M1.internalInt = 2;
M2.publicInt = 3;
M2.internalInt = 4;

// Cannot access the private member privateInt


// in either class:
// M1.privateInt = 2; //CS0122
}

public class M1
{
public static int publicInt;
internal static int internalInt;
private static int privateInt = 0;
}

private class M2
{
public static int publicInt = 0;
internal static int internalInt = 0;
private static int privateInt = 0;
}
}

class MainClass
{
{
static void Main()
{
// Access is unlimited.
T1.publicInt = 1;

// Accessible only in current assembly.


T1.internalInt = 2;

// Error CS0122: inaccessible outside T1.


// T1.privateInt = 3;

// Access is unlimited.
T1.M1.publicInt = 1;

// Accessible only in current assembly.


T1.M1.internalInt = 2;

// Error CS0122: inaccessible outside M1.


// T1.M1.privateInt = 3;

// Error CS0122: inaccessible outside T1.


// T1.M2.publicInt = 1;

// Error CS0122: inaccessible outside T1.


// T1.M2.internalInt = 2;

// Error CS0122: inaccessible outside M2.


// T1.M2.privateInt = 3;

// Keep the console open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Restrizioni relative all'uso dei livelli di accessibilità
Modificatori di accesso
public
private
protected
internal
Restrizioni relative all'uso dei livelli di accessibilità
(Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Quando si specifica un tipo in una dichiarazione, verificare se il livello di accessibilità del tipo dipende dal livello
di accessibilità di un membro o di un altro tipo. Ad esempio, la classe di base diretta deve essere accessibile
almeno quanto la classe derivata. Le dichiarazioni seguenti causano un errore del compilatore perché la classe di
base BaseClass è meno accessibile di MyClass :

class BaseClass {...}


public class MyClass: BaseClass {...} // Error

Nella tabella seguente sono riepilogate le restrizioni relative ai livelli di accessibilità dichiarata.

C O N T EXT C O M M EN T I

Classi La classe di base diretta di un tipo di classe deve essere


accessibile almeno quanto il tipo di classe.

Interfacce Le interfacce di base esplicite di un tipo di interfaccia devono


essere accessibili almeno quanto il tipo di interfaccia.

Delegati Il tipo restituito e i tipi di parametro di un tipo delegato


devono essere accessibili almeno quanto il tipo delegato.

Costanti Il tipo di una costante deve essere accessibile almeno quanto


la costante.

Fields Il tipo di un campo deve essere accessibile almeno quanto il


campo.

Metodi Il tipo restituito e i tipi di parametro di un metodo devono


essere accessibili almeno quanto il metodo.

Proprietà Il tipo di una proprietà deve essere accessibile almeno


quanto la proprietà.

Eventi Il tipo di un evento deve essere accessibile almeno quanto


l'evento.

Indicizzatori Il tipo e i tipi di parametro di un indicizzatore devono essere


accessibili almeno quanto l'indicizzatore.

Operatori Il tipo restituito e i tipi di parametro di un operatore devono


essere accessibili almeno quanto l'operatore.

Costruttori I tipi di parametro di un costruttore devono essere accessibili


almeno quanto il costruttore.
Esempio
L'esempio seguente contiene dichiarazioni errate di tipi diversi. Il commento che segue ogni dichiarazione indica
l'errore del compilatore previsto.

// Restrictions on Using Accessibility Levels


// CS0052 expected as well as CS0053, CS0056, and CS0057
// To make the program work, change access level of both class B
// and MyPrivateMethod() to public.

using System;

// A delegate:
delegate int MyDelegate();

class B
{
// A private method:
static int MyPrivateMethod()
{
return 0;
}
}

public class A
{
// Error: The type B is less accessible than the field A.myField.
public B myField = new B();

// Error: The type B is less accessible


// than the constant A.myConst.
public readonly B myConst = new B();

public B MyMethod()
{
// Error: The type B is less accessible
// than the method A.MyMethod.
return new B();
}

// Error: The type B is less accessible than the property A.MyProp


public B MyProp
{
set
{
}
}

MyDelegate d = new MyDelegate(B.MyPrivateMethod);


// Even when B is declared public, you still get the error:
// "The parameter B.MyPrivateMethod is not accessible due to
// protection level."

public static B operator +(A m1, B m2)


{
// Error: The type B is less accessible
// than the operator A.operator +(A,B)
return new B();
}

static void Main()


{
Console.Write("Compiled successfully");
}
}
Specifiche del linguaggio C#
Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Dominio di accessibilità
Livelli di accessibilità
Modificatori di accesso
public
private
protected
internal
internal (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

La parola chiave internal è un modificatore di accesso per tipi e membri dei tipi.

Questa pagina illustra l'accesso internal . La internal parola chiave fa anche parte del
protected internal modificatore di accesso.

I tipi o membri interni sono accessibili solo all'interno di file nello stesso assembly, come nell'esempio seguente:

public class BaseClass


{
// Only accessible within the same assembly.
internal static int x = 0;
}

Per un confronto di internal con altri modificatori di accesso, vedere Livelli di accessibilità e Modificatori di
accesso.
Per altre informazioni sugli assembly, vedere Assembly in .NET.
Un uso comune dell'accesso interno è in fase di sviluppo basato su componenti poiché consente a un gruppo di
componenti di collaborare in modo privato senza essere esposti al resto del codice dell'applicazione. Ad
esempio, un framework per la creazione di interfacce utente grafiche potrebbe indicare le classi Control e
Form che interagiscono usando membri con accesso interno. Poiché questi membri sono interni, non sono
esposti al codice che usa il framework.
Non è corretto fare riferimento a un tipo o a un membro con accesso interno all'esterno dell'assembly in cui è
stato definito.

Esempio
Questo esempio contiene due file, Assembly1.cs e Assembly1_a.cs . Il primo file contiene una classe di base
interna, BaseClass . Nel secondo file, un tentativo di creare un'istanza di BaseClass genererà un errore.

// Assembly1.cs
// Compile with: /target:library
internal class BaseClass
{
public static int intM = 0;
}

// Assembly1_a.cs
// Compile with: /reference:Assembly1.dll
class TestAccess
{
static void Main()
{
var myBase = new BaseClass(); // CS0122
}
}
Esempio
In questo esempio, usare gli stessi file dell'esempio 1 e modificare il livello di accessibilità di BaseClass in
public . Modificare anche il livello di accessibilità del membro intM in internal . In questo caso, è possibile
creare un'istanza della classe, ma non è possibile accedere al membro interno.

// Assembly2.cs
// Compile with: /target:library
public class BaseClass
{
internal static int intM = 0;
}

// Assembly2_a.cs
// Compile with: /reference:Assembly2.dll
public class TestAccess
{
static void Main()
{
var myBase = new BaseClass(); // Ok.
BaseClass.intM = 444; // CS0117
}
}

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
public
private
protected
private (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave private è un modificatore di accesso ai membri.

Questa pagina illustra l'accesso private . La private parola chiave fa anche parte del private protected
modificatore di accesso.

L'accesso privato è il livello di accesso più restrittivo. I membri privati sono accessibili solo all'interno del corpo
della classe o dello struct in cui sono stati dichiarati, come nell'esempio seguente:

class Employee
{
private int i;
double d; // private access by default
}

Anche i tipi annidati dello stesso corpo possono accedere ai membri privati.
Fare riferimento a un membro privato all'esterno della classe o dello struct in cui è stato dichiarato genera un
errore in fase di compilazione.
Per un confronto di private con altri modificatori di accesso, vedere Livelli di accessibilità e Modificatori di
accesso.

Esempio
In questo esempio la classe Employee contiene due membri dati privati, name e salary . Essendo privati, i
membri risulteranno accessibili solo ai metodi di membro. I metodi pubblici GetName e Salary vengono
aggiunti per consentire il controllo dell'accesso ai membri privati. È possibile accedere al membro name tramite
un metodo pubblico e al membro salary tramite una proprietà pubblica di sola lettura. Per altre informazioni,
vedere Proprietà.
class Employee2
{
private string name = "FirstName, LastName";
private double salary = 100.0;

public string GetName()


{
return name;
}

public double Salary


{
get { return salary; }
}
}

class PrivateTest
{
static void Main()
{
var e = new Employee2();

// The data members are inaccessible (private), so


// they can't be accessed like this:
// string n = e.name;
// double s = e.salary;

// 'name' is indirectly accessed via method:


string n = e.GetName();

// 'salary' is indirectly accessed via property


double s = e.Salary;
}
}

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
public
protected
internal
protected (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La parola chiave protected è un modificatore di accesso ai membri.

NOTE
Questa pagina illustra l'accesso protected . La protected parola chiave fa anche parte dei protected internal
private protected modificatori di accesso e.

Un membro protetto è accessibile all'interno della classe di appartenenza e dalle istanze della classe derivata.
Per un confronto di protected con altri modificatori di accesso, vedere Livelli di accessibilità.

Esempio
Un membro protetto di una classe di base è accessibile in una classe derivata solo se viene eseguito l'accesso
tramite il tipo di classe derivata. Si consideri il segmento di codice di esempio seguente:

class A
{
protected int x = 123;
}

class B : A
{
static void Main()
{
var a = new A();
var b = new B();

// Error CS1540, because x can only be accessed by


// classes derived from A.
// a.x = 10;

// OK, because this class derives from A.


b.x = 10;
}
}

L'istruzione a.x = 10 genera un errore perché usata all'interno del metodo statico Main e non in un'istanza
della classe B.
I membri struct non possono essere protetti perché struct non può essere ereditato.

Esempio
In questo esempio la classe DerivedPoint è derivata da Point . Pertanto, è possibile accedere i membri protetti
della classe di base direttamente dalla classe derivata.
class Point
{
protected int x;
protected int y;
}

class DerivedPoint: Point


{
static void Main()
{
var dpoint = new DerivedPoint();

// Direct access to protected members.


dpoint.x = 10;
dpoint.y = 15;
Console.WriteLine($"x = {dpoint.x}, y = {dpoint.y}");
}
}
// Output: x = 10, y = 15

Se si impostano i livelli di accesso di x e y su privato, il compilatore genererà i messaggi di errore seguenti:


'Point.y' is inaccessible due to its protection level.

'Point.x' is inaccessible due to its protection level.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
pubblico
private
internal
Problemi di sicurezza per le parole chiave virtuali interne
public (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La parola chiave public è un modificatore di accesso per tipi e membri dei tipi. L'accesso pubblico è il livello di
accesso più permissivo. Non esistono restrizioni per i membri dell'accesso pubblico, come nel seguente
esempio:

class SampleClass
{
public int x; // No access restrictions.
}

Per altre informazioni, vedere Modificatori di accesso e Livelli di accessibilità.

Esempio
Nell'esempio seguente vengono dichiarate due classi, PointTest e Program . I membri pubblici x e y di
PointTest sono accessibili direttamente da Program .

class PointTest
{
public int x;
public int y;
}

class Program
{
static void Main()
{
var p = new PointTest();
// Direct access to public members.
p.x = 10;
p.y = 15;
Console.WriteLine($"x = {p.x}, y = {p.y}");
}
}
// Output: x = 10, y = 15

Se si modifica il livello di accesso public impostandolo su private o protected, viene visualizzato un messaggio
di errore che
indica che PointTest.y è inaccessibile a causa del livello di protezione.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Modificatori di accesso
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
privata
protected
interno
protected internal (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La combinazione delle parole chiave protected internal è un modificatore di accesso ai membri. Un membro
protected internal è accessibile dall'assembly corrente o dai tipi che derivano dalla classe che li contiene. Per un
confronto di protected internal con altri modificatori di accesso, vedere Livelli di accessibilità.

Esempio
Un membro protected internal di una classe base è accessibile da qualsiasi tipo all'interno dell'assembly che lo
contiene. È inoltre accessibile in una classe derivata che si trova in un altro assembly solo se l'accesso viene
eseguito tramite una variabile del tipo di classe derivata. Si consideri il segmento di codice di esempio seguente:

// Assembly1.cs
// Compile with: /target:library
public class BaseClass
{
protected internal int myValue = 0;
}

class TestAccess
{
void Access()
{
var baseObject = new BaseClass();
baseObject.myValue = 5;
}
}

// Assembly2.cs
// Compile with: /reference:Assembly1.dll
class DerivedClass : BaseClass
{
static void Main()
{
var baseObject = new BaseClass();
var derivedObject = new DerivedClass();

// Error CS1540, because myValue can only be accessed by


// classes derived from BaseClass.
// baseObject.myValue = 10;

// OK, because this class derives from BaseClass.


derivedObject.myValue = 10;
}
}

Questo esempio contiene due file, Assembly1.cs e Assembly2.cs . Il primo file contiene una classe base pubblica,
BaseClass , e un'altra classe, TestAccess . BaseClass è proprietario di un membro protected internal, myValue , a
cui si accede dal tipo TestAccess . Nel secondo file un tentativo di accedere a myValue tramite un'istanza di
BaseClass produrrà un errore, mentre l'accesso a questo membro tramite un'istanza di una classe derivata,
DerivedClass , avrà esito positivo.

I membri struct non possono essere protected internal perché struct non può essere ereditato.
Specifiche del linguaggio C#
Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
public
private
internal
Problemi di sicurezza per le parole chiave virtuali interne
private protected (informazioni di riferimento su
C#)
02/11/2020 • 3 minutes to read • Edit Online

La combinazione delle parole chiave private protected è un modificatore di accesso ai membri. Un membro
protetto privato è accessibile per i tipi derivati dalla classe che lo contiene, ma solo all'interno dell'assembly che
lo contiene. Per un confronto di private protected con altri modificatori di accesso, vedere Livelli di
accessibilità.

NOTE
Il modificatore di accesso private protected è valido in C# versione 7.2 e versioni successive.

Esempio
Un membro protetto privato di una classe base è accessibile dai tipi derivati nell'assembly che lo contiene solo
se il tipo statico della variabile è il tipo della classe derivata. Si consideri il segmento di codice di esempio
seguente:

public class BaseClass


{
private protected int myValue = 0;
}

public class DerivedClass1 : BaseClass


{
void Access()
{
var baseObject = new BaseClass();

// Error CS1540, because myValue can only be accessed by


// classes derived from BaseClass.
// baseObject.myValue = 5;

// OK, accessed through the current derived class instance


myValue = 5;
}
}

// Assembly2.cs
// Compile with: /reference:Assembly1.dll
class DerivedClass2 : BaseClass
{
void Access()
{
// Error CS0122, because myValue can only be
// accessed by types in Assembly1
// myValue = 10;
}
}

Questo esempio contiene due file, Assembly1.cs e Assembly2.cs . Il primo file contiene una classe base pubblica,
BaseClass , e un tipo derivato, DerivedClass1 . BaseClass è proprietaria di un membro protetto privato,
myValue , a cui DerivedClass1 prova ad accedere in due modi. Il primo tentativo di accedere a myValue tramite
un'istanza di BaseClass genererà un errore. Il tentativo di usarlo come membro ereditato in DerivedClass1
avrà, tuttavia, esito positivo.
Nel secondo file un tentativo di accedere a myValue come membro ereditato di DerivedClass2 genererà un
errore, perché è accessibile solo per i tipi derivati in Assembly1.
Se contiene un oggetto InternalsVisibleToAttribute che chiama Assembly2 , la classe derivata
Assembly1.cs
DerivedClass1 avrà accesso ai private protected membri dichiarati in BaseClass . InternalsVisibleTo rende
private protected visibili i membri alle classi derivate in altri assembly.

I membri struct non possono essere private protected perché struct non può essere ereditato.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori di accesso
Livelli di accessibilità
Modificatori
public
private
internal
Problemi di sicurezza per le parole chiave virtuali interne
abstract (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Il modificatore abstract indica che l'oggetto in fase di modifica ha un'implementazione mancante o


incompleta. Il modificatore abstract può essere usato con classi, metodi, proprietà, indicizzatori ed eventi. Usare
il modificatore abstract in una dichiarazione di classe per indicare che una classe verrà usata solo come classe
di base per altre classi e che non verrà creata un'istanza relativamente alla stessa. I membri contrassegnati come
astratti devono essere implementati da classi non astratte che derivano dalla classe astratta.

Esempio
In questo esempio, la classe Square deve eseguire un'implementazione di GetArea poiché deriva da Shape :

abstract class Shape


{
public abstract int GetArea();
}

class Square : Shape


{
int side;

public Square(int n) => side = n;

// GetArea method is required to avoid a compile-time error.


public override int GetArea() => side * side;

static void Main()


{
var sq = new Square(12);
Console.WriteLine($"Area of the square = {sq.GetArea()}");
}
}
// Output: Area of the square = 144

Le classi astratte hanno le caratteristiche seguenti:


Non è possibile creare un'istanza di una classe astratta.
Una classe astratta può contenere funzioni di accesso e metodi astratti.
Non è possibile modificare una classe astratta con il modificatore sealed perché i due modificatori hanno
significati opposti. Il modificatore sealed impedisce a una classe che venga ereditata e il modificatore
abstract richiede una classe da ereditare.

Una classe non astratta derivata da una classe astratta deve includere implementazioni effettive di tutte le
funzioni di accesso e di tutti i metodi astratti ereditati.
Usare il modificatore abstract in una dichiarazione di metodo o proprietà per indicare che il metodo o
proprietà non contiene implementazioni.
I metodi astratti hanno le caratteristiche seguenti:
Un metodo astratto è implicitamente un metodo virtuale.
Le dichiarazioni di metodi astratti sono consentite solo in classi astratte.
Poiché una dichiarazione di un metodo astratto non offre alcuna implementazione effettiva, non c'è
nessun corpo del metodo. La dichiarazione del metodo termina semplicemente con un punto e virgola e
non ci sono parentesi graffe ({ }) dopo la firma. Ad esempio:

public abstract void MyMethod();

L'implementazione viene specificata tramite l'override di un metodo, che è un membro di una classe non
astratta.
Non è possibile usare il modificatore static o virtual in una dichiarazione di un metodo astratto.
Le proprietà astratte si comportano come i metodi astratti, ad eccezione delle differenze nella sintassi di
dichiarazione e di chiamata.
Non è possibile usare il modificatore abstract su una proprietà static.
Una proprietà astratta ereditata può essere sottoposta a override in una classe derivata includendo una
dichiarazione di proprietà che usa il modificatore di override.
Per altre informazioni sulle classi astratte, vedere Classi e membri delle classi astratte e sealed.
Una classe astratta deve specificare l'implementazione per tutti i membri di interfaccia.
Una classe astratta che implementa un'interfaccia può eseguire il mapping dei metodi di interfaccia su metodi
astratti. Ad esempio:

interface I
{
void M();
}

abstract class C : I
{
public abstract void M();
}

Esempio
In questo esempio, la classe DerivedClass è derivata da una classe di base astratta BaseClass . La classe astratta
contiene un metodo astratto, AbstractMethod , e due proprietà astratte, X e Y .
abstract class BaseClass // Abstract class
{
protected int _x = 100;
protected int _y = 150;
public abstract void AbstractMethod(); // Abstract method
public abstract int X { get; }
public abstract int Y { get; }
}

class DerivedClass : BaseClass


{
public override void AbstractMethod()
{
_x++;
_y++;
}

public override int X // overriding property


{
get
{
return _x + 10;
}
}

public override int Y // overriding property


{
get
{
return _y + 10;
}
}

static void Main()


{
var o = new DerivedClass();
o.AbstractMethod();
Console.WriteLine($"x = {o.X}, y = {o.Y}");
}
}
// Output: x = 111, y = 161

Nell'esempio precedente, se si prova a creare un'istanza della classe astratta tramite un'istruzione simile alla
seguente:

BaseClass bc = new BaseClass(); // Error

si ottiene un messaggio di errore che informa che il compilatore non può creare un'istanza della classe astratta
"BaseClass".

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Modificatori
virtuale
override
Parole chiave di C#
async (Riferimenti per C#)
02/11/2020 • 7 minutes to read • Edit Online

Usare il modificatore async per specificare che un metodo, un'espressione lambda o un metodo anonimo sia
asincrono. Se si usa questo modificatore in un metodo o in un'espressione, viene indicato come metodo
asincrono. L'esempio seguente definisce un metodo asincrono denominato ExampleMethodAsync :

public async Task<int> ExampleMethodAsync()


{
//...
}

Se non si ha familiarità con la programmazione asincrona o non si comprende in che modo un metodo
asincrono usa l' await operatore per eseguire operazioni potenzialmente prolungate senza bloccare il thread
del chiamante, leggere l'introduzione in programmazione asincrona con Async e await. Il codice seguente si
trova all'interno di un metodo asincrono e chiama il metodo HttpClient.GetStringAsync:

string contents = await httpClient.GetStringAsync(requestUrl);

Un metodo asincrono viene eseguito in modo sincrono finché non raggiunge la prima espressione await . A
quel punto, il metodo viene sospeso fino al completamento dell'attività attesa. Nel frattempo il controllo torna al
chiamante del metodo, come illustrato nell'esempio della sezione successiva.
Il metodo modificato dalla parola chiave async viene eseguito in modo sincrono se non contiene
un'espressione o un'istruzione await . Un avviso del compilatore segnala eventuali metodi asincroni che non
contengono istruzioni await , perché questa situazione potrebbe indicare un errore. Vedere Avviso del
compilatore (livello 1) CS4014.
La parola chiave async è contestuale, in quanto è una parola chiave solo quando modifica un metodo,
un'espressione lambda o un metodo anonimo. In tutti gli altri contesti, viene interpretato come identificatore.

Esempio
Nell'esempio seguente vengono illustrati la struttura e il flusso di controllo tra un gestore eventi asincrono,
StartButton_Click , e un metodo asincrono, ExampleMethodAsync . Il risultato ottenuto dal metodo asincrono è il
numero di caratteri di una pagina Web. Il codice è adatto per un'applicazione Windows Presentation Foundation
(WPF) o un'app di Windows Store creata in Visual Studio. Vedere i commenti del codice per l'installazione
dell'applicazione.
È possibile eseguire questo codice in Visual Studio come un'app Windows Presentation Foundation (WPF) o
un'app di Windows Store. Sono necessari un controllo Button denominato StartButton e un controllo Textbox
denominato ResultsTextBox . Ricordare di impostare i nomi e il gestore in modo da ottenere codice simile al
seguente:

<Button Content="Button" HorizontalAlignment="Left" Margin="88,77,0,0" VerticalAlignment="Top" Width="75"


Click="StartButton_Click" Name="StartButton"/>
<TextBox HorizontalAlignment="Left" Height="137" Margin="88,140,0,0" TextWrapping="Wrap"
Text="&lt;Enter a URL&gt;" VerticalAlignment="Top" Width="310" Name="ResultsTextBox"/>

Per eseguire il codice come app WPF:


Incollare il codice nella classe MainWindow in MainWindow.xaml.cs.
Aggiungere un riferimento a System.Net.Http.
Aggiungere una direttiva using per System.Net.Http.
Per eseguire il codice come app di Windows Store:
Incollare questo codice nella classe MainPage in MainPage.xaml.cs.
Aggiungere direttive using per System.Net.Http e System.Threading.Tasks.

private async void StartButton_Click(object sender, RoutedEventArgs e)


{
// ExampleMethodAsync returns a Task<int>, which means that the method
// eventually produces an int result. However, ExampleMethodAsync returns
// the Task<int> value as soon as it reaches an await.
ResultsTextBox.Text += "\n";

try
{
int length = await ExampleMethodAsync();
// Note that you could put "await ExampleMethodAsync()" in the next line where
// "length" is, but due to when '+=' fetches the value of ResultsTextBox, you
// would not see the global side effect of ExampleMethodAsync setting the text.
ResultsTextBox.Text += String.Format("Length: {0:N0}\n", length);
}
catch (Exception)
{
// Process the exception if one occurs.
}
}

public async Task<int> ExampleMethodAsync()


{
var httpClient = new HttpClient();
int exampleInt = (await httpClient.GetStringAsync("http://msdn.microsoft.com")).Length;
ResultsTextBox.Text += "Preparing to finish ExampleMethodAsync.\n";
// After the following return statement, any method that's awaiting
// ExampleMethodAsync (in this case, StartButton_Click) can get the
// integer result.
return exampleInt;
}
// The example displays the following output:
// Preparing to finish ExampleMethodAsync.
// Length: 53292

IMPORTANT
Per ulteriori informazioni sulle attività e sul codice eseguito in attesa di un'attività, vedere programmazione asincrona con
Async e await. Per un esempio di console completo che usa elementi simili, vedere elaborare le attività asincrone quando
vengono completate (C#).

Tipi restituiti
Un metodo asincrono può avere i tipi restituiti seguenti:
Task
Task<TResult>
void. I metodi async void sono in genere sconsigliati per il codice diverso dai gestori eventi perché i
chiamanti non possono usare await per questi metodi e devono implementare un meccanismo diverso per
segnalare il completamento corretto o condizioni di errore.
A partire da C# 7.0, qualsiasi tipo con un metodo GetAwaiter accessibile. Il tipo
System.Threading.Tasks.ValueTask<TResult> è una di queste implementazioni ed è disponibile aggiungendo il
pacchetto NuGet System.Threading.Tasks.Extensions .

Un metodo asincrono non può dichiarare parametri in, ref o out e nemmeno avere un valore di riferimento
restituito , ma può chiamare metodi con tali parametri.
Specificare Task<TResult> come tipo restituito di un metodo asincrono se l'istruzione return del metodo
specifica un operando di tipo TResult . Utilizzare Task se non viene restituito alcun valore significativo al
completamento del metodo. Ciò significa che una chiamata al metodo restituisce Task , ma al completamento di
Task , qualsiasi espressione await in attesa di Task restituisce void .

Utilizzare il tipo restituito void principalmente per definire gestori eventi, che richiedono tale tipo restituito. Il
chiamante di un metodo asincrono che restituisce void non può attendere il metodo e non può acquisire
eccezioni generate dal metodo.
A partire da C# 7.0, viene restituito un altro tipo, in genere un tipo valore, che ha un metodo GetAwaiter per
ridurre al minimo le allocazioni di memoria nelle sezioni di codice critiche per le prestazioni.
Per altre informazioni ed esempi, vedere Tipi restituiti asincroni.

Vedere anche
AsyncStateMachineAttribute
await
Programmazione asincrona con Async e await
Elaborare le attività asincrone quando vengono completate
const (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Si usa la parola chiave const per dichiarare un campo costante o una variabile locale costante. I campi e le
variabili locali costanti non sono variabili e non sono quindi modificabili. Gli elementi costanti possono essere
numeri, valori booleani, stringhe o un riferimento Null. Non creare una costante per rappresentare informazioni
di cui si prevede la modifica in qualsiasi momento. Un campo costante, ad esempio, non deve essere usato per
archiviare il prezzo di un servizio, il numero di versione di un prodotto o il nome dell'organizzazione di una
società. Tali valori potrebbero cambiare nel tempo e, poiché i compilatori propagano le costanti, eventuale altro
codice compilato con quelle librerie dovrebbe essere ricompilato per riflettere le modifiche. Vedere anche la
parola chiave readonly. Ad esempio:

const int X = 0;
public const double GravitationalConstant = 6.673e-11;
private const string ProductName = "Visual C#";

Osservazioni
Il tipo di una dichiarazione di costante specifica il tipo di membri introdotti dalla dichiarazione. L'inizializzatore di
una variabile locale costante o di un campo costante deve essere un'espressione costante che possa essere
convertita in modo implicito nel tipo di destinazione.
Un'espressione di costanti è un'espressione che può essere valutata interamente in fase di compilazione. Di
conseguenza, gli unici valori possibili per le costanti dei tipi di riferimento sono string e il riferimento Null.
La dichiarazione di costante può includere più costanti, ad esempio:

public const double X = 1.0, Y = 2.0, Z = 3.0;

Il modificatore static non è consentito in una dichiarazione di costante.


Una costante può far parte di un'espressione costante, ad esempio:

public const int C1 = 5;


public const int C2 = C1 + 100;

NOTE
La parola chiave readonly è diversa dalla parola chiave const . Un campo const può essere inizializzato solo nella
dichiarazione del campo. Un campo readonly può essere inizializzato nella dichiarazione o in un costruttore. I campi
readonly possono quindi presentare valori diversi a seconda del costruttore usato. Inoltre, mentre un campo const
rappresenta una costante in fase di compilazione, il campo readonly può essere usato per le costanti in fase di
esecuzione, come nella riga seguente: public static readonly uint l1 = (uint)DateTime.Now.Ticks;

Esempio
public class ConstTest
{
class SampleClass
{
public int x;
public int y;
public const int C1 = 5;
public const int C2 = C1 + 5;

public SampleClass(int p1, int p2)


{
x = p1;
y = p2;
}
}

static void Main()


{
var mC = new SampleClass(11, 22);
Console.WriteLine($"x = {mC.x}, y = {mC.y}");
Console.WriteLine($"C1 = {SampleClass.C1}, C2 = {SampleClass.C2}");
}
}
/* Output
x = 11, y = 22
C1 = 5, C2 = 10
*/

Esempio
In questo esempio viene illustrato come usare le costanti come variabili locali.

public class SealedTest


{
static void Main()
{
const int C = 707;
Console.WriteLine($"My local constant = {C}");
}
}
// Output: My local constant = 707

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
ReadOnly
Event (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

La parola chiave event viene usata per dichiarare un evento in una classe autore.

Esempio
L'esempio seguente illustra come dichiarare e generare un evento che usa EventHandler come tipo di delegato
sottostante. Per l'esempio di codice completo che illustra anche come usare il EventHandler<TEventArgs> tipo
delegato generico e come sottoscrivere un evento e creare un metodo del gestore eventi, vedere come
pubblicare eventi conformi alle linee guida di .NET.

public class SampleEventArgs


{
public SampleEventArgs(string text) { Text = text; }
public string Text { get; } // readonly
}

public class Publisher


{
// Declare the delegate (if using non-generic pattern).
public delegate void SampleEventHandler(object sender, SampleEventArgs e);

// Declare the event.


public event SampleEventHandler SampleEvent;

// Wrap the event in a protected virtual method


// to enable derived classes to raise the event.
protected virtual void RaiseSampleEvent()
{
// Raise the event in a thread-safe manner using the ?. operator.
SampleEvent?.Invoke(this, new SampleEventArgs("Hello"));
}
}

Gli eventi sono un tipo di delegati multicast speciali che possono essere chiamati solo dall'interno della classe o
dello struct in cui sono dichiarati (la classe autore). Se altre classi o altri struct sottoscrivono l'evento, i metodi di
gestione eventi corrispondenti verranno chiamati quando la classe publisher genera l'evento. Per altre
informazioni e altri esempi di codice, vedere Eventi e Delegati.
Gli eventi possono essere contrassegnati come public, private, protected, Internal, protected internalo private
protected. Questi modificatori di accesso definiscono in che modo gli utenti della classe possono accedere
all'evento. Per altre informazioni, vedere Modificatori di accesso.

Parole chiave ed eventi


Agli eventi si applicano le parole chiave seguenti.

PA RO L A C H IAVE DESC RIZ IO N E P ER ULT ERIO RI IN F O RM A Z IO N I

static Rende l'evento disponibile per i Classi statiche e membri di classi


chiamanti in qualsiasi momento, anche statiche
se non esiste alcuna istanza della
classe.
PA RO L A C H IAVE DESC RIZ IO N E P ER ULT ERIO RI IN F O RM A Z IO N I

virtuale Consente alle classi derivate di Ereditarietà


eseguire l'override del comportamento
dell'evento tramite la parola chiave
override.

sealed Specifica che per le classi derivate


l'evento non è più virtuale.

astratta Il compilatore non genererà i blocchi di


funzioni di accesso degli eventi add e
remove e pertanto le classi derivate
dovranno fornire la propria
implementazione.

Un evento può essere dichiarato statico mediante la parola chiave static. Ciò rende l'evento disponibile per i
chiamanti in qualsiasi momento, anche se non esiste alcuna istanza della classe. Per altre informazioni, vedere
classi statiche e membri di classi statiche.
Un evento può essere contrassegnato come virtuale mediante la parola chiave virtual. Ciò consente alle classi
derivate di eseguire l'override del comportamento dell'evento tramite la parola chiave override. Per altre
informazioni, vedere Ereditarietà. Un evento che esegue l'override di un evento virtuale può anche essere
contrassegnato come sealed, in modo che non risulti più virtuale per le classi derivate. Infine, un evento può
essere dichiarato abstract, in modo che il compilatore non generi blocchi di funzioni di accesso agli eventi add e
remove . Le classi derivate devono pertanto fornire la propria implementazione.

Specifiche del linguaggio C#


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

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
add
remove
Modificatori
Come combinare delegati (delegati multicast)
extern (Riferimenti per C#)
28/01/2021 • 4 minutes to read • Edit Online

Il modificatore extern consente di dichiarare un metodo implementato esternamente. Il modificatore extern


viene utilizzato in genere con l'attributo DllImport quando si effettua una chiamata in codice non gestito
tramite i servizi di interoperabilità. In questo caso, anche il metodo deve essere dichiarato come static , come
illustrato nell'esempio seguente:

[DllImport("avifil32.dll")]
private static extern void AVIFileInit();

La parola chiave extern può definire anche un alias di assembly esterno, rendendo possibile il riferimento a
versioni diverse dello stesso componente dall'interno di un unico assembly. Per altre informazioni, vedere alias
esterno.
È errato usare i modificatori abstract e extern contemporaneamente per modificare lo stesso membro.
L'utilizzo del modificatore extern indica che il metodo viene implementato all'esterno del codice C#, mentre
l'utilizzo del modificatore abstract indica che l'implementazione del metodo non viene fornita nella classe.
La parola chiave extern ha un utilizzo più limitato in c# rispetto a C++. Per confrontare la parola chiave C# con la
parola chiave C++, vedere Utilizzo di extern per specificare il collegamento in Riferimenti al linguaggio C++.

Esempio 1
In questo esempio il programma riceve una stringa dall'utente e la visualizza in una finestra di messaggio. Il
programma utilizza il metodo MessageBox importato dalla libreria User32.dll.

//using System.Runtime.InteropServices;
class ExternTest
{
[DllImport("User32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBox(IntPtr h, string m, string c, int type);

static int Main()


{
string myString;
Console.Write("Enter your message: ");
myString = Console.ReadLine();
return MessageBox((IntPtr)0, myString, "My Message Box", 0);
}
}

Esempio 2
In questo esempio viene illustrato un programma C# che chiama una libreria C (una DLL nativa).
1. Creare il seguente file C e denominarlo cmdll.c :
// cmdll.c
// Compile with: -LD
int __declspec(dllexport) SampleMethod(int i)
{
return i*10;
}

2. Aprire una finestra del prompt dei comandi degli strumenti nativi di Visual Studio x64 o x32 dalla
directory di installazione di Visual Studio e compilare il file cmdll.c digitando cl -LD cmdll.c al prompt
dei comandi.
3. Nella stessa directory creare il seguente file C# e denominarlo cm.cs :

// cm.cs
using System;
using System.Runtime.InteropServices;
public class MainClass
{
[DllImport("Cmdll.dll")]
public static extern int SampleMethod(int x);

static void Main()


{
Console.WriteLine("SampleMethod() returns {0}.", SampleMethod(5));
}
}

4. Aprire una finestra del prompt dei comandi degli strumenti nativi di Visual Studio x64 o x32) dalla
directory di installazione di Visual Studio e compilare il file cm.cs digitando:

csc cm.cs (al prompt dei comandi x64) o csc-platform:x86 cm.cs (al prompt dei comandi x32)

Verrà creato il file eseguibile cm.exe .


5. Eseguire cm.exe . Il metodo SampleMethod passa il valore 5 al file DLL, che restituisce il valore moltiplicato
per 10. Il programma produce l'output seguente:

SampleMethod() returns 50.

Specifiche del linguaggio C#


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

Vedere anche
System.Runtime.InteropServices.DllImportAttribute
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
in (Modificatore generico) (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Per i parametri di tipo generico, la parola chiave in specifica che il parametro di tipo è controvariante. È
possibile usare la parola chiave in in interfacce e delegati generici.
La controvarianza consente di usare un tipo meno derivato di quello specificato dal parametro generico. Ciò
consente la conversione implicita di classi che implementano interfacce controvarianti e la conversione implicita
di tipi delegati. Nei parametri di tipo generico la covarianza e la controvarianza sono supportate per i tipi di
riferimento, ma non per i tipi di valore.
Un tipo può essere dichiarato controvariante in un'interfaccia o in un delegato generico solo se definisce il tipo
dei parametri di un metodo e non di un tipo restituito dal metodo. I parametri In , ref e out devono essere
invarianti, ovvero non sono né covariante né controvariante.
Un'interfaccia che dispone di un parametro di tipo controvariante consente ai metodi di accettare argomenti di
tipi meno derivati di quelli specificati dal parametro di tipo di interfaccia. Ad esempio, nell'interfaccia
IComparer<T>, il tipo T è controvariante, è possibile assegnare un oggetto di tipo IComparer<Person> a un
oggetto di tipo IComparer<Employee> senza usare alcun metodo di conversione speciale se Employee eredita
Person .

A un delegato controvariante può essere assegnato un altro delegato dello stesso tipo, ma con un parametro di
tipo generico meno derivato.
Per altre informazioni, vedere Covarianza e controvarianza.

Interfaccia generica controvariante


L'esempio seguente illustra come dichiarare, estendere e implementare un'interfaccia generica controvariante.
L'esempio descrive anche come usare la conversione implicita per le classi che implementano quest'interfaccia.

// Contravariant interface.
interface IContravariant<in A> { }

// Extending contravariant interface.


interface IExtContravariant<in A> : IContravariant<A> { }

// Implementing contravariant interface.


class Sample<A> : IContravariant<A> { }

class Program
{
static void Test()
{
IContravariant<Object> iobj = new Sample<Object>();
IContravariant<String> istr = new Sample<String>();

// You can assign iobj to istr because


// the IContravariant interface is contravariant.
istr = iobj;
}
}

Delegato generico controvariante


L'esempio seguente illustra come dichiarare, creare un'istanza e chiamare un delegato generico controvariante.
Illustra anche come convertire in modo implicito un tipo delegato.

// Contravariant delegate.
public delegate void DContravariant<in A>(A argument);

// Methods that match the delegate signature.


public static void SampleControl(Control control)
{ }
public static void SampleButton(Button button)
{ }

public void Test()


{

// Instantiating the delegates with the methods.


DContravariant<Control> dControl = SampleControl;
DContravariant<Button> dButton = SampleButton;

// You can assign dControl to dButton


// because the DContravariant delegate is contravariant.
dButton = dControl;

// Invoke the delegate.


dButton(new Button());
}

Specifiche del linguaggio C#


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

Vedere anche
out
Covarianza e controvarianza
Modificatori
Modificatore new (Riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

Se usata come modificatore di dichiarazione, la parola chiave new nasconde in modo esplicito un membro
ereditato da una classe base. Quando si nasconde un membro ereditato, la versione derivata del membro
sostituisce la versione della classe base. Sebbene sia possibile nascondere i membri senza usare il modificatore
new , viene visualizzato un avviso del compilatore. Se si usa new in modo esplicito per nascondere un membro,
esso elimina l'avviso.
È anche possibile usare la parola chiave new per creare un'istanza di un tipo o come vincolo di tipo generico.
Per nascondere un membro ereditato, dichiararlo nella classe derivata usando lo stesso nome di membro e
modificarlo con la parola chiave new . Ad esempio:

public class BaseC


{
public int x;
public void Invoke() { }
}
public class DerivedC : BaseC
{
new public void Invoke() { }
}

In questo esempio BaseC.Invoke è nascosto da DerivedC.Invoke . Il campo x non è interessato perché non è
nascosto da un nome simile.
Un nome nascosto tramite ereditarietà accetta uno dei formati seguenti:
In genere, una costante, un campo, una proprietà o un tipo introdotto in una classe o uno struct nasconde
tutti i membri della classe base che condividono il nome. Esistono casi particolari. Se, ad esempio, si
dichiara che un nuovo campo con il nome N dispone di un tipo non richiamabile e un tipo di base
dichiara che N sia un metodo, il nuovo campo non nasconde la dichiarazione di base nella sintassi di
chiamata. Per altre informazioni, vedere la sezione Ricerca di membri della specifica del linguaggio C#.
Un metodo inserito in una classe o uno struct nasconde proprietà, campi e tipi che condividono il nome
con la classe base. Nasconde inoltre tutti i metodi della classe base con la stessa firma.
Un indicizzatore inserito in una classe o uno struct nasconde tutti gli indicizzatori della classe base con la
stessa firma.
Non è possibile usare sia new sia override nello stesso membro, in quanto i significati dei due modificatori si
escludono reciprocamente. Il modificatore new crea un nuovo membro con lo stesso nome e fa sì che il
membro originale venga nascosto. Il modificatore override estende l'implementazione per un membro
ereditato.
L'utilizzo del modificatore new in una dichiarazione che non nasconde un membro ereditato genera un avviso.

Esempio
In questo esempio, una classe base, BaseC , e una classe derivata, DerivedC , usano lo stesso nome di campo x ,
che nasconde il valore del campo ereditato. Nell'esempio viene illustrato l'utilizzo del modificatore new . Viene
inoltre descritto come accedere ai membri nascosti della classe base usando i relativi nomi completi.
public class BaseC
{
public static int x = 55;
public static int y = 22;
}

public class DerivedC : BaseC


{
// Hide field 'x'.
new public static int x = 100;

static void Main()


{
// Display the new value of x:
Console.WriteLine(x);

// Display the hidden value of x:


Console.WriteLine(BaseC.x);

// Display the unhidden member y:


Console.WriteLine(y);
}
}
/*
Output:
100
55
22
*/

Esempio
In questo esempio, una classe annidata nasconde una classe che ha lo stesso nome nella classe base. L'esempio
illustra come usare il modificatore new per eliminare il messaggio di avviso e come accedere ai membri di
classe nascosti tramite i relativi nomi completi.
public class BaseC
{
public class NestedC
{
public int x = 200;
public int y;
}
}

public class DerivedC : BaseC


{
// Nested type hiding the base type members.
new public class NestedC
{
public int x = 100;
public int y;
public int z;
}

static void Main()


{
// Creating an object from the overlapping class:
NestedC c1 = new NestedC();

// Creating an object from the hidden class:


BaseC.NestedC c2 = new BaseC.NestedC();

Console.WriteLine(c1.x);
Console.WriteLine(c2.x);
}
}
/*
Output:
100
200
*/

Se si rimuove il modificatore new , la compilazione e l'esecuzione del programma saranno comunque possibili,
ma verrà visualizzato il messaggio di avviso riportato di seguito:

The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Modificatore new della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
Controllo delle versioni con le parole chiave Override e New
Sapere quando utilizzare le parole chiave Override e New
out (modificatore generico) (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Per i parametri di tipo generico, la parola chiave out specifica che il parametro di tipo è covariante. È possibile
usare la parola chiave out in interfacce e delegati generici.
La covarianza consente di usare un tipo più derivato di quello specificato dal parametro generico. Ciò consente
la conversione implicita di classi che implementano interfacce covarianti e la conversione implicita di tipi
delegati. La covarianza e la controvarianza sono supportate per i tipi di riferimento, ma non per i tipi di valore.
Un'interfaccia che dispone di un parametro di tipo covariante consente ai metodi di restituire tipi più derivati di
quelli specificati dal parametro di tipo. Poiché, ad esempio, in .NET Framework 4, nell'interfaccia
IEnumerable<T>, il tipo T è covariante, è possibile assegnare un oggetto di tipo IEnumerable(Of String) a un
oggetto di tipo IEnumerable(Of Object) senza usare alcun metodo di conversione speciale.
A un delegato covariante può essere assegnato un altro delegato dello stesso tipo, ma con un parametro di tipo
generico più derivato.
Per altre informazioni, vedere Covarianza e controvarianza.

Esempio: interfaccia generica covariante


L'esempio seguente illustra come dichiarare, estendere e implementare un'interfaccia generica covariante.
L'esempio descrive anche come usare la conversione implicita per le classi che implementano un'interfaccia
covariante.

// Covariant interface.
interface ICovariant<out R> { }

// Extending covariant interface.


interface IExtCovariant<out R> : ICovariant<R> { }

// Implementing covariant interface.


class Sample<R> : ICovariant<R> { }

class Program
{
static void Test()
{
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();

// You can assign istr to iobj because


// the ICovariant interface is covariant.
iobj = istr;
}
}

In un'interfaccia generica un parametro di tipo può essere dichiarato covariante se soddisfa le condizioni
seguenti:
Il parametro di tipo viene usato solo come tipo restituito di metodi di interfaccia e non viene usato come
tipo di argomenti del metodo.
NOTE
Esiste un'eccezione a questa regola. Se in un'interfaccia covariante è presente un delegato generico controvariante
come parametro del metodo, è possibile usare il tipo covariante come parametro di tipo generico per questo
delegato. Per altre informazioni sui delegati generici covarianti e controvarianti, vedere Varianza nei delegati e Uso
della varianza per i delegati generici Func e Action.

Il parametro di tipo non viene usato come vincolo generico per i metodi di interfaccia.

Esempio: delegato generico covariante


L'esempio seguente illustra come dichiarare, creare un'istanza e richiamare un delegato generico covariante.
Viene anche descritto come convertire in modo implicito i tipi delegati.

// Covariant delegate.
public delegate R DCovariant<out R>();

// Methods that match the delegate signature.


public static Control SampleControl()
{ return new Control(); }

public static Button SampleButton()


{ return new Button(); }

public void Test()


{
// Instantiate the delegates with the methods.
DCovariant<Control> dControl = SampleControl;
DCovariant<Button> dButton = SampleButton;

// You can assign dButton to dControl


// because the DCovariant delegate is covariant.
dControl = dButton;

// Invoke the delegate.


dControl();
}

In un delegato generico un tipo può essere dichiarato covariante se viene usato solo come tipo restituito del
metodo e non per gli argomenti del metodo.

Specifiche del linguaggio C#


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

Vedere anche
Varianza nelle interfacce generiche
in
Modificatori
override (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Il modificatore override è necessario per estendere o modificare l'implementazione astratta o virtuale di un


metodo, una proprietà, un indicizzatore o un evento ereditato.
Nell'esempio seguente, la Square classe deve fornire un'implementazione sottoposta a override di GetArea
perché GetArea viene ereditato dalla Shape classe astratta:

abstract class Shape


{
public abstract int GetArea();
}

class Square : Shape


{
int side;

public Square(int n) => side = n;

// GetArea method is required to avoid a compile-time error.


public override int GetArea() => side * side;

static void Main()


{
var sq = new Square(12);
Console.WriteLine($"Area of the square = {sq.GetArea()}");
}
}
// Output: Area of the square = 144

Un override metodo fornisce una nuova implementazione del metodo ereditato da una classe base. Il metodo
che viene sottoposto a override mediante una dichiarazione override viene definito metodo di base sottoposto
a override. Un override metodo deve avere la stessa firma del metodo di base sottoposto a override. A partire
da C# 9,0, i override metodi supportano i tipi restituiti covariante. In particolare, il tipo restituito di un
override metodo può derivare dal tipo restituito del metodo di base corrispondente. In C# 8,0 e versioni
precedenti, i tipi restituiti di un override metodo e il metodo di base sottoposto a override devono essere
uguali.
Non è possibile eseguire l'override di un metodo non virtuale o statico. Il metodo di base sottoposto a override
deve essere virtual , abstract o override .
Una dichiarazione override non può modificare l'accessibilità del metodo virtual . Il metodo override e il
metodo virtual devono avere lo stesso modificatore del livello di accesso.
Non è possibile usare i modificatori new , static o virtual per modificare un metodo override .
Una dichiarazione di proprietà che esegue l'override deve specificare esattamente lo stesso modificatore di
accesso, il tipo e il nome della proprietà ereditata. A partire da C# 9,0, le proprietà che eseguono l'override di
sola lettura supportano i tipi restituiti covariante. La proprietà sottoposta a override deve essere virtual ,
abstract o override .

Per altre informazioni sull'uso della parola chiave override , vedere Controllo delle versioni con le parole chiave
Override e New e Sapere quando utilizzare le parole chiave Override e New. Per informazioni sull'ereditarietà,
vedere Ereditarietà.
Esempio
Questo esempio definisce una classe base denominata Employee e una classe derivata denominata
SalesEmployee . La classe SalesEmployee include il campo aggiuntivo salesbonus ed esegue l'override del
metodo CalculatePay per prenderlo in considerazione.
class TestOverride
{
public class Employee
{
public string name;

// Basepay is defined as protected, so that it may be


// accessed only by this class and derived classes.
protected decimal basepay;

// Constructor to set the name and basepay values.


public Employee(string name, decimal basepay)
{
this.name = name;
this.basepay = basepay;
}

// Declared virtual so it can be overridden.


public virtual decimal CalculatePay()
{
return basepay;
}
}

// Derive a new class from Employee.


public class SalesEmployee : Employee
{
// New field that will affect the base pay.
private decimal salesbonus;

// The constructor calls the base-class version, and


// initializes the salesbonus field.
public SalesEmployee(string name, decimal basepay,
decimal salesbonus) : base(name, basepay)
{
this.salesbonus = salesbonus;
}

// Override the CalculatePay method


// to take bonus into account.
public override decimal CalculatePay()
{
return basepay + salesbonus;
}
}

static void Main()


{
// Create some new employees.
var employee1 = new SalesEmployee("Alice",
1000, 500);
var employee2 = new Employee("Bob", 1200);

Console.WriteLine($"Employee1 {employee1.name} earned: {employee1.CalculatePay()}");


Console.WriteLine($"Employee2 {employee2.name} earned: {employee2.CalculatePay()}");
}
}
/*
Output:
Employee1 Alice earned: 1500
Employee2 Bob earned: 1200
*/

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione metodi di override della specifica del linguaggio C#.
Per ulteriori informazioni sui tipi restituiti covarianti, vedere la Nota relativa alla proposta di funzionalità.

Vedere anche
Informazioni di riferimento su C#
Ereditarietà
Parole chiave di C#
Modificatori
abstract
virtuale
new (modifier)
Polimorfismo
readonly (Riferimenti per C#)
02/11/2020 • 7 minutes to read • Edit Online

La readonly parola chiave è un modificatore che può essere usato in quattro contesti:
In una dichiarazionedi campo readonly indica che l'assegnazione al campo può essere eseguita solo
come parte della dichiarazione o in un costruttore della stessa classe. Un campo readonly può essere
assegnato e riassegnato più volte nella dichiarazione del campo e nel costruttore.
readonly Non è possibile assegnare un campo dopo la chiusura del costruttore. Questa regola ha
implicazioni diverse per i tipi di valore e i tipi di riferimento:
Poiché i tipi di valore contengono direttamente i rispettivi dati, un campo contenente un tipo di valore
readonly non è modificabile.
Poiché i tipi di riferimento contengono un riferimento ai rispettivi dati, un campo contenente un tipo di
riferimento readonly deve sempre fare riferimento allo stesso oggetto, L'oggetto non è modificabile.
Il modificatore readonly impedisce che il campo venga sostituito da un'istanza diversa del tipo di
riferimento. Tuttavia, il modificatore non impedisce la modifica dei dati dell'istanza del campo tramite
il campo di sola lettura.

WARNING
Un tipo visibile esternamente che contiene un campo di sola lettura visibile esternamente che è un tipo di
riferimento modificabile può essere una vulnerabilità di sicurezza e può generare un avviso CA2104 : "non
dichiarare tipi di riferimento modificabili in sola lettura".

In una readonly struct definizione di tipo, readonly indica che il tipo di struttura non è modificabile. Per
altre informazioni, vedere la sezione readonly struct dell'articolo tipi di struttura .
In una dichiarazione di membro di istanza all'interno di un tipo di struttura, readonly indica che un
membro di istanza non modifica lo stato della struttura. Per ulteriori informazioni, vedere la sezione
readonly membri di istanza dell'articolo tipi di struttura .

In un ref readonly metodo restituito, il readonly modificatore indica che il metodo restituisce un
riferimento e le Scritture non sono consentite a tale riferimento.
I readonly struct ref readonly contesti e sono stati aggiunti in C# 7,2. readonly membri struct aggiunti in C#
8,0

Esempio di campo di sola lettura


In questo esempio, il valore del campo year non può essere modificato nel metodo ChangeYear , anche se
viene assegnato un valore nel costruttore della classe:
class Age
{
readonly int year;
Age(int year)
{
this.year = year;
}
void ChangeYear()
{
//year = 1967; // Compile error if uncommented.
}
}

È possibile assegnare un valore a un campo readonly solo nei contesti seguenti:


Quando la variabile viene inizializzata nella dichiarazione, ad esempio:

public readonly int y = 5;

In un costruttore di istanze della classe che contiene la dichiarazione del campo di istanza.
Nel costruttore statico della classe che contiene la dichiarazione del campo statico.
Questi contesti del costruttore sono anche gli unici contesti in cui è possibile passare un readonly campo come
parametro out o ref .

NOTE
La parola chiave readonly è diversa dalla parola chiave const. Un campo const può essere inizializzato solo nella
dichiarazione del campo. Un campo readonly può essere assegnato più volte nella dichiarazione del campo e in qualsiasi
costruttore. I campi readonly possono quindi presentare valori diversi a seconda del costruttore usato. Inoltre, mentre
un const campo è una costante in fase di compilazione, il readonly campo può essere usato per le costanti in fase di
esecuzione, come nell'esempio seguente:

public static readonly uint timeStamp = (uint)DateTime.Now.Ticks;


public class SamplePoint
{
public int x;
// Initialize a readonly field
public readonly int y = 25;
public readonly int z;

public SamplePoint()
{
// Initialize a readonly instance field
z = 24;
}

public SamplePoint(int p1, int p2, int p3)


{
x = p1;
y = p2;
z = p3;
}

public static void Main()


{
SamplePoint p1 = new SamplePoint(11, 21, 32); // OK
Console.WriteLine($"p1: x={p1.x}, y={p1.y}, z={p1.z}");
SamplePoint p2 = new SamplePoint();
p2.x = 55; // OK
Console.WriteLine($"p2: x={p2.x}, y={p2.y}, z={p2.z}");
}
/*
Output:
p1: x=11, y=21, z=32
p2: x=55, y=25, z=24
*/
}

Nell'esempio precedente, se si usa un'istruzione simile all'esempio seguente:

p2.y = 66; // Error

verrà ricevuto il messaggio di errore del compilatore:


Non è possibile assegnare un campo di sola lettura a (tranne che in un costruttore o in un
inizializzatore di variabile)

Esempio di restituzione riferimento di sola lettura


Il readonly modificatore di un oggetto ref return indica che il riferimento restituito non può essere
modificato. L'esempio seguente restituisce un riferimento all'origine. Usa il readonly modificatore per indicare
che i chiamanti non possono modificare l'origine:

private static readonly SamplePoint origin = new SamplePoint(0, 0, 0);


public static ref readonly SamplePoint Origin => ref origin;

Il tipo restituito può non essere readonly struct . Qualsiasi tipo che può essere restituito da ref può essere
restituito anche da ref readonly .

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
È anche possibile visualizzare le proposte specifiche del linguaggio:
struct Ref e ReadOnly struct
membri struct di sola lettura

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
const
Fields
sealed (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Quando applicato a una classe, il modificatore sealed impedisce che altre classi ereditino da esso. Nell'esempio
seguente, la classe B eredita dalla classe A , ma nessuna classe può ereditare dalla classe B .

class A {}
sealed class B : A {}

È anche possibile usare il modificatore sealed su un metodo o su una proprietà che esegue l'override di un
metodo o di una proprietà virtuale in una classe di base. In questo modo è possibile consentire alle classi di
derivare dalla classe e impedire l'override di proprietà o metodi virtuali specifici.

Esempio
Nell'esempio seguente, Z eredita da Y ma Z non può eseguire l'override della funzione virtuale F
dichiarata in X e bloccata in Y .

class X
{
protected virtual void F() { Console.WriteLine("X.F"); }
protected virtual void F2() { Console.WriteLine("X.F2"); }
}

class Y : X
{
sealed protected override void F() { Console.WriteLine("Y.F"); }
protected override void F2() { Console.WriteLine("Y.F2"); }
}

class Z : Y
{
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }

// Overriding F2 is allowed.
protected override void F2() { Console.WriteLine("Z.F2"); }
}

Quando si definiscono nuovi metodi o proprietà in una classe, è possibile impedire alle classi derivate di
eseguire l'override non dichiarandole come virtuali.
È un errore usare il modificatore abstract con una classe sealed, poiché la classe astratta deve essere ereditata da
una classe che fornisce un'implementazione dei metodi o delle proprietà astratti.
Quando applicato a un metodo o a una proprietà, il modificatore sealed deve sempre essere usato con
override.
Poiché gli struct sono sealed in modo implicito, non possono essere ereditati.
Per altre informazioni, vedere Ereditarietà.
Per altri esempi, vedere Classi e membri delle classi astratte e sealed.
Esempio
sealed class SealedClass
{
public int x;
public int y;
}

class SealedTest2
{
static void Main()
{
var sc = new SealedClass();
sc.x = 110;
sc.y = 150;
Console.WriteLine($"x = {sc.x}, y = {sc.y}");
}
}
// Output: x = 110, y = 150

Nell'esempio precedente è possibile ereditare dalla classe sealed tramite l'istruzione seguente:
class MyDerivedC: SealedClass {} // Error

Il risultato è un messaggio di errore:


'MyDerivedC': cannot derive from sealed type 'SealedClass'

Commenti
Per determinare se bloccare una classe, proprietà o metodo, è consigliabile in genere considerare i due punti
seguenti:
I potenziali vantaggi di cui le classi derivate possono usufruire grazie alla possibilità di personalizzare la
classe.
La possibilità che le classi derivate possono modificare le classi in uso in modo tale che non funzionino
più correttamente o come previsto.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Classi statiche e membri di classi statiche
Classi e membri delle classi astratte e sealed
Modificatori di accesso
Modificatori
override
virtuale
static (Riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

In questa pagina viene illustrata la static parola chiave Modifier. La static parola chiave fa anche parte della
using static direttiva.

Usare il modificatore static per dichiarare un membro statico, che appartiene allo stesso tipo invece che a un
oggetto specifico. Il static modificatore può essere usato per dichiarare static le classi. In classi, interfacce e
struct è possibile aggiungere il static modificatore a campi, metodi, proprietà, operatori, eventi e costruttori. Il
static modificatore non può essere usato con indicizzatori o finalizzatori. Per altre informazioni, vedere classi
statiche e membri di classi statiche.
A partire da C# 8,0, è possibile aggiungere il static modificatore a una funzione locale. Una funzione locale
statica non può acquisire le variabili locali o lo stato dell'istanza.
A partire da C# 9,0, è possibile aggiungere il static modificatore a un' espressione lambda o a un metodo
anonimo. Un metodo lambda statico o anonimo non può acquisire le variabili locali o lo stato dell'istanza.

Esempio-classe statica
La classe seguente viene dichiarata come static e contiene solo metodi static :

static class CompanyEmployee


{
public static void DoSomething() { /*...*/ }
public static void DoSomethingElse() { /*...*/ }
}

Una costante o una dichiarazione di tipo è implicitamente un static membro. static Non è possibile fare
riferimento A un membro tramite un'istanza. Al contrario, viene fatto riferimento tramite il nome del tipo. Si
consideri ad esempio la classe seguente:

public class MyBaseC


{
public struct MyStruct
{
public static int x = 100;
}
}

Per fare riferimento al static membro x , usare il nome completo, MyBaseC.MyStruct.x , a meno che il
membro non sia accessibile dallo stesso ambito:

Console.WriteLine(MyBaseC.MyStruct.x);

Mentre un'istanza di una classe contiene una copia separata di tutti i campi di istanza della classe, è presente una
sola copia di ogni static campo.
Non è possibile usare this per fare riferimento a static metodi o funzioni di accesso alle proprietà.
Se la static parola chiave viene applicata a una classe, tutti i membri della classe devono essere static .
Classi, interfacce e static classi possono avere static costruttori. Un static costruttore viene chiamato in
un determinato punto tra l'avvio del programma e la creazione di un'istanza della classe.

NOTE
La parola chiave static ha un uso più limitato rispetto a C++. Per un confronto con la parola chiave di C++, vedere
Classi di archiviazione (C++).

Per illustrare static i membri, si consideri una classe che rappresenta un dipendente della società. Si supponga
che la classe contenga un metodo di conteggio dei dipendenti e un campo per memorizzare il numero dei
dipendenti. Sia il metodo che il campo non appartengono ad alcuna istanza di un dipendente. Appartengono
invece alla classe di dipendenti nel suo complesso. Devono essere dichiarati come static membri della classe.

Esempio-campo statico e metodo


Questo esempio legge il nome e l'ID di un nuovo dipendente, il contatore dipendente viene incrementato di uno
e vengono visualizzate le informazioni per il nuovo dipendente e il nuovo numero di dipendenti. Questo
programma legge il numero corrente di dipendenti dalla tastiera.
public class Employee4
{
public string id;
public string name;

public Employee4()
{
}

public Employee4(string name, string id)


{
this.name = name;
this.id = id;
}

public static int employeeCounter;

public static int AddEmployee()


{
return ++employeeCounter;
}
}

class MainClass : Employee4


{
static void Main()
{
Console.Write("Enter the employee's name: ");
string name = Console.ReadLine();
Console.Write("Enter the employee's ID: ");
string id = Console.ReadLine();

// Create and configure the employee object.


Employee4 e = new Employee4(name, id);
Console.Write("Enter the current number of employees: ");
string n = Console.ReadLine();
Employee4.employeeCounter = Int32.Parse(n);
Employee4.AddEmployee();

// Display the new information.


Console.WriteLine($"Name: {e.name}");
Console.WriteLine($"ID: {e.id}");
Console.WriteLine($"New Number of Employees: {Employee4.employeeCounter}");
}
}
/*
Input:
Matthias Berndt
AF643G
15
*
Sample Output:
Enter the employee's name: Matthias Berndt
Enter the employee's ID: AF643G
Enter the current number of employees: 15
Name: Matthias Berndt
ID: AF643G
New Number of Employees: 16
*/

Esempio-inizializzazione statica
Questo esempio mostra che è possibile inizializzare un static campo usando un altro static campo non
ancora dichiarato. I risultati non saranno definiti fino a quando non si assegna in modo esplicito un valore al
static campo.
class Test
{
static int x = y;
static int y = 5;

static void Main()


{
Console.WriteLine(Test.x);
Console.WriteLine(Test.y);

Test.x = 99;
Console.WriteLine(Test.x);
}
}
/*
Output:
0
5
99
*/

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
uso della direttiva statica
Classi statiche e membri di classi statiche
unsafe (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave unsafe denota un contesto unsafe, necessario per qualsiasi operazione che interessa i
puntatori. Per ulteriori informazioni, vedere codice unsafe e puntatori.
È possibile usare il modificatore unsafe nella dichiarazione di un tipo o di un membro. L'intera estensione
testuale del tipo o membro viene pertanto considerato come contesto unsafe. Ad esempio, il seguente è un
metodo dichiarato con il modificatore unsafe :

unsafe static void FastCopy(byte[] src, byte[] dst, int count)


{
// Unsafe context: can use pointers here.
}

L'ambito del contesto unsafe si estende dall'elenco di parametri alla fine del metodo, in modo tale che i
puntatori possano essere usati anche nell'elenco dei parametri:

unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}

È anche possibile usare un blocco unsafe per consentire l'uso di un codice unsafe all'interno del blocco. Ad
esempio:

unsafe
{
// Unsafe context: can use pointers here.
}

Per compilare codice unsafe, è necessario specificare l' -unsafe opzione del compilatore. Il codice unsafe non è
verificabile da Common Language Runtime.

Esempio
// compile with: -unsafe
class UnsafeTest
{
// Unsafe method: takes pointer to int.
unsafe static void SquarePtrParam(int* p)
{
*p *= *p;
}

unsafe static void Main()


{
int i = 5;
// Unsafe method: uses address-of operator (&).
SquarePtrParam(&i);
Console.WriteLine(i);
}
}
// Output: 25
Specifiche del linguaggio C#
Per altre informazioni, vedere Codice di tipo unsafe nella specifica del linguaggio C#. La specifica del linguaggio
costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzione fixed
Codice unsafe e puntatori
Buffer a dimensione fissa
virtual (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

La parola chiave virtual viene usata per modificare una dichiarazione di metodo, proprietà, indicizzatore o
evento e consentire che sia sottoposta a override in una classe derivata. Ad esempio, questo metodo può essere
sottoposto a override da qualsiasi classe che lo eredita:

public virtual double Area()


{
return x * y;
}

L'implementazione di un membro virtuale può essere modificata da un membro di sostituzione di una classe
derivata. Per altre informazioni su come usare la virtual parola chiave, vedere controllo delle versioni con le
parole chiave override e New e sapere quando usare le parole chiave override e New.

Commenti
Quando viene richiamato un metodo virtuale, il tipo di runtime dell'oggetto viene controllato per verificare la
presenza di un membro di sostituzione. Viene chiamato il membro di sostituzione nella classe più derivata e
potrebbe trattarsi del membro originale, se nessuna classe derivata ha eseguito l'override del membro.
Per impostazione predefinita, i metodi sono non virtuali. Non è possibile eseguire l'override di un metodo non
virtuale.
Non è possibile usare il modificatore virtual con i modificatori static , abstract , private o override .
L'esempio seguente illustra una proprietà virtuale:
class MyBaseClass
{
// virtual auto-implemented property. Overrides can only
// provide specialized behavior if they implement get and set accessors.
public virtual string Name { get; set; }

// ordinary virtual property with backing field


private int num;
public virtual int Number
{
get { return num; }
set { num = value; }
}
}

class MyDerivedClass : MyBaseClass


{
private string name;

// Override auto-implemented property with ordinary property


// to provide specialized accessor behavior.
public override string Name
{
get
{
return name;
}
set
{
if (!string.IsNullOrEmpty(value))
{
name = value;
}
else
{
name = "Unknown";
}
}
}
}

Le proprietà virtuali si comportano come i metodi virtuali, ad eccezione delle differenze nella sintassi della
dichiarazione e della chiamata.
Non è possibile usare il modificatore virtual su una proprietà static.
Una proprietà virtuale ereditata può essere sottoposta a override in una classe derivata includendo una
dichiarazione di proprietà che usa il modificatore override .

Esempio
In questo esempio la classe Shape contiene le due coordinate x , y e il metodo virtuale Area() . Le classi di
forma diversa, ad esempio Circle , Cylinder e Sphere , ereditano la classe Shape e la superficie viene calcolata
per ogni figura. Ogni classe derivata ha la propria implementazione di override di Area() .
Si noti che le classi ereditate Circle , Sphere e Cylinder usano tutte costruttori che inizializzano la classe di
base, come illustrato nella seguente dichiarazione.

public Cylinder(double r, double h): base(r, h) {}

Il programma seguente calcola e visualizza l'area appropriata per ogni figura richiamando l'implementazione
corretta del metodo Area() in base all'oggetto associato al metodo.

class TestClass
{
public class Shape
{
public const double PI = Math.PI;
protected double x, y;

public Shape()
{
}

public Shape(double x, double y)


{
this.x = x;
this.y = y;
}

public virtual double Area()


{
return x * y;
}
}

public class Circle : Shape


{
public Circle(double r) : base(r, 0)
{
}

public override double Area()


{
return PI * x * x;
}
}

class Sphere : Shape


{
public Sphere(double r) : base(r, 0)
{
}

public override double Area()


{
return 4 * PI * x * x;
}
}

class Cylinder : Shape


{
public Cylinder(double r, double h) : base(r, h)
{
}

public override double Area()


{
return 2 * PI * x * x + 2 * PI * x * y;
}
}

static void Main()


{
double r = 3.0, h = 5.0;
Shape c = new Circle(r);
Shape s = new Sphere(r);
Shape l = new Cylinder(r, h);
// Display results.
// Display results.
Console.WriteLine("Area of Circle = {0:F2}", c.Area());
Console.WriteLine("Area of Sphere = {0:F2}", s.Area());
Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
}
}
/*
Output:
Area of Circle = 28.27
Area of Sphere = 113.10
Area of Cylinder = 150.80
*/

Specifiche del linguaggio C#


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

Vedere anche
Polimorfismo
astratta
override
new (modifier)
volatile (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

La parola chiave volatile indica che un campo potrebbe essere modificato da più thread eseguiti
contemporaneamente. Il compilatore, il sistema di runtime e anche l'hardware possono riordinare le letture e le
scritture in posizioni di memoria per motivi di prestazioni. I campi dichiarati volatile non sono soggetti a
queste ottimizzazioni. L'aggiunta del modificatore volatile garantisce che tutti thread osserveranno le scritture
volatili eseguite da qualsiasi altro thread nell'ordine in cui sono state eseguite. Non c'è garanzia di un singolo
ordinamento totale delle scritture volatili come osservato da tutti i thread di esecuzione.
La parola chiave volatile può essere applicata ai campi di questi tipi:
Tipi di riferimento.
Tipi di puntatore (in un contesto non sicuro). Si noti che sebbene il puntatore in sé possa essere volatile, non
può esserlo l'oggetto a cui punta. In altre parole, non è possibile dichiarare un "puntatore a volatile".
Tipi semplici come sbyte , byte , short , ushort , int , uint , char , float e bool .
Tipo enum con uno di questi tipi di base: byte , sbyte , short , ushort , int o uint .
Parametri di tipo generico noti come tipi di riferimento.
IntPtr e UIntPtr.
Altri tipi, inclusi double e long , non possono essere contrassegnati come volatile , perché non è possibile
garantire che le letture e le scritture in campi di questi tipi siano atomiche. Per proteggere l'accesso multithread
a questi tipi di campi, usare i membri della Interlocked classe o proteggere l'accesso mediante l' lock
istruzione.
La parola chiave volatile può essere applicata solo a campi di un oggetto class o struct . Le variabili locali
non possono essere dichiarate volatile .

Esempio
Nell'esempio riportato di seguito viene illustrato come dichiarare volatile una variabile di campo pubblico.

class VolatileTest
{
public volatile int sharedStorage;

public void Test(int _i)


{
sharedStorage = _i;
}
}

Nell'esempio seguente viene illustrato come un thread di lavoro o ausiliario può essere creato e usato per
eseguire l'elaborazione in parallelo con quella del thread principale. Per altre informazioni sul multithreading,
vedere Threading gestito.
public class Worker
{
// This method is called when the thread is started.
public void DoWork()
{
bool work = false;
while (!_shouldStop)
{
work = !work; // simulate some work
}
Console.WriteLine("Worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Keyword volatile is used as a hint to the compiler that this data
// member is accessed by multiple threads.
private volatile bool _shouldStop;
}

public class WorkerThreadExample


{
public static void Main()
{
// Create the worker thread object. This does not start the thread.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);

// Start the worker thread.


workerThread.Start();
Console.WriteLine("Main thread: starting worker thread...");

// Loop until the worker thread activates.


while (!workerThread.IsAlive)
;

// Put the main thread to sleep for 500 milliseconds to


// allow the worker thread to do some work.
Thread.Sleep(500);

// Request that the worker thread stop itself.


workerObject.RequestStop();

// Use the Thread.Join method to block the current thread


// until the object's thread terminates.
workerThread.Join();
Console.WriteLine("Main thread: worker thread has terminated.");
}
// Sample output:
// Main thread: starting worker thread...
// Worker thread: terminating gracefully.
// Main thread: worker thread has terminated.
}

Dopo aver aggiunto il modificatore volatile alla dichiarazione di _shouldStop , si otterranno sempre gli stessi
risultati, analogamente all'estratto mostrato nel codice precedente. Tuttavia, senza il modificatore nel membro
_shouldStop , il comportamento è imprevedibile. Il metodo DoWork può ottimizzare l'accesso ai membri,
causando la lettura dei dati non aggiornati. A causa della natura della programmazione multithread, il numero di
letture non aggiornate è imprevedibile. Esecuzioni diverse del programma produrranno risultati leggermente
diversi.

Specifiche del linguaggio C#


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

Vedere anche
Specifica del linguaggio C#: parola chiave volatile
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatori
istruzione lock
Interlocked
Parole chiave per le istruzioni (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Le istruzioni sono istruzioni per i programmi. Tranne che nei casi descritti negli argomenti a cui viene fatto
riferimento nella tabella seguente, le istruzioni sono eseguite in sequenza. Nella tabella seguente sono elencate
le parole chiave per le istruzioni C#. Per altre informazioni sulle istruzioni che non sono espresse con alcuna
parola chiave, vedere Istruzioni.

C AT EGO RY PA RO L E C H IAVE DI C #

Istruzioni di selezione if, else, switch, case

Istruzioni di iterazione do, for, foreach, in, while

Istruzioni di salto break, continue, default, goto, return, yield

Istruzioni di gestione delle eccezioni throw, try-catch, try-finally, try-catch-finally

Checked e unchecked checked, unchecked

istruzione fixed fissa

istruzione lock blocco

Vedere anche
Riferimenti per C#
Istruzioni
Parole chiave di C#
if-else (Riferimenti per C#)
02/11/2020 • 7 minutes to read • Edit Online

Un'istruzione if identifica quale istruzione eseguire in base al valore di un'espressione booleana. Nell'esempio
seguente la variabile bool``condition viene impostata su true e quindi archiviata nell'istruzione if . L'output
è The variable is set to true. .

bool condition = true;

if (condition)
{
Console.WriteLine("The variable is set to true.");
}
else
{
Console.WriteLine("The variable is set to false.");
}

È possibile eseguire gli esempi di questo argomento posizionandoli nel metodo Main di un'app console.
Un'istruzione if in C# può essere usata in due modi, come illustrato nell'esempio seguente.

// if-else statement
if (condition)
{
then-statement;
}
else
{
else-statement;
}
// Next statement in the program.

// if statement without an else


if (condition)
{
then-statement;
}
// Next statement in the program.

In un'istruzione if-else se condition restituisce true, viene eseguito then-statement . Se condition è false,
viene eseguito else-statement . Poiché condition non può essere contemporaneamente true e false,
then-statement e else-statement di un'istruzione if-else non possono mai essere eseguiti insieme. Dopo
l'esecuzione di then-statement o else-statement il controllo viene trasferito all'istruzione successiva dopo
l'istruzione if .
In un'istruzione if che non include un'istruzione else , se condition è true, viene eseguito then-statement .
Se condition è false, il controllo viene trasferito all'istruzione successiva dopo l'istruzione if .
then-statement e else-statement possono essere entrambi costituiti da una singola istruzione o da più
istruzioni racchiuse tra parentesi graffe ( {} ). Per una singola istruzione le parentesi graffe sono facoltative ma
consigliate.
L'istruzione o le istruzioni in then-statement e else-statement possono essere di qualsiasi tipo, compresa
un'altra istruzione if annidata all'interno dell'istruzione if originale. Nelle istruzioni if annidate ogni
clausola else appartiene all'ultima istruzione if che non ha un'istruzione else corrispondente. Nell'esempio
seguente viene visualizzato Result1 se m > 10 e n > 20 restituiscono entrambi true. Se m > 10 è true ma
n > 20 è false, viene visualizzato Result2 .

// Try with m = 12 and then with m = 8.


int m = 12;
int n = 18;

if (m > 10)
if (n > 20)
{
Console.WriteLine("Result1");
}
else
{
Console.WriteLine("Result2");
}

Se invece si vuole che venga visualizzato Result2 quando (m > 10) è false, è possibile specificare tale
associazione usando le parentesi graffe per stabilire l'inizio e la fine dell'istruzione if annidata, come illustrato
nell'esempio seguente.

// Try with m = 12 and then with m = 8.


if (m > 10)
{
if (n > 20)
Console.WriteLine("Result1");
}
else
{
Console.WriteLine("Result2");
}

Result2 viene visualizzato se la condizione (m > 10) restituisce false.

Esempio
Nell'esempio seguente si immette un carattere dalla tastiera e il programma usa un'istruzione if annidata per
determinare se il carattere di input è un carattere alfabetico. Se il carattere di input è un carattere alfabetico, il
programma verifica se il carattere di input è maiuscolo o minuscolo. Viene visualizzato un messaggio per ogni
situazione.
Console.Write("Enter a character: ");
char c = (char)Console.Read();
if (Char.IsLetter(c))
{
if (Char.IsLower(c))
{
Console.WriteLine("The character is lowercase.");
}
else
{
Console.WriteLine("The character is uppercase.");
}
}
else
{
Console.WriteLine("The character isn't an alphabetic character.");
}

//Sample Output:

//Enter a character: 2
//The character isn't an alphabetic character.

//Enter a character: A
//The character is uppercase.

//Enter a character: h
//The character is lowercase.

Esempio
È anche possibile annidare un' if istruzione all'interno di un blocco Else, come illustrato nel codice parziale
seguente. Nell'esempio le istruzioni if vengono annidate all'interno di due blocchi else e di un blocco then. I
commenti specificano che le condizioni sono true o false in ogni blocco.
// Change the values of these variables to test the results.
bool Condition1 = true;
bool Condition2 = true;
bool Condition3 = true;
bool Condition4 = true;

if (Condition1)
{
// Condition1 is true.
}
else if (Condition2)
{
// Condition1 is false and Condition2 is true.
}
else if (Condition3)
{
if (Condition4)
{
// Condition1 and Condition2 are false. Condition3 and Condition4 are true.
}
else
{
// Condition1, Condition2, and Condition4 are false. Condition3 is true.
}
}
else
{
// Condition1, Condition2, and Condition3 are false.
}

Esempio
L'esempio seguente determina se un carattere di input è una lettera minuscola, una lettera maiuscola o un
numero. Se tutte e tre le condizioni sono false, il carattere non è un carattere alfanumerico. Nell'esempio viene
visualizzato un messaggio per ogni situazione.
Console.Write("Enter a character: ");
char ch = (char)Console.Read();

if (Char.IsUpper(ch))
{
Console.WriteLine("The character is an uppercase letter.");
}
else if (Char.IsLower(ch))
{
Console.WriteLine("The character is a lowercase letter.");
}
else if (Char.IsDigit(ch))
{
Console.WriteLine("The character is a number.");
}
else
{
Console.WriteLine("The character is not alphanumeric.");
}

//Sample Input and Output:


//Enter a character: E
//The character is an uppercase letter.

//Enter a character: e
//The character is a lowercase letter.

//Enter a character: 4
//The character is a number.

//Enter a character: =
//The character is not alphanumeric.

Come per un'istruzione nel blocco else o nel blocco then è possibile usare qualsiasi istruzione valida, allo stesso
modo per la condizione è possibile usare qualsiasi espressione booleana valida. È possibile utilizzare operatori
logici come ! , && , || , & , | e ^ per creare condizioni composte. Il codice seguente illustra alcuni esempi.
// NOT
bool result = true;
if (!result)
{
Console.WriteLine("The condition is true (result is false).");
}
else
{
Console.WriteLine("The condition is false (result is true).");
}

// Short-circuit AND
int m = 9;
int n = 7;
int p = 5;
if (m >= n && m >= p)
{
Console.WriteLine("Nothing is larger than m.");
}

// AND and NOT


if (m >= n && !(p > m))
{
Console.WriteLine("Nothing is larger than m.");
}

// Short-circuit OR
if (m > n || m > p)
{
Console.WriteLine("m isn't the smallest.");
}

// NOT and OR
m = 4;
if (!(m >= n || m >= p))
{
Console.WriteLine("Now m is the smallest.");
}
// Output:
// The condition is false (result is true).
// Nothing is larger than m.
// Nothing is larger than m.
// m isn't the smallest.
// Now m is the smallest.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Operatore?:
Istruzione if-else (C++)
switch
switch (Riferimenti per C#)
02/11/2020 • 25 minutes to read • Edit Online

In questo articolo viene illustrata l' switch istruzione. Per informazioni sull' switch espressione (introdotta in
C# 8,0), vedere l'articolo sulle switch espressioni nella sezione espressioni e operatori .
switch è un'istruzione di selezione che sceglie un'unica sezione di commutazione da eseguire da un elenco di
candidati in base a una corrispondenza di criteri con l' espressione di corrispondenza.

using System;

public class Example


{
public static void Main()
{
int caseSwitch = 1;

switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
Console.WriteLine("Case 2");
break;
default:
Console.WriteLine("Default case");
break;
}
}
}
// The example displays the following output:
// Case 1

L'istruzione switch viene spesso usata come alternativa a un costrutto if-else se una singola espressione viene
testata in base a tre o più condizioni. Ad esempio, l'istruzione switch determina se una variabile di tipo Color
ha uno dei tre valori:
using System;

public enum Color { Red, Green, Blue }

public class Example


{
public static void Main()
{
Color c = (Color) (new Random()).Next(0, 3);
switch (c)
{
case Color.Red:
Console.WriteLine("The color is red");
break;
case Color.Green:
Console.WriteLine("The color is green");
break;
case Color.Blue:
Console.WriteLine("The color is blue");
break;
default:
Console.WriteLine("The color is unknown.");
break;
}
}
}

È equivalente all'esempio seguente che usa un costrutto if - else .

using System;

public enum Color { Red, Green, Blue }

public class Example


{
public static void Main()
{
Color c = (Color) (new Random()).Next(0, 3);
if (c == Color.Red)
Console.WriteLine("The color is red");
else if (c == Color.Green)
Console.WriteLine("The color is green");
else if (c == Color.Blue)
Console.WriteLine("The color is blue");
else
Console.WriteLine("The color is unknown.");
}
}
// The example displays the following output:
// The color is red

Espressione di ricerca
L'espressione di ricerca fornisce il valore da confrontare con i modelli nelle etichette case . La relativa sintassi è
la seguente:

switch (expr)

In C# 6 e versioni precedenti l'espressione di ricerca deve essere un'espressione che restituisce un valore dei tipi
seguenti:
un char.
stringa.
un bool.
valore integrale , ad esempio int o long .
valore enum .
A partire da C# 7.0, l'espressione di ricerca può essere qualsiasi espressione non null.

Sezione opzioni
Un'istruzione switch include una o più sezioni opzioni. Ogni sezione switch contiene una o più etichette case ,
ovvero un case o un'etichetta predefinita, seguite da una o più istruzioni. L'istruzione switch può includere al
massimo un'etichetta default posizionata in qualsiasi sezione switch. Nell'esempio seguente viene illustrata una
semplice istruzione switch con tre sezioni switch, contenenti ognuna due istruzioni. La seconda sezione switch
contiene le etichette case 2: e case 3: .
Un'istruzione switch può contenere qualsiasi numero di sezioni opzioni e ogni sezione può avere una o più
etichette case, come illustrato nell'esempio seguente. Tuttavia, due etichette case non possono contenere la
stessa espressione.

using System;

public class Example


{
public static void Main()
{
Random rnd = new Random();
int caseSwitch = rnd.Next(1,4);

switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
case 3:
Console.WriteLine($"Case {caseSwitch}");
break;
default:
Console.WriteLine($"An unexpected value ({caseSwitch})");
break;
}
}
}
// The example displays output like the following:
// Case 1

Viene eseguita una sola sezione opzioni in un'istruzione switch. C# non consente di continuare l'esecuzione da
una sezione opzioni a quella successiva. Per questo motivo, il codice seguente genera un errore di compilazione
CS0163: "Il controllo non può passare da un'etichetta case ("<case label>") a un'altra".
switch (caseSwitch)
{
// The following switch section causes an error.
case 1:
Console.WriteLine("Case 1...");
// Add a break or other jump statement here.
case 2:
Console.WriteLine("... and/or Case 2");
break;
}

Questo requisito viene generalmente soddisfatto chiudendo in modo esplicito la sezione opzioni tramite
un'istruzione break, goto o return. Tuttavia, anche il codice seguente è valido, perché assicura che il controllo del
programma non possa passare alla sezione opzioni default .

switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1...");
break;
case 2:
case 3:
Console.WriteLine("... and/or Case 2");
break;
case 4:
while (true)
Console.WriteLine("Endless looping. . . .");
default:
Console.WriteLine("Default value...");
break;
}

L'esecuzione dell'elenco di istruzioni nella sezione opzioni con un'etichetta case che corrisponde all'espressione
di ricerca inizia con la prima istruzione e continua con l'elenco di istruzioni, in genere fino a raggiungere
un'istruzione di salto, ad esempio break , goto case , goto label , return o throw . A quel punto, il controllo
viene trasferito al di fuori dell'istruzione switch o in un'altra etichetta case. Un'istruzione goto , se usata, deve
trasferire il controllo a un'etichetta costante. Questa restrizione è necessaria, perché il tentativo di trasferire il
controllo a un'etichetta non costante può avere effetti collaterali indesiderati, come il trasferimento del controllo
a una posizione non prevista nel codice o la creazione di un ciclo infinito.

Etichette case
Ogni etichetta case specifica un criterio da confrontare con l'espressione di ricerca (la variabile caseSwitch negli
esempi precedenti). Se corrispondono, il controllo viene trasferito alla sezione opzioni che contiene la prima
etichetta case corrispondente. Se nessun criterio di etichetta case corrisponde all'espressione di ricerca, il
controllo viene trasferito alla sezione con etichetta case default , se presente. Se non è presente alcun case
default , non vengono eseguite istruzioni in nessuna sezione opzioni e il controllo viene trasferito al di fuori
dell'istruzione switch .
Per informazioni sull'istruzione switch e sui criteri di ricerca, vedere Criteri di ricerca con la sezione switch
istruzione.
Poiché C# 6 supporta solo il criterio costante e non consente la ripetizione di valori costanti, le etichette case
definiscono valori che si escludono a vicenda e solo un criterio può corrispondere all'espressione di ricerca. Di
conseguenza, l'ordine in cui vengono visualizzate le istruzioni case non è rilevante.
In C# 7.0, tuttavia, poiché sono supportati altri criteri, le etichette case non devono necessariamente definire
valori che si escludono a vicenda e più criteri possono corrispondere all'espressione di ricerca. Dal momento che
vengono eseguite solo le istruzioni nella prima sezione opzione che contiene il criterio di corrispondenza,
l'ordine in cui ora vengono visualizzate le istruzioni case è importante. Se C# rileva una sezione opzioni la cui
istruzione o istruzioni case sono equivalenti a o sono subset di istruzioni precedenti, viene generato l'errore del
compilatore CS8120: "Lo switch case è già stato gestito da un case precedente."
Nell'esempio seguente viene illustrata un'istruzione switch che usa un'ampia gamma di criteri che non si
escludono a vicenda. Se si sposta la sezione opzioni case 0: in modo che non sia più la prima sezione
nell'istruzione switch , C# genera un errore del compilatore perché un numero intero il cui valore è uguale a
zero è un sottoinsieme di tutti i numeri interi, che corrisponde al criterio definito dall'istruzione case int val .

using System;
using System.Collections.Generic;
using System.Linq;

public class Example


{
public static void Main()
{
var values = new List<object>();
for (int ctr = 0; ctr <= 7; ctr++) {
if (ctr == 2)
values.Add(DiceLibrary.Roll2());
else if (ctr == 4)
values.Add(DiceLibrary.Pass());
else
values.Add(DiceLibrary.Roll());
}

Console.WriteLine($"The sum of { values.Count } die is { DiceLibrary.DiceSum(values) }");


}
}

public static class DiceLibrary


{
// Random number generator to simulate dice rolls.
static Random rnd = new Random();

// Roll a single die.


public static int Roll()
{
return rnd.Next(1, 7);
}

// Roll two dice.


public static List<object> Roll2()
{
var rolls = new List<object>();
rolls.Add(Roll());
rolls.Add(Roll());
return rolls;
}

// Calculate the sum of n dice rolls.


public static int DiceSum(IEnumerable<object> values)
{
var sum = 0;
foreach (var item in values)
{
switch (item)
{
// A single zero value.
case 0:
break;
// A single value.
case int val:
sum += val;
sum += val;
break;
// A non-empty collection.
case IEnumerable<object> subList when subList.Any():
sum += DiceSum(subList);
break;
// An empty collection.
case IEnumerable<object> subList:
break;
// A null reference.
case null:
break;
// A value that is neither an integer nor a collection.
default:
throw new InvalidOperationException("unknown item type");
}
}
return sum;
}

public static object Pass()


{
if (rnd.Next(0, 2) == 0)
return null;
else
return new List<object>();
}
}

È possibile correggere il problema ed eliminare l'avviso del compilatore in uno dei due modi seguenti:
Modificando l'ordine delle sezioni opzioni.
Usando una clausola when nell'etichetta case .

Case default
Il case default specifica la sezione opzioni da eseguire se l'espressione di ricerca non corrisponde a
nessun'altra etichetta case . Se non è presente un case default e l'espressione di ricerca non corrisponde a
nessun'altra etichetta case , il flusso del programma salta l'istruzione switch .
Il case default può essere visualizzato in qualsiasi ordine nell'istruzione switch . Indipendentemente
dall'ordine nel codice sorgente, viene sempre valutato per ultimo, dopo la valutazione di tutte le etichette case .

Criteri di ricerca con istruzione switch


Ogni istruzione case definisce un criterio che, in caso di corrispondenza con l'espressione di ricerca, provoca
l'esecuzione della sezione opzioni che la contiene. Tutte le versioni di C# supportano il criterio costante. I criteri
rimanenti sono supportati a partire da C# 7.0.
Criterio costante
Il criterio costante verifica se un'espressione di ricerca è uguale a una costante specificata. La relativa sintassi è la
seguente:

case constant:

dove costant è il valore su cui eseguire il test. constant può essere una delle espressioni costanti seguenti:
Valore letterale bool : true o false .
Qualsiasi costante integrale , ad esempio un oggetto int , un oggetto long o un oggetto byte .
Il nome di una variabile const dichiarata.
Una costante di enumerazione.
Un valore letterale char.
Un valore letterale string.
L'espressione costante viene valutata nel modo seguente:
Se expr e constant sono tipi integrali, l'operatore di uguaglianza C# determina se l'espressione restituisce
true (ovvero, se expr == constant ).

In caso contrario, il valore dell'espressione è determinato da una chiamata al metodo Object.Equals(expr,


constant) statico.
Nell'esempio seguente viene usato il modello costante per determinare se una determinata data è un fine
settimana, il primo giorno della settimana lavorativa, l'ultimo giorno della settimana o nel mezzo della settimana
lavorativa. Viene valutata la proprietà DateTime.DayOfWeek del giorno corrente con i membri
dell'enumerazione DayOfWeek.

using System;

class Program
{
static void Main()
{
switch (DateTime.Now.DayOfWeek)
{
case DayOfWeek.Sunday:
case DayOfWeek.Saturday:
Console.WriteLine("The weekend");
break;
case DayOfWeek.Monday:
Console.WriteLine("The first day of the work week.");
break;
case DayOfWeek.Friday:
Console.WriteLine("The last day of the work week.");
break;
default:
Console.WriteLine("The middle of the work week.");
break;
}
}
}
// The example displays output like the following:
// The middle of the work week.

L'esempio seguente usa il modello costante per gestire l'input dell'utente in un'applicazione console che simula
una macchina per il caffè automatica.
using System;

class Example
{
static void Main()
{
Console.WriteLine("Coffee sizes: 1=small 2=medium 3=large");
Console.Write("Please enter your selection: ");
string str = Console.ReadLine();
int cost = 0;

// Because of the goto statements in cases 2 and 3, the base cost of 25


// cents is added to the additional cost for the medium and large sizes.
switch (str)
{
case "1":
case "small":
cost += 25;
break;
case "2":
case "medium":
cost += 25;
goto case "1";
case "3":
case "large":
cost += 50;
goto case "1";
default:
Console.WriteLine("Invalid selection. Please select 1, 2, or 3.");
break;
}
if (cost != 0)
{
Console.WriteLine("Please insert {0} cents.", cost);
}
Console.WriteLine("Thank you for your business.");
}
}
// The example displays output like the following:
// Coffee sizes: 1=small 2=medium 3=large
// Please enter your selection: 2
// Please insert 50 cents.
// Thank you for your business.

Criterio del tipo


Il criterio del tipo consente la conversione e valutazione concise del tipo. Quando si usa con l'espressione
switch per eseguire i criteri di ricerca, verifica se un'espressione può essere convertita in un tipo specificato e,
in tal caso, esegue il cast a una variabile di quel tipo. La relativa sintassi è la seguente:

case type varname

dove type è il nome del tipo in cui il risultato di expr deve essere convertito e varname è l'oggetto in cui il
risultato di exprdeve essere convertito se la ricerca ha esito positivo. Il tipo in fase di compilazione di expr può
essere un parametro di tipo generico, a partire da C# 7.1.
L'espressione case è true se una delle condizioni seguenti è true:
expr è un'istanza dello stesso tipo di type.
expr è un'istanza di un tipo che deriva da type. In altre parole, il risultato di expr può subire l'upcast a
un'istanza di type.
expr ha un tipo in fase di compilazione che è una classe di base di type e expr ha un tipo di runtime che è
type o è derivato da type. Il tipo in fase di compilazione di una variabile è il tipo della variabile come
definito nella relativa dichiarazione del tipo. Il tipo di runtime di una variabile è il tipo dell'istanza che
viene assegnato alla variabile.
expr è un'istanza di un tipo che implementa l'interfaccia type.
Se l'espressione del case è true, varname viene assegnata in modo definitivo e ha l'ambito locale esclusivamente
all'interno della sezione opzioni.
Si noti che null non corrisponde a un tipo. Per associare un null , usare l'etichetta case seguente:

case null:

Nell'esempio seguente viene usato il criterio del tipo per fornire informazioni sui vari generi di tipi di raccolta.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

class Example
{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8, 10 };
ShowCollectionInformation(values);

var names = new List<string>();


names.AddRange(new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" });
ShowCollectionInformation(names);

List<int> numbers = null;


ShowCollectionInformation(numbers);
}

private static void ShowCollectionInformation(object coll)


{
switch (coll)
{
case Array arr:
Console.WriteLine($"An array with {arr.Length} elements.");
break;
case IEnumerable<int> ieInt:
Console.WriteLine($"Average: {ieInt.Average(s => s)}");
break;
case IList list:
Console.WriteLine($"{list.Count} items");
break;
case IEnumerable ie:
string result = "";
foreach (var e in ie)
result += $"{e} ";
Console.WriteLine(result);
break;
case null:
// Do nothing for a null.
break;
default:
Console.WriteLine($"A instance of type {coll.GetType().Name}");
break;
}
}
}
// The example displays the following output:
// An array with 5 elements.
// 4 items

Invece di object , si potrebbe creare un metodo generico usando il tipo della raccolta come parametro di tipo,
come illustrato nel codice seguente:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

class Example
{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8, 10 };
ShowCollectionInformation(values);

var names = new List<string>();


names.AddRange(new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" });
ShowCollectionInformation(names);

List<int> numbers = null;


ShowCollectionInformation(numbers);
}

private static void ShowCollectionInformation<T>(T coll)


{
switch (coll)
{
case Array arr:
Console.WriteLine($"An array with {arr.Length} elements.");
break;
case IEnumerable<int> ieInt:
Console.WriteLine($"Average: {ieInt.Average(s => s)}");
break;
case IList list:
Console.WriteLine($"{list.Count} items");
break;
case IEnumerable ie:
string result = "";
foreach (var e in ie)
result += $"{e} ";
Console.WriteLine(result);
break;
case object o:
Console.WriteLine($"A instance of type {o.GetType().Name}");
break;
default:
Console.WriteLine("Null passed to this method.");
break;
}
}
}
// The example displays the following output:
// An array with 5 elements.
// 4 items
// Null passed to this method.

La versione generica presenta due differenze rispetto al primo esempio. La prima è che non è possibile usare il
case null . Non è possibile usare un case costante perché il compilatore non è in grado di convertire un tipo
arbitrario T in alcun tipo diverso da object . Ciò che prima era il case default ora esegue il test per un
object non Null. Questo significa che il case default esegue il test solo per null .

Senza criteri di ricerca, questo codice potrebbe essere scritto come segue. L'uso di criteri di ricerca del tipo
produce codice più compatto e leggibile eliminando la necessità di verificare se il risultato di una conversione è
un null o eseguire cast ripetuti.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

class Example
{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8, 10 };
ShowCollectionInformation(values);

var names = new List<string>();


names.AddRange(new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" });
ShowCollectionInformation(names);

List<int> numbers = null;


ShowCollectionInformation(numbers);
}

private static void ShowCollectionInformation(object coll)


{
if (coll is Array)
{
Array arr = (Array) coll;
Console.WriteLine($"An array with {arr.Length} elements.");
}
else if (coll is IEnumerable<int>)
{
IEnumerable<int> ieInt = (IEnumerable<int>) coll;
Console.WriteLine($"Average: {ieInt.Average(s => s)}");
}
else if (coll is IList)
{
IList list = (IList) coll;
Console.WriteLine($"{list.Count} items");
}
else if (coll is IEnumerable)
{
IEnumerable ie = (IEnumerable) coll;
string result = "";
foreach (var e in ie)
result += $"{e} ";
Console.WriteLine(result);
}
else if (coll == null)
{
// Do nothing.
}
else
{
Console.WriteLine($"An instance of type {coll.GetType().Name}");
}
}
}
// The example displays the following output:
// An array with 5 elements.
// 4 items

L'istruzione case e la clausola when


A partire da C# 7.0, poiché le istruzioni case non devono escludersi a vicenda, è possibile aggiungere una
clausola when per specificare una condizione aggiuntiva che deve essere soddisfatta perché l'istruzione case
restituisca true. La clausola when può essere qualsiasi espressione che restituisce un valore booleano.
Nell'esempio seguente viene definita una classe Shape di base, una classe Rectangle che deriva da Shape e
una classe Square che deriva da Rectangle . L'esempio usa la clausola when per assicurarsi che ShowShapeInfo
consideri un oggetto Rectangle a cui è stata assegnata pari lunghezza e larghezza di un elemento Square ,
anche nel caso in cui non sia stata creata un'istanza come oggetto Square . Il metodo non tenta di visualizzare
informazioni su un oggetto null o su una forma la cui area è pari a zero.

using System;

public abstract class Shape


{
public abstract double Area { get; }
public abstract double Circumference { get; }
}

public class Rectangle : Shape


{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}

public double Length { get; set; }


public double Width { get; set; }

public override double Area


{
get { return Math.Round(Length * Width,2); }
}

public override double Circumference


{
get { return (Length + Width) * 2; }
}
}

public class Square : Rectangle


{
public Square(double side) : base(side, side)
{
Side = side;
}

public double Side { get; set; }


}

public class Circle : Shape


{
public Circle(double radius)
{
Radius = radius;
}

public double Radius { get; set; }

public override double Circumference


{
get { return 2 * Math.PI * Radius; }
}

public override double Area


{
get { return Math.PI * Math.Pow(Radius, 2); }
}
}

public class Example


public class Example
{
public static void Main()
{
Shape sh = null;
Shape[] shapes = { new Square(10), new Rectangle(5, 7),
sh, new Square(0), new Rectangle(8, 8),
new Circle(3) };
foreach (var shape in shapes)
ShowShapeInfo(shape);
}

private static void ShowShapeInfo(Shape sh)


{
switch (sh)
{
// Note that this code never evaluates to true.
case Shape shape when shape == null:
Console.WriteLine($"An uninitialized shape (shape == null)");
break;
case null:
Console.WriteLine($"An uninitialized shape");
break;
case Shape shape when sh.Area == 0:
Console.WriteLine($"The shape: {sh.GetType().Name} with no dimensions");
break;
case Square sq when sh.Area > 0:
Console.WriteLine("Information about square:");
Console.WriteLine($" Length of a side: {sq.Side}");
Console.WriteLine($" Area: {sq.Area}");
break;
case Rectangle r when r.Length == r.Width && r.Area > 0:
Console.WriteLine("Information about square rectangle:");
Console.WriteLine($" Length of a side: {r.Length}");
Console.WriteLine($" Area: {r.Area}");
break;
case Rectangle r when sh.Area > 0:
Console.WriteLine("Information about rectangle:");
Console.WriteLine($" Dimensions: {r.Length} x {r.Width}");
Console.WriteLine($" Area: {r.Area}");
break;
case Shape shape when sh != null:
Console.WriteLine($"A {sh.GetType().Name} shape");
break;
default:
Console.WriteLine($"The {nameof(sh)} variable does not represent a Shape.");
break;
}
}
}
// The example displays the following output:
// Information about square:
// Length of a side: 10
// Area: 100
// Information about rectangle:
// Dimensions: 5 x 7
// Area: 35
// An uninitialized shape
// The shape: Square with no dimensions
// Information about square rectangle:
// Length of a side: 8
// Area: 64
// A Circle shape

Si noti che la clausola when dell'esempio che tenta di verificare se un oggetto Shape è null non viene
eseguita. Il criterio del tipo corretto da verificare per un null è case null: .
Specifiche del linguaggio C#
Per altre informazioni, vedere la sezione relativa all'istruzione switch nella specifica del linguaggio C#. La
specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
if-else
Criteri di ricerca
do (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione do esegue un'istruzione o un blocco di istruzioni mentre un'espressione booleana specificata


restituisce true . Poiché tale espressione viene valutata dopo ogni esecuzione del ciclo, un ciclo do-while viene
eseguito una o più volte. Questo comportamento è diverso da quello del ciclo while, che viene eseguito zero o
più volte.
In qualsiasi punto all'interno del blocco do è possibile uscire dal ciclo usando l'istruzione break.
È possibile passare direttamente alla valutazione dell'espressione while tramite l'istruzione continue. Se
l'espressione restituisce true , l'esecuzione continua con la prima istruzione nel ciclo. In caso contrario,
l'esecuzione continua con la prima istruzione dopo il ciclo.
È anche possibile uscire do-while da un ciclo tramite le istruzioni goto, returno throw .

Esempio
L'esempio seguente illustra l'utilizzo dell'istruzione do . Selezionare Esegui per eseguire il codice di esempio.
Dopo l'esecuzione è possibile modificare il codice ed eseguirlo di nuovo.

int n = 0;
do
{
Console.WriteLine(n);
n++;
} while (n < 5);

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione do della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzione while
for (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L'istruzione for esegue un'istruzione o un blocco di istruzioni mentre un'espressione booleana specificata
restituisce true .
In un punto qualsiasi all'interno del blocco dell'istruzione for è possibile uscire dal ciclo usando l'istruzione
break o passare all'iterazione successiva nel ciclo con l'istruzione continue. È anche possibile uscire for da un
ciclo tramite le istruzioni goto, returno throw .

Struttura dell'istruzione for


L'istruzione for definisce le sezioni inizializzatore, condizione e iteratore:

for (initializer; condition; iterator)


body

Le tre sezioni sono facoltative. Il corpo del ciclo è un'istruzione o un blocco di istruzioni.
L'esempio seguente illustra l'istruzione for con tutte le sezioni definite:

for (int i = 0; i < 5; i++)


{
Console.WriteLine(i);
}

Sezione inizializzatore
Le istruzioni nella sezione inizializzatore vengono eseguite una sola volta, prima dell'avvio del ciclo. La sezione
inizializzatore può essere costituita da quanto segue:
Dichiarazione e inizializzazione di una variabile di ciclo locale, non accessibile dall'esterno del ciclo stesso.
Zero o più istruzioni, ricavate dal seguente elenco, separate da virgole:
Istruzione di assegnazione
Chiamata di un metodo
Espressione di incremento in forma prefissa o suffissa, ad esempio ++i o i++

Espressione di decremento in forma prefissa o suffissa, ad esempio --i o i--

Creazione di un oggetto con l'operatore new


Espressione await
La sezione inizializzatore dell'esempio precedente dichiara e inizializza la variabile di ciclo locale i :

int i = 0

Sezione condizione
La sezione condizione, se presente, deve essere un'espressione booleana. Tale espressione viene valutata prima
di ogni iterazione del ciclo. Se la sezione condizione non è presente o l'espressione booleana restituisce true ,
viene eseguita la successiva iterazione del ciclo; in caso contrario, si esce dal ciclo.
La sezione condizione dell'esempio precedente determina se il ciclo termina in base al valore della variabile di
ciclo locale:

i < 5

Sezione iteratore
La sezione iteratore definisce cosa accade dopo ogni iterazione del corpo del ciclo. La sezione dell' iteratore
contiene zero o più delle espressioni di istruzione seguenti, separate da virgole:
Istruzione di assegnazione
Chiamata di un metodo
Espressione di incremento in forma prefissa o suffissa, ad esempio ++i o i++

Espressione di decremento in forma prefissa o suffissa, ad esempio --i o i--

Creazione di un oggetto con l'operatore new


Espressione await
La sezione iteratore dell'esempio precedente incrementa la variabile di ciclo locale:

i++

Esempi
L'esempio seguente illustra alcuni utilizzi meno comuni delle sezioni dell'istruzione for : assegnazione di un
valore a una variabile di ciclo esterna nella sezione inizializzatore, chiamata di un metodo sia nella sezione
inizializzatore che nella sezione iteratore e modifica dei valori di due variabili nella sezione iteratore. Selezionare
Esegui per eseguire il codice di esempio. Dopo l'esecuzione è possibile modificare il codice ed eseguirlo di
nuovo.

int i;
int j = 10;
for (i = 0, Console.WriteLine($"Start: i={i}, j={j}"); i < j; i++, j--, Console.WriteLine($"Step: i={i}, j=
{j}"))
{
// Body of the loop.
}

L'esempio seguente definisce il ciclo for infinito:

for ( ; ; )
{
// Body of the loop.
}

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione L'istruzione for della specifica del linguaggio C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
foreach, in
foreach, in (Riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

L' foreach istruzione esegue un'istruzione o un blocco di istruzioni per ogni elemento in un'istanza del tipo che
implementa l' System.Collections.IEnumerable System.Collections.Generic.IEnumerable<T> interfaccia o, come
illustrato nell'esempio seguente:

var fibNumbers = new List<int> { 0, 1, 1, 2, 3, 5, 8, 13 };


int count = 0;
foreach (int element in fibNumbers)
{
Console.WriteLine($"Element #{count}: {element}");
count++;
}
Console.WriteLine($"Number of elements: {count}");

L' foreach istruzione non è limitata a tali tipi. È possibile usarlo con un'istanza di qualsiasi tipo che soddisfi le
condizioni seguenti:
Un tipo ha il metodo pubblico senza parametri il GetEnumerator cui tipo restituito è una classe, uno struct o
un tipo di interfaccia. A partire da C# 9,0, il GetEnumerator metodo può essere un metodo di estensionedel
tipo.
Il tipo restituito del GetEnumerator metodo ha la proprietà Public Current e il metodo pubblico senza
parametri il MoveNext cui tipo restituito è Boolean .
Nell'esempio seguente viene utilizzata l' foreach istruzione con un'istanza del System.Span<T> tipo, che non
implementa alcuna interfaccia:

public class IterateSpanExample


{
public static void Main()
{
Span<int> numbers = new int[] { 3, 14, 15, 92, 6 };
foreach (int number in numbers)
{
Console.Write($"{number} ");
}
Console.WriteLine();
}
}

A partire da C# 7,3, se la proprietà dell'enumeratore Current restituisce un valore restituito di riferimento (


ref T dove T è il tipo di un elemento della raccolta), è possibile dichiarare una variabile di iterazione con il
ref ref readonly modificatore o, come illustrato nell'esempio seguente:
public class ForeachRefExample
{
public static void Main()
{
Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
item = num++;
}

foreach (ref readonly var item in storage)


{
Console.Write($"{item} ");
}
// Output:
// 0 1 2 3 4 5 6 7 8 9
}
}

A partire da C# 8,0, è possibile usare l' await foreach istruzione per utilizzare un flusso asincrono di dati,
ovvero il tipo di raccolta che implementa l' IAsyncEnumerable<T> interfaccia. Ogni iterazione del ciclo può
essere sospesa mentre l'elemento successivo viene recuperato in modo asincrono. Nell'esempio seguente viene
illustrato come utilizzare l' await foreach istruzione:

await foreach (var item in GenerateSequenceAsync())


{
Console.WriteLine(item);
}

Per impostazione predefinita, gli elementi del flusso vengono elaborati nel contesto acquisito. Se si desidera
disabilitare l'acquisizione del contesto, utilizzare il TaskAsyncEnumerableExtensions.ConfigureAwait metodo di
estensione. Per ulteriori informazioni sui contesti di sincronizzazione e sull'acquisizione del contesto corrente,
vedere utilizzo del modello asincrono basato su attività. Per ulteriori informazioni sui flussi asincroni, vedere la
sezione relativa ai flussi asincroni nell'articolo novità di C# 8,0 .
In un punto qualsiasi all'interno del blocco dell'istruzione foreach è possibile uscire dal ciclo usando l'istruzione
break o passare all'iterazione successiva nel ciclo con l'istruzione continue. È anche possibile uscire foreach da
un ciclo tramite le istruzioni goto, returno throw .
Se l'istruzione foreach viene applicata a null , viene generata una NullReferenceException. Se la raccolta di
origine dell' foreach istruzione è vuota, il corpo del foreach ciclo non viene eseguito e ignorato.

Tipo di una variabile di iterazione


È possibile usare la var parola chiave per consentire al compilatore di dedurre il tipo di una variabile di
iterazione nell' foreach istruzione, come illustrato nel codice seguente:

foreach (var item in collection) { }

È anche possibile specificare in modo esplicito il tipo di una variabile di iterazione, come illustrato nel codice
seguente:

IEnumerable<T> collection = new T[5];


foreach (V item in collection) { }
Nel form precedente, il tipo T di un elemento della raccolta deve essere convertibile in modo implicito o
esplicito nel tipo V di una variabile di iterazione. Se una conversione esplicita da T a V non riesce in fase di
esecuzione, l' foreach istruzione genera un'eccezione InvalidCastException . Se, ad esempio, T è un tipo di
classe non sealed, V può essere qualsiasi tipo di interfaccia, anche quello che T non implementa. In fase di
esecuzione, il tipo di un elemento della raccolta può essere quello che deriva da T e implementa effettivamente
V . In caso contrario, InvalidCastException viene generata un'eccezione.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione L'istruzione foreach della specifica del linguaggio C#.
Per ulteriori informazioni sulle funzionalità aggiunte in C# 8,0 e versioni successive, vedere le note della
proposta di funzionalità seguenti:
Flussi asincroni (C# 8,0)
GetEnumerator Supporto dell'estensione per i foreach cicli (C# 9,0)

Vedere anche
Informazioni di riferimento su C#
Parole chiave di C#
Utilizzo di foreach con matrici
Istruzione for
while (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione while esegue un'istruzione o un blocco di istruzioni mentre un'espressione booleana specificata
restituisce true . Poiché tale espressione viene valutata prima di ogni esecuzione del ciclo, un ciclo while viene
eseguito zero o più volte. Questo comportamento è diverso dal ciclo do, che viene eseguito una o più volte.
In qualsiasi punto all'interno del blocco while è possibile uscire dal ciclo usando l'istruzione break.
È possibile passare direttamente alla valutazione dell'espressione while tramite l'istruzione continue. Se
l'espressione restituisce true , l'esecuzione continua con la prima istruzione nel ciclo. In caso contrario,
l'esecuzione continua con la prima istruzione dopo il ciclo.
È anche possibile uscire while da un ciclo tramite le istruzioni goto, returno throw .

Esempio
L'esempio seguente illustra l'utilizzo dell'istruzione while . Selezionare Esegui per eseguire il codice di
esempio. Dopo l'esecuzione è possibile modificare il codice ed eseguirlo di nuovo.

int n = 0;
while (n < 5)
{
Console.WriteLine(n);
n++;
}

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione L'istruzione while della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
istruzione do
break (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

L'istruzione break termina il ciclo di inclusione più vicino o l'istruzione switch in cui appare. Il controllo viene
passato all'istruzione che segue l'istruzione terminata, se presente.

Esempio
In questo esempio, l'istruzione condizionale contiene un contatore che dovrebbe contare da 1 a 100. L'istruzione
break tuttavia termina il ciclo dopo 4 conteggi.

class BreakTest
{
static void Main()
{
for (int i = 1; i <= 100; i++)
{
if (i == 5)
{
break;
}
Console.WriteLine(i);
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Output:
1
2
3
4
*/

Esempio
Questo esempio illustra l'uso di break in un'istruzione switch.
class Switch
{
static void Main()
{
Console.Write("Enter your selection (1, 2, or 3): ");
string s = Console.ReadLine();
int n = Int32.Parse(s);

switch (n)
{
case 1:
Console.WriteLine("Current value is 1");
break;
case 2:
Console.WriteLine("Current value is 2");
break;
case 3:
Console.WriteLine("Current value is 3");
break;
default:
Console.WriteLine("Sorry, invalid selection.");
break;
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Sample Input: 1

Sample Output:
Enter your selection (1, 2, or 3): 1
Current value is 1
*/

Se è stato immesso 4 , l'output dovrebbe essere simile al seguente:

Enter your selection (1, 2, or 3): 4


Sorry, invalid selection.

Esempio
In questo esempio, l'istruzione break viene usata per interrompere un ciclo annidato interno e restituire il
controllo al ciclo esterno. Il controllo viene restituito solo un livello verso l'alto nei cicli nidificati.
class BreakInNestedLoops
{
static void Main(string[] args)
{

int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
char[] letters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' };

// Outer loop.
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"num = {numbers[i]}");

// Inner loop.
for (int j = 0; j < letters.Length; j++)
{
if (j == i)
{
// Return control to outer loop.
break;
}
Console.Write($" {letters[j]} ");
}
Console.WriteLine();
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

/*
* Output:
num = 0

num = 1
a
num = 2
a b
num = 3
a b c
num = 4
a b c d
num = 5
a b c d e
num = 6
a b c d e f
num = 7
a b c d e f g
num = 8
a b c d e f g h
num = 9
a b c d e f g h i
*/

Esempio
In questo esempio, l' break istruzione viene usata solo per interrompere il ramo corrente durante ogni
iterazione del ciclo. Il ciclo stesso non è influenzato dalle istanze di break che appartengono all'istruzione
Switch nidificata.
class BreakFromSwitchInsideLoop
{
static void Main(string[] args)
{
// loop 1 to 3
for (int i = 1; i <= 3; i++)
{
switch(i)
{
case 1:
Console.WriteLine("Current value is 1");
break;
case 2:
Console.WriteLine("Current value is 2");
break;
case 3:
Console.WriteLine("Current value is 3");
break;
default:
Console.WriteLine("This shouldn't happen.");
break;
}
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

/*
* Output:
Current value is 1
Current value is 2
Current value is 3
*/

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
switch
continue (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione continue passa il controllo all'iterazione successiva dell'istruzione while, do, for o foreach di
inclusione in cui è presente.

Esempio
In questo esempio viene inizializzato un contatore per il conteggio da 1 a 10. Utilizzando l' continue istruzione
insieme all'espressione (i < 9) , le istruzioni comprese tra continue e la fine del for corpo vengono ignorate
nelle iterazioni in cui i è minore di 9. Nelle ultime due iterazioni del for ciclo (dove i = = 9 e i = = 10) l'
continue istruzione non viene eseguita e il valore di i viene stampato nella console.

class ContinueTest
{
static void Main()
{
for (int i = 1; i <= 10; i++)
{
if (i < 9)
{
continue;
}
Console.WriteLine(i);
}

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Output:
9
10
*/

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Break (istruzione)
goto (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione goto passa direttamente il controllo del programma a un'istruzione con etichetta.
L'istruzione goto viene generalmente usata per trasferire il controllo a un'etichetta switch case specifica o
all'etichetta predefinita di un'istruzione switch .
L'istruzione goto viene anche usata per uscire da cicli annidati più interni.

Esempio
Nell'esempio riportato di seguito viene illustrato l'uso di goto in un'istruzione switch.

class SwitchTest
{
static void Main()
{
Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large");
Console.Write("Please enter your selection: ");
string s = Console.ReadLine();
int n = int.Parse(s);
int cost = 0;
switch (n)
{
case 1:
cost += 25;
break;
case 2:
cost += 25;
goto case 1;
case 3:
cost += 50;
goto case 1;
default:
Console.WriteLine("Invalid selection.");
break;
}
if (cost != 0)
{
Console.WriteLine($"Please insert {cost} cents.");
}
Console.WriteLine("Thank you for your business.");

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Sample Input: 2

Sample Output:
Coffee sizes: 1=Small 2=Medium 3=Large
Please enter your selection: 2
Please insert 50 cents.
Thank you for your business.
*/
Esempio
Nell'esempio riportato di seguito viene illustrato come usare goto per uscire da cicli annidati.

public class GotoTest1


{
static void Main()
{
int x = 200, y = 4;
int count = 0;
string[,] array = new string[x, y];

// Initialize the array.


for (int i = 0; i < x; i++)

for (int j = 0; j < y; j++)


array[i, j] = (++count).ToString();

// Read input.
Console.Write("Enter the number to search for: ");

// Input a string.
string myNumber = Console.ReadLine();

// Search.
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
if (array[i, j].Equals(myNumber))
{
goto Found;
}
}
}

Console.WriteLine($"The number {myNumber} was not found.");


goto Finish;

Found:
Console.WriteLine($"The number {myNumber} is found.");

Finish:
Console.WriteLine("End of search.");

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
Sample Input: 44

Sample Output
Enter the number to search for: 44
The number 44 is found.
End of search.
*/

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzione goto (C++)
return (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'istruzione return termina l'esecuzione del metodo in cui viene visualizzata e restituisce il controllo al metodo
di chiamata. Può restituire anche un valore facoltativo. Se il metodo è un tipo void , l'istruzione return può
essere omessa.
Se l'istruzione return è all'interno di un blocco try , il blocco finally , se presente, verrà eseguito prima che il
controllo venga restituito al metodo di chiamata.

Esempio
Nell'esempio seguente il metodo CalculateArea() restituisce la variabile locale area come valore double .

class ReturnTest
{
static double CalculateArea(int r)
{
double area = r * r * Math.PI;
return area;
}

static void Main()


{
int radius = 5;
double result = CalculateArea(radius);
Console.WriteLine("The area is {0:0.00}", result);

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: The area is 78.54

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
return (istruzione)
throw (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Segnala l'occorrenza di un'eccezione durante l'esecuzione del programma.

Commenti
La sintassi di throw è:

throw [e];

dove e è un'istanza di una classe derivata da System.Exception. Nell'esempio seguente l'istruzione throw
viene usata per generare una IndexOutOfRangeException, se l'argomento passato a un metodo denominato
GetNumber non corrisponde a un indice valido di una matrice interna.

using System;

public class NumberGenerator


{
int[] numbers = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };

public int GetNumber(int index)


{
if (index < 0 || index >= numbers.Length) {
throw new IndexOutOfRangeException();
}
return numbers[index];
}
}

I caller al metodo usano quindi un blocco try-catch o try-catch-finally per gestire l'eccezione generata.
L'esempio seguente gestisce l'eccezione generata dal metodo GetNumber .

using System;

public class Example


{
public static void Main()
{
var gen = new NumberGenerator();
int index = 10;
try {
int value = gen.GetNumber(index);
Console.WriteLine($"Retrieved {value}");
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine($"{e.GetType().Name}: {index} is outside the bounds of the array");
}
}
}
// The example displays the following output:
// IndexOutOfRangeException: 10 is outside the bounds of the array
Rigenerazione di un'eccezione
È possibile anche usare throw in un blocco catch per generare nuovamente un'eccezione gestita in un blocco
catch . In questo caso, throw non accetta un operando di eccezione. È particolarmente utile quando un metodo
passa un argomento da un caller a un altro metodo di raccolta e il metodo di raccolta genera un'eccezione che
deve essere passata al caller. Nell'esempio seguente viene generata nuovamente una NullReferenceException,
che viene generata quando si tenta di recuperare il primo carattere di una stringa non inizializzata.

using System;

public class Sentence


{
public Sentence(string s)
{
Value = s;
}

public string Value { get; set; }

public char GetFirstCharacter()


{
try {
return Value[0];
}
catch (NullReferenceException e) {
throw;
}
}
}

public class Example


{
public static void Main()
{
var s = new Sentence(null);
Console.WriteLine($"The first character is {s.GetFirstCharacter()}");
}
}
// The example displays the following output:
// Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an
object.
// at Sentence.GetFirstCharacter()
// at Example.Main()

IMPORTANT
È possibile anche usare la sintassi throw e in un blocco catch per creare un'istanza di una nuova eccezione da passare
al caller. In questo caso non viene mantenuta la traccia dello stack dell'eccezione originale, che è disponibile dalla proprietà
StackTrace.

Espressione throw
A partire da C# 7.0 è possibile usare throw come espressione e come istruzione. Ciò consente di generare
un'eccezione in contesti non supportati in precedenza. Sono inclusi:
operatore condizionale. Nell'esempio seguente viene usata un'espressione throw per generare una
ArgumentException se a un metodo viene passato una matrice di stringa vuota. Prima di C# 7.0, la logica
avrebbe dovuto usare un'istruzione if / else .
private static void DisplayFirstNumber(string[] args)
{
string arg = args.Length >= 1 ? args[0] :
throw new ArgumentException("You must supply an argument");
if (Int64.TryParse(arg, out var number))
Console.WriteLine($"You entered {number:F0}");
else
Console.WriteLine($"{arg} is not a number.");
}

L'operatore null-coalescing. Nell'esempio seguente viene usata un'espressione throw con un operatore
null-coalescing per generare un'eccezione, se la stringa assegnata a una proprietà Name è null .

public string Name


{
get => name;
set => name = value ??
throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
}

lambda o metodo con corpo di espressione. L'esempio seguente illustra un metodo con corpo di
espressione che genera una InvalidCastException perché non è supportata una conversione in un valore
DateTime.

DateTime ToDateTime(IFormatProvider provider) =>


throw new InvalidCastException("Conversion to a DateTime is not supported.");

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
try-catch
Parole chiave di C#
Procedura: Come generare in modo esplicito le eccezioni
try-catch (Riferimenti per C#)
02/11/2020 • 14 minutes to read • Edit Online

L'istruzione try-catch è costituita da un blocco try seguito da una o più clausole catch , che specificano i
gestori per eccezioni diverse.
Quando viene generata un'eccezione, Common Language Runtime(CLR) cerca l'istruzione catch che gestisce
questa eccezione. Se il metodo attualmente in esecuzione non contiene tale blocco catch , CLR analizza il
metodo che ha chiamato il metodo corrente e così via fino allo stack di chiamate. Se non viene trovato alcun
blocco catch , CLR visualizza un messaggio di eccezione non gestita per l'utente e interrompe l'esecuzione del
programma.
Il blocco try contiene il codice protetto che potrebbe generare l'eccezione. Il blocco viene eseguito fino a
quando non viene generata un'eccezione o viene completato correttamente. Ad esempio, il seguente tentativo di
eseguire il cast di un oggetto null genera l'eccezione NullReferenceException:

object o2 = null;
try
{
int i2 = (int)o2; // Error
}

Anche se la clausola catch può essere usata senza argomenti per intercettare qualsiasi tipo di eccezione, questo
utilizzo non è consigliato. In generale, è consigliabile intercettare solo le eccezioni di cui si sa come eseguire il
ripristino. È quindi opportuno specificare sempre argomento di oggetto derivato da System.Exception, ad
esempio:

catch (InvalidCastException e)
{
}

È possibile usare più clausole catch specifiche nella stessa istruzione try-catch. In questo caso, l'ordine delle
clausole catch è importante perché le clausole catch vengono esaminate nell'ordine specificato. Intercettare
le eccezioni più specifiche prima di quelle meno specifiche. Il compilatore provoca un errore se si ordinano i
blocchi catch in modo che un blocco successivo non possa mai essere raggiunto.
Usando gli argomenti catch , è possibile filtrare le eccezioni che si desidera gestire. È anche possibile usare un
filtro eccezioni per esaminare ulteriormente l'eccezione e decidere se gestirla. Se il filtro eccezioni restituisce
false, la ricerca di un gestore prosegue.

catch (ArgumentException e) when (e.ParamName == "…")


{
}

I filtri eccezioni sono preferibili per l'intercettazione e la rigenerazione (come illustrato di seguito) perché
lasciano intatto lo stack. Se un gestore successivo esegue il dump dello stack, è possibile visualizzare l'origine
dell'eccezione, anziché solo l'ultima posizione in cui è stata rigenerata. Un uso comune delle espressioni di filtro
eccezioni è la registrazione. È possibile creare un filtro che restituisca sempre false da visualizzare anche in un
log. È possibile registrare le eccezioni mentre si verificano senza doverle gestire e rigenerare.
Un'istruzione throw può essere usata in un blocco catch per rigenerare l'eccezione intercettata dall'istruzione
catch . Il seguente esempio estrae le informazioni di origine da un'eccezione IOException e quindi genera
l'eccezione per il metodo padre.

catch (FileNotFoundException e)
{
// FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
// Extract some information from this exception, and then
// throw it to the parent method.
if (e.Source != null)
Console.WriteLine("IOException source: {0}", e.Source);
throw;
}

È possibile intercettare un'eccezione e generarne un'altra. In questo caso, specificare l'eccezione intercettata
come eccezione interna, come mostrato nell'esempio seguente.

catch (InvalidCastException e)
{
// Perform some action here, and then throw a new exception.
throw new YourCustomException("Put your error message here.", e);
}

È anche possibile generare nuovamente un'eccezione quando una determinata condizione è true, come
mostrato nell'esempio seguente.

catch (InvalidCastException e)
{
if (e.Data == null)
{
throw;
}
else
{
// Take some action.
}
}

NOTE
È anche possibile usare un filtro eccezioni per ottenere un risultato simile in un modo spesso più pulito, che in più non
modifichi lo stack, come descritto in precedenza in questo documento. L'esempio seguente presenta un comportamento
simile al precedente per i chiamanti. La funzione restituisce InvalidCastException al chiamante quando e.Data è
null .

catch (InvalidCastException e) when (e.Data != null)


{
// Take some action.
}

Da un blocco try inizializzare solo le variabili dichiarate al suo interno. In caso contrario, è possibile che si
verifichi un'eccezione prima del completamento dell'esecuzione del blocco. Nel seguente esempio di codice, la
variabile n viene inizializzata nel blocco try . Un tentativo di usare questa variabile al di fuori del blocco try
nell'istruzione Write(n) genererà un errore del compilatore.

static void Main()


{
int n;
try
{
// Do not initialize this variable here.
n = 123;
}
catch
{
}
// Error: Use of unassigned local variable 'n'.
Console.Write(n);
}

Per altre informazioni su catch, vedere try-catch-finally.

Eccezioni nei metodi asincroni


Un metodo asincrono viene contrassegnato da un modificatore async e in genere contiene una o più espressioni
o istruzioni await. Un'espressione await applica l'operatore await a un oggetto Task o Task<TResult>.
Quando il controllo raggiunge un oggetto await nel metodo asincrono, l'avanzamento nel metodo viene
sospeso fino al completamento dell'attività attesa. Una volta completata l'attività, l'esecuzione del metodo può
riprendere. Per altre informazioni, vedere programmazione asincrona con Async e await.
L'attività completata a cui viene applicato await può essere in uno stato di errore a causa di un'eccezione non
gestita nel metodo che restituisce l'attività. L'attesa dell'attività genera un'eccezione. Un'attività può inoltre
terminare con uno stato di annullamento se viene annullato il processo asincrono che la restituisce. L'attesa di
un'attività annullata genera un'eccezione OperationCanceledException .
Per intercettare l'eccezione, attendere l'attività in un blocco try e intercettare l'eccezione nel blocco catch
associato. Per un esempio, vedere la sezione Esempio di metodo asincrono.
Un'attività può essere in uno stato di errore perché si sono verificate più eccezioni nel metodo asincrono atteso.
Ad esempio, l'attività può essere il risultato di una chiamata a Task.WhenAll. Quando si attende tale attività, solo
una delle eccezioni viene intercettata, ma non è possibile prevedere quale. Per un esempio, vedere la sezione
Esempio di Task.WhenAll.

Esempio
Nel seguente esempio, il blocco try contiene una chiamata al metodo ProcessString che potrebbe causare
un'eccezione. La clausola catch contiene il gestore di eccezioni che visualizza solo un messaggio sullo schermo.
Quando l'istruzione throw viene chiamata da ProcessString , il sistema cerca l'istruzione catch e visualizza il
messaggio Exception caught .
class TryFinallyTest
{
static void ProcessString(string s)
{
if (s == null)
{
throw new ArgumentNullException();
}
}

static void Main()


{
string s = null; // For demonstration purposes.

try
{
ProcessString(s);
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
}
}
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
at TryFinallyTest.Main() Exception caught.
* */

Esempio di due blocchi catch


Nell'esempio seguente, vengono usati due blocchi catch e viene intercettata l'eccezione più specifica, che è la
prima.
Per intercettare l'eccezione meno specifica, è possibile sostituire l'istruzione throw in ProcessString con
l'istruzione seguente: throw new Exception() .
Se nell'esempio si inserisce per primo il blocco catch meno specifico, viene visualizzato il messaggio di errore
seguente: A previous catch clause already catches all exceptions of this or a super type ('System.Exception')
.
class ThrowTest3
{
static void ProcessString(string s)
{
if (s == null)
{
throw new ArgumentNullException();
}
}

static void Main()


{
try
{
string s = null;
ProcessString(s);
}
// Most specific:
catch (ArgumentNullException e)
{
Console.WriteLine("{0} First exception caught.", e);
}
// Least specific:
catch (Exception e)
{
Console.WriteLine("{0} Second exception caught.", e);
}
}
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
at Test.ThrowTest3.ProcessString(String s) ... First exception caught.
*/

Esempio di metodo asincrono


L'esempio seguente illustra la gestione delle eccezioni per i metodi asincroni. Per intercettare un'eccezione
generata da un'attività asincrona, inserire l'espressione await in un blocco try e intercettare l'eccezione in un
blocco catch .
Rimuovere il commento dalla riga throw new Exception nell'esempio per illustrare la gestione delle eccezioni. La
proprietà IsFaulted dell'attività viene impostata su True , la proprietà Exception.InnerException dell'attività
viene impostata sull'eccezione e l'eccezione viene intercettata nel blocco catch .
Rimuovere il commento dalla riga throw new OperationCanceledException per illustrare cosa accade quando si
annulla un processo asincrono. La proprietà IsCanceled dell'attività viene impostata su true e l'eccezione
viene intercettata nel blocco catch . In alcune condizioni che non si applicano a questo esempio, la proprietà
IsFaulted dell'attività viene impostata su true e IsCanceled viene impostata su false .
public async Task DoSomethingAsync()
{
Task<string> theTask = DelayAsync();

try
{
string result = await theTask;
Debug.WriteLine("Result: " + result);
}
catch (Exception ex)
{
Debug.WriteLine("Exception Message: " + ex.Message);
}
Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
Debug.WriteLine("Task IsFaulted: " + theTask.IsFaulted);
if (theTask.Exception != null)
{
Debug.WriteLine("Task Exception Message: "
+ theTask.Exception.Message);
Debug.WriteLine("Task Inner Exception Message: "
+ theTask.Exception.InnerException.Message);
}
}

private async Task<string> DelayAsync()


{
await Task.Delay(100);

// Uncomment each of the following lines to


// demonstrate exception handling.

//throw new OperationCanceledException("canceled");


//throw new Exception("Something happened.");
return "Done";
}

// Output when no exception is thrown in the awaited method:


// Result: Done
// Task IsCanceled: False
// Task IsFaulted: False

// Output when an Exception is thrown in the awaited method:


// Exception Message: Something happened.
// Task IsCanceled: False
// Task IsFaulted: True
// Task Exception Message: One or more errors occurred.
// Task Inner Exception Message: Something happened.

// Output when a OperationCanceledException or TaskCanceledException


// is thrown in the awaited method:
// Exception Message: canceled
// Task IsCanceled: True
// Task IsFaulted: False

Esempio di Task.WhenAll
Il seguente esempio illustra la gestione delle eccezioni dove più attività possono restituire più eccezioni. Il blocco
try attende l'attività restituita da una chiamata a Task.WhenAll. L'attività viene completata quando vengono
completate le tre attività a cui si applica WhenAll.
Ognuna delle tre attività genera un'eccezione. Il blocco catch scorre le eccezioni presenti nella proprietà
Exception.InnerExceptions dell'attività restituita da Task.WhenAll.
public async Task DoMultipleAsync()
{
Task theTask1 = ExcAsync(info: "First Task");
Task theTask2 = ExcAsync(info: "Second Task");
Task theTask3 = ExcAsync(info: "Third Task");

Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);

try
{
await allTasks;
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex.Message);
Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
foreach (var inEx in allTasks.Exception.InnerExceptions)
{
Debug.WriteLine("Task Inner Exception: " + inEx.Message);
}
}
}

private async Task ExcAsync(string info)


{
await Task.Delay(100);

throw new Exception("Error-" + info);


}

// Output:
// Exception: Error-First Task
// Task IsFaulted: True
// Task Inner Exception: Error-First Task
// Task Inner Exception: Error-Second Task
// Task Inner Exception: Error-Third Task

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione try della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzioni try, throw e catch (C++)
throw
try-finally
Procedura: Come generare in modo esplicito le eccezioni
try...finally (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Usando un blocco finally , è possibile eliminare le risorse allocate in un blocco try ed è possibile eseguire
codice anche se viene generata un'eccezione di blocco try . In genere, le istruzioni di un blocco finally
vengono eseguite quando il controllo lascia un'istruzione try . Il trasferimento del controllo può verificarsi
come risultato dell'esecuzione normale, dell'esecuzione di un'istruzione break , continue , goto o return o
della propagazione di un'eccezione dell'istruzione try .
All'interno di un'eccezione gestita, l'esecuzione del blocco finally associato è garantita. Tuttavia, se l'eccezione
non è gestita, l'esecuzione del blocco finally dipende da come viene attivata l'operazione di rimozione
dell'eccezione. Ciò, a sua volta, dipende da come viene configurato il computer.
In genere, quando un'eccezione non gestita termina un'applicazione non è importante sapere se il blocco
finally è in esecuzione. Tuttavia, se si dispone di istruzioni in un blocco finally che deve essere eseguito
anche in questo caso, una soluzione consiste nell'aggiungere un blocco catch all'istruzione try - finally . In
alternativa, è possibile intercettare l'eccezione che potrebbe essere generata nel blocco try di un'istruzione
try - finally in alto nello stack di chiamate. Vale a dire, è possibile intercettare l'eccezione nel metodo che
chiama il metodo che contiene l'istruzione try - finally , nel metodo che chiama questo metodo o in qualsiasi
metodo nello stack di chiamate. Se non viene rilevata l'eccezione, l'esecuzione del blocco finally varia a
seconda che il sistema operativo scelga di generare un'operazione di rimozione dell'eccezione o meno.

Esempio
Nell'esempio seguente un'istruzione di conversione non valida causa un'eccezione System.InvalidCastException .
L'eccezione viene non è gestita.
public class ThrowTestA
{
static void Main()
{
int i = 123;
string s = "Some string";
object obj = s;

try
{
// Invalid conversion; obj contains a string, not a numeric type.
i = (int)obj;

// The following statement is not run.


Console.WriteLine("WriteLine at the end of the try block.");
}
finally
{
// To run the program in Visual Studio, type CTRL+F5. Then
// click Cancel in the error dialog.
Console.WriteLine("\nExecution of the finally block after an unhandled\n" +
"error depends on how the exception unwind operation is triggered.");
Console.WriteLine("i = {0}", i);
}
}
// Output:
// Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
//
// Execution of the finally block after an unhandled
// error depends on how the exception unwind operation is triggered.
// i = 123
}

Nell'esempio seguente viene intercettata un'eccezione del metodo TryCast in un metodo nella parte più in alto
dello stack di chiamate.
public class ThrowTestB
{
static void Main()
{
try
{
// TryCast produces an unhandled exception.
TryCast();
}
catch (Exception ex)
{
// Catch the exception that is unhandled in TryCast.
Console.WriteLine
("Catching the {0} exception triggers the finally block.",
ex.GetType());

// Restore the original unhandled exception. You might not


// know what exception to expect, or how to handle it, so pass
// it on.
throw;
}
}

public static void TryCast()


{
int i = 123;
string s = "Some string";
object obj = s;

try
{
// Invalid conversion; obj contains a string, not a numeric type.
i = (int)obj;

// The following statement is not run.


Console.WriteLine("WriteLine at the end of the try block.");
}
finally
{
// Report that the finally block is run, and show that the value of
// i has not been changed.
Console.WriteLine("\nIn the finally block in TryCast, i = {0}.\n", i);
}
}
// Output:
// In the finally block in TryCast, i = 123.

// Catching the System.InvalidCastException exception triggers the finally block.

// Unhandled Exception: System.InvalidCastException: Specified cast is not valid.


}

Per altre informazioni su finally , vedere try-catch-finally.


C# contiene anche l'istruzione using, che fornisce funzionalità simili per gli oggetti IDisposable con una sintassi
comoda.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione try della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzioni try, throw e catch (C++)
throw
try-catch
Procedura: Come generare in modo esplicito le eccezioni
try...catch...finally (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Le parole chiave catch e finally vengono in genere usate insieme per rilevare e usare le risorse in un blocco
try , gestire situazioni anomale in un blocco catch e liberare le risorse nel blocco finally .

Per altre informazioni ed esempi sulla rigenerazione di un'eccezione, vedere try-catch e Generazione di
eccezioni. Per altre informazioni sul blocco finally , vedere la pagina try-finally.

Esempio
public class EHClass
{
void ReadFile(int index)
{
// To run this code, substitute a valid path from your local machine
string path = @"c:\users\public\test.txt";
System.IO.StreamReader file = new System.IO.StreamReader(path);
char[] buffer = new char[10];
try
{
file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
}

finally
{
if (file != null)
{
file.Close();
}
}
// Do something with buffer...
}
}

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione try della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Istruzioni try, throw e catch (C++)
throw
Procedura: Come generare in modo esplicito le eccezioni
Istruzione using
Checked e Unchecked (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

È possibile eseguire istruzioni C# in contesti verificati o non verificati. In un contesto verificato l'overflow
aritmetico genera un'eccezione. In un contesto non verificato, l'overflow aritmetico viene ignorato e il risultato
viene troncato mediante l'eliminazione di tutti i bit più significativi che non corrispondono al tipo di
destinazione.
checked Specificare il contesto verificato.
unchecked Specificare il contesto non verificato.
Le operazioni seguenti sono interessate dal controllo di overflow:
Espressioni che usano gli operatori predefiniti seguenti su tipi integrali:
++ , -- , - unario, + , - , * , /

Conversioni numeriche esplicite tra tipi integrali oppure da float o double a un tipo integrale.
Se non si specifica checked né unchecked , il contesto predefinito per le espressioni non costanti (espressioni
che vengono valutate in fase di esecuzione) è definito dal valore dell'opzione del compilatore -checked. Per
impostazione predefinita il valore di tale opzione non è impostato e le operazioni aritmetiche vengono eseguite
in un contesto non verificato.
Per le espressioni costanti (espressioni che possono essere valutate per intero in fase di compilazione), il
contesto predefinito viene sempre controllato. A meno che un'espressione costante non venga posizionata in
modo esplicito in un contesto non verificato, gli overflow che si verificano durante la valutazione in fase di
compilazione dell'espressione causano errori in fase di compilazione.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Parole chiave per le istruzioni
checked (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

La parola chiave checked viene usata per abilitare in modo esplicito il controllo dell'overflow per le conversioni
e le operazioni aritmetiche di tipo integrale.
Per impostazione predefinita, un'espressione che contiene solo valori costanti provoca un errore di compilazione
se l'espressione produce un valore che non è incluso nell'intervallo del tipo di destinazione. Se l'espressione
contiene uno o più valori non costanti, il compilatore non rileva l'overflow. La valutazione dell'espressione
assegnata a i2 nell'esempio seguente non genera un errore di compilazione.

// The following example causes compiler error CS0220 because 2147483647


// is the maximum value for integers.
//int i1 = 2147483647 + 10;

// The following example, which includes variable ten, does not cause
// a compiler error.
int ten = 10;
int i2 = 2147483647 + ten;

// By default, the overflow in the previous statement also does


// not cause a run-time exception. The following line displays
// -2,147,483,639 as the sum of 2,147,483,647 and 10.
Console.WriteLine(i2);

Per impostazione predefinita, in queste espressioni non costanti non viene verificato l'overflow in fase di
esecuzione e non vengono generate eccezioni di overflow. Nell'esempio precedente viene visualizzato il valore -
2,147,483,639 come somma di due numeri interi positivi.
Il controllo dell'overflow può essere abilitato tramite le opzioni del compilatore, la configurazione dell'ambiente
o l'uso della parola chiave checked . Gli esempi seguenti illustrano come usare un'espressione checked o un
blocco checked per rilevare l'overflow generato dalla somma precedente in fase di esecuzione. Entrambi gli
esempi generano un'eccezione di overflow.

// If the previous sum is attempted in a checked environment, an


// OverflowException error is raised.

// Checked expression.
Console.WriteLine(checked(2147483647 + ten));

// Checked block.
checked
{
int i3 = 2147483647 + ten;
Console.WriteLine(i3);
}

La parola chiave unchecked può essere usata per impedire il controllo dell'overflow.

Esempio
Questo esempio illustra come usare checked per abilitare il controllo dell'overflow in fase di esecuzione.
class OverFlowTest
{
// Set maxIntValue to the maximum value for integers.
static int maxIntValue = 2147483647;

// Using a checked expression.


static int CheckedMethod()
{
int z = 0;
try
{
// The following line raises an exception because it is checked.
z = checked(maxIntValue + 10);
}
catch (System.OverflowException e)
{
// The following line displays information about the error.
Console.WriteLine("CHECKED and CAUGHT: " + e.ToString());
}
// The value of z is still 0.
return z;
}

// Using an unchecked expression.


static int UncheckedMethod()
{
int z = 0;
try
{
// The following calculation is unchecked and will not
// raise an exception.
z = maxIntValue + 10;
}
catch (System.OverflowException e)
{
// The following line will not be executed.
Console.WriteLine("UNCHECKED and CAUGHT: " + e.ToString());
}
// Because of the undetected overflow, the sum of 2147483647 + 10 is
// returned as -2147483639.
return z;
}

static void Main()


{
Console.WriteLine("\nCHECKED output value is: {0}",
CheckedMethod());
Console.WriteLine("UNCHECKED output value is: {0}",
UncheckedMethod());
}
/*
Output:
CHECKED and CAUGHT: System.OverflowException: Arithmetic operation resulted
in an overflow.
at ConsoleApplication1.OverFlowTest.CheckedMethod()

CHECKED output value is: 0


UNCHECKED output value is: -2147483639
*/
}

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Checked e Unchecked
unchecked
unchecked (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

La parola chiave unchecked viene usata per eliminare il controllo dell'overflow per le conversioni e le operazioni
aritmetiche di tipo integrale.
In un contesto non controllato o di tipo unchecked, se un'espressione produce un valore non compreso
nell'intervallo del tipo di destinazione, l'overflow non viene contrassegnato. Poiché, ad esempio, il calcolo
nell'esempio seguente viene eseguito in un'espressione o un blocco di tipo unchecked , il fatto che il risultato sia
troppo grande per un numero intero viene ignorato e a int1 viene assegnato il valore -2.147.483.639.

unchecked
{
int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);

Se l'ambiente unchecked viene rimosso, si verifica un errore di compilazione. È possibile rilevare l'overflow in
fase di compilazione perché tutti i termini dell'espressione sono costanti.
Per impostazione predefinita, le espressioni che contengono termini non costanti non vengono controllate in
fase di compilazione e di esecuzione. Per informazioni sull'abilitazione di un ambiente controllato, o di tipo
checked, vedere checked.
Poiché il controllo dell'overflow richiede tempo, l'uso di codice non controllato in situazioni in cui non vi è
pericolo di overflow potrebbe consentire un miglioramento delle prestazioni. Se tuttavia è possibile che si
verifichi un overflow, è necessario usare un ambiente controllato.

Esempio
In questo esempio viene illustrato come usare la parola chiave unchecked .
class UncheckedDemo
{
static void Main(string[] args)
{
// int.MaxValue is 2,147,483,647.
const int ConstantMax = int.MaxValue;
int int1;
int int2;
int variableMax = 2147483647;

// The following statements are checked by default at compile time. They do not
// compile.
//int1 = 2147483647 + 10;
//int1 = ConstantMax + 10;

// To enable the assignments to int1 to compile and run, place them inside
// an unchecked block or expression. The following statements compile and
// run.
unchecked
{
int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);

// The sum of 2,147,483,647 and 10 is displayed as -2,147,483,639.


Console.WriteLine(int1);

// The following statement is unchecked by default at compile time and run


// time because the expression contains the variable variableMax. It causes
// overflow but the overflow is not detected. The statement compiles and runs.
int2 = variableMax + 10;

// Again, the sum of 2,147,483,647 and 10 is displayed as -2,147,483,639.


Console.WriteLine(int2);

// To catch the overflow in the assignment to int2 at run time, put the
// declaration in a checked block or expression. The following
// statements compile but raise an overflow exception at run time.
checked
{
//int2 = variableMax + 10;
}
//int2 = checked(variableMax + 10);

// Unchecked sections frequently are used to break out of a checked


// environment in order to improve performance in a portion of code
// that is not expected to raise overflow exceptions.
checked
{
// Code that might cause overflow should be executed in a checked
// environment.
unchecked
{
// This section is appropriate for code that you are confident
// will not result in overflow, and for which performance is
// a priority.
}
// Additional checked code here.
}
}
}

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Checked e Unchecked
selezionata
Istruzione fixed (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

L'istruzione fixed impedisce che il Garbage Collector esegua la rilocazione di una variabile mobile. L'istruzione
fixed è consentita solo in un contesto di tipo unsafe. È anche possibile usare la parola chiave fixed per creare
buffer a dimensione fissa.
L'istruzione fixed imposta un puntatore a una variabile gestita e la blocca durante l'esecuzione dell'istruzione. I
puntatori alle variabili mobili gestite sono utili solo in un contesto fixed . Senza un contesto fixed ,
l'operazione di garbage collection potrebbe spostare le variabili in modo imprevedibile. Il compilatore C#
consente di assegnare un puntatore a una variabile gestita in un'istruzione fixed .

class Point
{
public int x;
public int y;
}

unsafe private static void ModifyFixedStorage()


{
// Variable pt is a managed variable, subject to garbage collection.
Point pt = new Point();

// Using fixed allows the address of pt members to be taken,


// and "pins" pt so that it is not relocated.

fixed (int* p = &pt.x)


{
*p = 1;
}
}

È possibile inizializzare un puntatore usando una matrice, una stringa, un buffer a dimensione fissa o l'indirizzo
di una variabile. L'esempio seguente illustra l'uso di indirizzi di variabili, matrici e stringhe:

Point point = new Point();


double[] arr = { 0, 1.5, 2.3, 3.4, 4.0, 5.9 };
string str = "Hello World";

// The following two assignments are equivalent. Each assigns the address
// of the first element in array arr to pointer p.

// You can initialize a pointer by using an array.


fixed (double* p = arr) { /*...*/ }

// You can initialize a pointer by using the address of a variable.


fixed (double* p = &arr[0]) { /*...*/ }

// The following assignment initializes p by using a string.


fixed (char* p = str) { /*...*/ }

// The following assignment is not valid, because str[0] is a char,


// which is a value, not a variable.
//fixed (char* p = &str[0]) { /*...*/ }

A partire da C# 7.3, l'istruzione fixed opera su tipi aggiuntivi oltre a matrici, stringhe, buffer a dimensione fissa
o variabili non gestite. È possibile bloccare qualsiasi tipo che implementa un metodo denominato
GetPinnableReference . GetPinnableReference deve restituire una variabile ref di un tipo non gestito. I tipi .NET
System.Span<T> e System.ReadOnlySpan<T> introdotti in .NET Core 2.0 usano questo modello e possono
essere bloccati. Questa operazione è illustrata nell'esempio seguente:

unsafe private static void FixedSpanExample()


{
int[] PascalsTriangle = {
1,
1, 1,
1, 2, 1,
1, 3, 3, 1,
1, 4, 6, 4, 1,
1, 5, 10, 10, 5, 1
};

Span<int> RowFive = new Span<int>(PascalsTriangle, 10, 5);

fixed (int* ptrToRow = RowFive)


{
// Sum the numbers 1,4,6,4,1
var sum = 0;
for (int i = 0; i < RowFive.Length; i++)
{
sum += *(ptrToRow + i);
}
Console.WriteLine(sum);
}
}

Se si creano tipi che devono partecipare a questo modello, vedere Span<T>.GetPinnableReference() per un
esempio di implementazione del modello.
Se sono dello stesso tipo, è possibile inizializzare più puntatori in un'unica istruzione:

fixed (byte* ps = srcarray, pd = dstarray) {...}

Per inizializzare puntatori di tipi diversi, annidare semplicemente le istruzioni fixed come illustrato
nell'esempio seguente.

fixed (int* p1 = &point.x)


{
fixed (double* p2 = &arr[5])
{
// Do something with p1 and p2.
}
}

Dopo aver eseguito il codice nell'istruzione, le variabili bloccate vengono sbloccate e sottoposte al Garbage
Collection. Di conseguenza, evitare di puntare alle variabili esterne all'istruzione fixed . Le variabili dichiarate
nell'istruzione fixed rientrano nell'ambito di tale istruzione, semplificandola:

fixed (byte* ps = srcarray, pd = dstarray)


{
...
}
// ps and pd are no longer in scope here.
I puntatori inizializzati nelle istruzioni fixed sono variabili di sola lettura. Per modificare il valore puntatore, è
necessario dichiarare una seconda variabile e quindi modificarla. La variabile dichiarata nell'istruzione fixed
non può essere modificata:

fixed (byte* ps = srcarray, pd = dstarray)


{
byte* pSourceCopy = ps;
pSourceCopy++; // point to the next element.
ps++; // invalid: cannot modify ps, as it is declared in the fixed statement.
}

È possibile allocare memoria nello stack, dove non è soggetta a Garbage Collection e pertanto non deve essere
bloccata. A tale scopo, utilizzare un' stackalloc espressione.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione fixed della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
unsafe
Tipi puntatore
Buffer a dimensione fissa
Istruzione lock (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L'istruzione lock acquisisce il blocco a esclusione reciproca per un oggetto specificato, esegue un blocco di
istruzioni e quindi rilascia il blocco. Mentre è attivo un blocco, il thread che contiene il blocco può ancora
acquisire e rilasciare il blocco. Gli altri thread non possono acquisire il blocco e devono attendere finché il blocco
non viene rilasciato.
L'istruzione lock ha il formato

lock (x)
{
// Your code...
}

in cui x è un'espressione di un tipo riferimento. È esattamente equivalente a

object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}

Poiché il codice usa un blocco try... finally, il blocco viene rilasciato anche se viene generata un'eccezione nel
corpo di un'istruzione lock .
Non è possibile usare l'operatore await nel corpo di un'istruzione lock .

Linee guida
Quando si sincronizza l'accesso dei thread a una risorsa condivisa, applicare il blocco a un'istanza dell'oggetto
dedicata (ad esempio private readonly object balanceLock = new object(); ) o a un'altra istanza che ha poche
probabilità di essere usata come oggetto di blocco da parti del codice non correlate. Evitare di usare la stessa
istanza di oggetto di blocco per diverse risorse condivise. Questo può originare problemi di deadlock o conflitti
di blocco. In particolare, evitare di usare gli elementi seguenti come oggetti di blocco:
this , perché potrebbe essere usato dai chiamanti come blocco.
Istanze Type, in quanto possono essere ottenute dall'operatore typeof o dalla reflection.
Istanze stringa, tra cui i valori letterali stringa, in quanto potrebbero essere centralizzate.
Mantenere un blocco per il minor tempo possibile per ridurre la contesa di blocchi.

Esempio
L'esempio seguente definisce una classe Account che sincronizza l'accesso al proprio campo balance privato
applicando il blocco su un'istanza balanceLock dedicata. L'uso della stessa istanza per il blocco garantisce che il
campo balance non possa essere aggiornato contemporaneamente da due thread che provano a chiamare i
metodi Debit o Credit allo stesso tempo.

using System;
using System.Threading.Tasks;

public class Account


{
private readonly object balanceLock = new object();
private decimal balance;

public Account(decimal initialBalance) => balance = initialBalance;

public decimal Debit(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
}

decimal appliedAmount = 0;
lock (balanceLock)
{
if (balance >= amount)
{
balance -= amount;
appliedAmount = amount;
}
}
return appliedAmount;
}

public void Credit(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
}

lock (balanceLock)
{
balance += amount;
}
}

public decimal GetBalance()


{
lock (balanceLock)
{
return balance;
}
}
}

class AccountTest
{
static async Task Main()
{
var account = new Account(1000);
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => Update(account));
}
await Task.WhenAll(tasks);
Console.WriteLine($"Account's balance is {account.GetBalance()}");
// Output:
// Account's balance is 2000
// Account's balance is 2000
}

static void Update(Account account)


{
decimal[] amounts = { 0, 2, -3, 6, -2, -1, 8, -5, 11, -6 };
foreach (var amount in amounts)
{
if (amount >= 0)
{
account.Credit(amount);
}
else
{
account.Debit(Math.Abs(amount));
}
}
}
}

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Istruzione lock della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Parole chiave di C#
System.Threading.Monitor
System.Threading.SpinLock
System.Threading.Interlocked
Panoramica delle primitive di sincronizzazione
Parametri di metodo (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

I parametri dichiarati per un metodo senza in, ref o out, vengono passati al metodo chiamato per valore. che può
essere modificato all'interno del metodo. Il valore modificato, tuttavia, non verrà mantenuto quando il controllo
viene restituito alla routine chiamante. Usando una parola chiave del parametro, è possibile modificare questo
comportamento.
Questa sezione descrive le parole chiave che è possibile usare quando si dichiarano i parametri dei metodi:
params specifica che questo parametro può accettare un numero variabile di argomenti.
in specifica che questo parametro viene passato per riferimento ma viene letto solo dal metodo chiamato.
ref specifica che questo parametro viene passato per riferimento e può essere letto o scritto dal metodo
chiamato.
out specifica che questo parametro viene passato per riferimento e viene scritto dal metodo chiamato.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
params (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Usando la parola chiave params , è possibile specificare un parametro di metodo che usa un numero variabile di
argomenti. Il tipo di parametro deve essere una matrice unidimensionale.
In una dichiarazione di metodo non è possibile aggiungere altri parametri dopo la parola chiave params ed è
consentito l'uso di una sola parola chiave params .
Se il tipo dichiarato del params parametro non è una matrice unidimensionale, si verifica l'errore del
compilatore CS0225 .
Quando si chiama un metodo con un params parametro, è possibile passare:
Elenco delimitato da virgole di argomenti del tipo degli elementi della matrice.
Matrice di argomenti del tipo specificato.
Nessun argomento. Se non vengono inviati argomenti, la lunghezza dell'elenco params è zero.

Esempio
Nell'esempio seguente vengono illustrati i vari modi in cui è possibile inviare argomenti al parametro params .
public class MyClass
{
public static void UseParams(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
Console.Write(list[i] + " ");
}
Console.WriteLine();
}

public static void UseParams2(params object[] list)


{
for (int i = 0; i < list.Length; i++)
{
Console.Write(list[i] + " ");
}
Console.WriteLine();
}

static void Main()


{
// You can send a comma-separated list of arguments of the
// specified type.
UseParams(1, 2, 3, 4);
UseParams2(1, 'a', "test");

// A params parameter accepts zero or more arguments.


// The following calling statement displays only a blank line.
UseParams2();

// An array argument can be passed, as long as the array


// type matches the parameter type of the method being called.
int[] myIntArray = { 5, 6, 7, 8, 9 };
UseParams(myIntArray);

object[] myObjArray = { 2, 'b', "test", "again" };


UseParams2(myObjArray);

// The following call causes a compiler error because the object


// array cannot be converted into an integer array.
//UseParams(myObjArray);

// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
UseParams2(myIntArray);
}
}
/*
Output:
1 2 3 4
1 a test

5 6 7 8 9
2 b test again
System.Int32[]
*/

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Parametri di metodo
Modificatore del parametro in (Riferimenti per C#)
02/11/2020 • 10 minutes to read • Edit Online

La parola chiave in fa sì che gli argomenti vengono passati per riferimento. Imposta il parametro formale
come alias dell'argomento, che deve essere una variabile. In altre parole, qualsiasi operazione sul parametro
viene eseguita sull'argomento. È simile alle parole chiave ref o out, ma gli argomenti in non possono essere
modificati dal metodo chiamato. Mentre gli argomenti ref possono essere modificati, gli argomenti out
devono essere modificati dal metodo chiamato e queste modifiche sono osservabili nel contesto di chiamata.

int readonlyArgument = 44;


InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44

void InArgExample(in int number)


{
// Uncomment the following line to see error CS8331
//number = 19;
}

L'esempio precedente dimostra che il modificatore in non è in genere necessario nel sito di chiamata. Viene
richiesto soltanto nella dichiarazione di metodo.

NOTE
La parola chiave in può essere usata anche con un parametro di tipo generico per specificare che il parametro è di tipo
controvariante, quale parte di un'istruzione foreach o parte di una clausola join in una query LINQ. Per altre
informazioni sull'uso della parola chiave in in questi contesti, vedere la pagina in, in cui sono presenti collegamenti a
tutti gli usi.

Le variabili passate come argomenti in devono essere inizializzate prima di essere passate in una chiamata al
metodo. Tuttavia, il metodo chiamato non può assegnare un valore o modificare l'argomento.
Il modificatore di parametro in è disponibile in C# 7.2 e versioni successive. Nelle versioni precedenti viene
generato l'errore del compilatore CS8107 ("La funzionalità 'riferimenti di sola lettura' non è disponibile in C#
7.0. Usare la versione 7.2 o versioni successive del linguaggio.") Per configurare la versione del linguaggio del
compilatore, vedere Selezionare la versione del linguaggio C#.
Le parole chiave in , ref e out non sono considerate parte della firma del metodo ai fini della risoluzione
dell'overload. Non è quindi possibile eseguirne l'overload se l'unica differenza è che un metodo accetta un
argomento ref o in e l'altro un argomento out . Il codice seguente, ad esempio, non verrà compilato:

class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on in, ref and out".
public void SampleMethod(in int i) { }
public void SampleMethod(ref int i) { }
}

È consentito l'overload in base alla presenza di in :


class InOverloads
{
public void SampleMethod(in int i) { }
public void SampleMethod(int i) { }
}

Regole di risoluzione dell'overload


È possibile comprendere le regole di risoluzione dell'overload per i metodi con argomenti per valore rispetto ai
metodi con argomenti in se si comprende la motivazione per gli argomenti in . La definizione di metodi
tramite parametri in costituisce un'ottimizzazione potenziale delle prestazioni. Alcuni argomenti di tipo
struct possono essere di grandi dimensioni e quando vengono chiamati metodi all'interno di cicli ristretti o in
percorsi di codice critici, il costo della copia di tali strutture ha una rilevanza fondamentale. I metodi dichiarano
parametri in per specificare che è possibile passare argomenti per riferimento in modo sicuro, perché il
metodo chiamato non modifica lo stato degli argomenti. Il passaggio di tali argomenti per riferimento consente
di evitare una copia potenzialmente dispendiosa.
Specificare in per gli argomenti presso il sito di chiamata è in genere facoltativo. Non esiste alcuna differenza
semantica tra il passaggio di argomenti per valore e il passaggio per riferimento tramite il modificatore in . Il
modificatore in presso il sito di chiamata è facoltativo perché non è necessario indicare che il valore
dell'argomento può essere modificato. Si aggiunge il modificatore in in modo esplicito presso il sito di
chiamata per assicurarsi che l'argomento venga passato per riferimento, non per valore. L'uso di in in modo
esplicito ha i due effetti seguenti:
In primo luogo, se si specifica in presso il sito di chiamata si impone al compilatore di selezionare un metodo
definito con un parametro in corrispondente. In caso contrario, se due metodi si differenziano solo per la
presenza di in , l'overload per valore rappresenta una corrispondenza migliore.
In secondo luogo, se si specifica in si dichiara l'intenzione di passare un argomento per riferimento.
L'argomento usato con in deve rappresentare una posizione a cui sia possibile fare riferimento direttamente.
Sono valide le stesse regole generali di out e ref : non è possibile usare costanti, proprietà ordinarie o altre
espressioni che producono valori. In caso contrario, l'omissione di in presso il sito di chiamata informa il
compilatore che è consentito creare una variabile temporanea da passare per riferimento di sola lettura al
metodo. Il compilatore crea una variabile temporanea per superare diverse restrizioni degli argomenti in :
Una variabile temporanea consente costanti in fase di compilazione come parametri in .
Una variabile temporanea consente proprietà o altre espressioni per i parametri in .
Una variabile temporanea consente argomenti che includono una conversione implicita dal tipo di
argomento al tipo di parametro.
In tutte le istanze precedenti, il compilatore crea una variabile temporanea che archivia il valore della costante,
della proprietà o di un'altra espressione.
Il codice seguente illustra queste regole:
static void Method(in int argument)
{
// implementation removed
}

Method(5); // OK, temporary variable created.


Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`

Si supponga a questo punto che sia disponibile un altro metodo che usa argomenti per valore. I risultati
cambiano, come illustrato nel codice seguente:

static void Method(int argument)


{
// implementation removed
}

static void Method(in int argument)


{
// implementation removed
}

Method(5); // Calls overload passed by value


Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`

L'unica chiamata a un metodo in cui l'argomento viene passato per riferimento è quella finale.

NOTE
Per semplicità, il codice precedente usa int come tipo di argomento. Poiché nella maggior parte dei computer moderni
le dimensioni di int non sono maggiori di quelle di un riferimento, non si ottiene alcun vantaggio dal passaggio di un
unico int come riferimento di sola lettura.

Limitazioni dei parametri in


Non è possibile usare le parole chiave in , ref e out per i seguenti tipi di metodi:
Metodi asincroni definiti usando il modificatore async.
Metodi iteratori che includono un'istruzione yield return o yield break .
Il primo argomento di un metodo di estensione non può avere il in modificatore, a meno che tale
argomento non sia uno struct.
Primo argomento di un metodo di estensione in cui l'argomento è un tipo generico (anche quando tale tipo è
vincolato a essere uno struct).

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Parametri di metodo
Scrivi codice efficiente sicuro
ref (Riferimenti per C#)
02/11/2020 • 16 minutes to read • Edit Online

La parola chiave ref indica un valore che viene passato per riferimento. Viene usata in quattro contesti diversi:
Nella firma di un metodo e in una chiamata al metodo, per passare un argomento a un metodo per
riferimento. Per altre informazioni, vedere Passaggio di un argomento per riferimento.
Nella firma di un metodo, per restituire un valore al chiamante per riferimento. Per altre informazioni, vedere
Valori di riferimento restituiti.
Nel corpo di un membro, per indicare che un valore restituito di riferimento è archiviato in locale come un
riferimento che il chiamante intende modificare o, in generale, che una variabile locale accede a un altro
valore per riferimento. Per altre informazioni, vedere Variabili locali ref.
In una dichiarazione struct per dichiarare ref struct o readonly ref struct . Per altre informazioni,
vedere la sezione ref struct dell'articolo tipi di struttura .

Passaggio di un argomento per riferimento


Quando viene usata nell'elenco di parametri di un metodo, la parola chiave ref indica che un argomento viene
passato per riferimento, non per valore. La parola chiave ref imposta il parametro formale come alias
dell'argomento, che deve essere una variabile. In altre parole, qualsiasi operazione sul parametro viene eseguita
sull'argomento. Se, ad esempio, il chiamante passa un'espressione variabile locale o un'espressione di accesso
dell'elemento della matrice e il metodo chiamato sostituisce l'oggetto a cui fa riferimento il parametro ref, la
variabile locale del chiamante o l'elemento di matrice fa ora riferimento al nuovo oggetto quando il metodo
restituisce.

NOTE
Non confondere il concetto di passaggio per riferimento con il concetto di tipi di riferimento. I due concetti non sono
uguali. Un parametro di metodo può essere modificato da ref che si tratti di un tipo di valore o di un tipo di
riferimento. Non viene eseguito il boxing di un tipo di valore quando viene passato per riferimento.

Per usare un parametro ref , la definizione del metodo e il metodo chiamante devono usare in modo esplicito
la parola chiave ref , come illustrato nell'esempio seguente.

void Method(ref int refArgument)


{
refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

Un argomento passato a un parametro ref o in deve essere inizializzato prima di essere passato. Questo
comportamento è diverso dai parametri out, i cui argomenti non devono essere inizializzati in modo esplicito
prima di essere passati.
I membri di una classe non possono avere firme che differiscono solo per ref , in o out . Un errore del
compilatore si verifica se l'unica differenza tra due membri di un tipo è che uno di essi ha un parametro ref e
l'altro ha un parametro out o in . Il codice seguente, ad esempio, non viene compilato.

class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}

È tuttavia possibile eseguire l'overload dei metodi quando un metodo ha un parametro ref , in o out e
l'altro ha un parametro di valore, come illustrato nell'esempio seguente.

class RefOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(ref int i) { }
}

In altre situazioni che richiedono la firma corrispondente, ad esempio nascondere o sottoporre a override, in ,
ref e out fanno parte della firma e non sono corrispondenti tra loro.

Le proprietà non sono variabili. Sono metodi e non possono essere passate ai parametri ref .
Non è possibile usare le parole chiave ref , in e out per i seguenti tipi di metodi:
Metodi asincroni definiti usando il modificatore async.
Metodi iteratori che includono un'istruzione yield return o yield break .
I metodi di estensione presentano inoltre le restrizioni seguenti:
out Non è possibile usare la parola chiave sul primo argomento di un metodo di estensione.
Non ref è possibile usare la parola chiave sul primo argomento di un metodo di estensione quando
l'argomento non è uno struct oppure un tipo generico non vincolato come uno struct.
in Non è possibile usare la parola chiave a meno che il primo argomento non sia uno struct. La in parola
chiave non può essere usata in alcun tipo generico, anche quando è vincolato a essere uno struct.

Passaggio di un argomento per riferimento: un esempio


Negli esempi precedenti vengono passati tipi valore per riferimento. È anche possibile usare la parola chiave
ref per passare tipi riferimento per riferimento. Il passaggio di un tipo riferimento per riferimento consente al
metodo chiamato di sostituire l'oggetto a cui fa riferimento il parametro per riferimento nel chiamante. Il
percorso di archiviazione dell'oggetto viene passato al metodo come valore del parametro referenziato. Se si
modifica il valore nella posizione di archiviazione del parametro (in modo che punti a un nuovo oggetto), è
anche possibile modificare il percorso di archiviazione a cui fa riferimento il chiamante. Nell'esempio seguente
viene passata un'istanza di un tipo di riferimento come parametro ref .
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}

public string ItemName { get; set; }


public int ItemID { get; set; }
}

private static void ChangeByReference(ref Product itemRef)


{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 99999);

// You can change the value of one of the properties of


// itemRef. The change happens to item in Main as well.
itemRef.ItemID = 12345;
}

private static void ModifyProductsByReference()


{
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);

// Pass the product instance to ChangeByReference.


ChangeByReference(ref item);
System.Console.WriteLine("Back in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}

// This method displays the following output:


// Original values in Main. Name: Fasteners, ID: 54321
// Back in Main. Name: Stapler, ID: 12345

Per altre informazioni su come passare i tipi di riferimento per valore e per riferimento, vedere Passaggio di
parametri di tipi di riferimento.

Valori restituiti di riferimento


I valori restituiti di riferimento (o valori restituiti ref) sono i valori che un metodo restituisce per riferimento al
chiamante. Ovvero il chiamante può modificare il valore restituito da un metodo e tale modifica viene riflessa
nello stato dell'oggetto nel metodo chiamante.
Un valore restituito di riferimento viene definito mediante la parola chiave ref :
Nella firma del metodo. Ad esempio, la firma del metodo seguente indica che il metodo GetCurrentPrice
restituisce un valore Decimal per riferimento.

public ref decimal GetCurrentPrice()

Tra il token return e la variabile restituita in un'istruzione return nel metodo. Ad esempio:

return ref DecimalArray[0];


Affinché il chiamante modifichi lo stato dell'oggetto, il valore restituito di riferimento deve essere archiviato in
una variabile definita in modo esplicito come variabile locale ref.
Di seguito è riportato un esempio di Return Ref più completo, che mostra sia la firma del metodo che il corpo
del metodo.

public static ref int Find(int[,] matrix, Func<int, bool> predicate)


{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}

Il metodo chiamato può anche dichiarare il valore restituito come ref readonly per restituire il valore per
riferimento e specificare che il codice chiamante non può modificare il valore restituito. Il metodo chiamante
può evitare di copiare il valore restituito archiviando il valore in una variabile locale ref ReadOnly .
Per un esempio, vedere un esempio Ref Returns e Ref locals.

Variabili locali ref


Una variabile locale ref viene usata per fare riferimento ai valori restituiti mediante return ref . Non è possibile
inizializzare una variabile locale ref in un valore restituito non ref. In altre parole, il lato destro
dell'inizializzazione deve essere un riferimento. Tutte le modifiche apportate al valore della variabile locale ref
vengono riflesse nello stato dell'oggetto di cui metodo ha restituito il valore per riferimento.
Per definire una variabile locale ref, usare la parola chiave ref prima della dichiarazione di variabile, nonché
immediatamente prima della chiamata al metodo che restituisce il valore per riferimento.
Ad esempio, l'istruzione seguente definisce un valore di variabile locale ref restituito da un metodo denominato
GetEstimatedValue :

ref decimal estValue = ref Building.GetEstimatedValue();

È possibile accedere a un valore per riferimento nello stesso modo. In alcuni casi, l'accesso a un valore per
riferimento migliora le prestazioni evitando un'operazione di copia potenzialmente dispendiosa. L'istruzione
seguente, ad esempio, spiega come sia possibile definire un valore locale di riferimento usato per fare
riferimento a un valore.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

In entrambi gli esempi la ref parola chiave deve essere usata in entrambe le posizioni o il compilatore genera
l'errore CS8172 "non è possibile inizializzare una variabile per riferimento con un valore".
A partire da C# 7.3, la variabile di iterazione dell'istruzione foreach può essere una variabile locale ref o locale
ref readonly. Per altre informazioni, vedere l'articolo sull'istruzione foreach.
Inoltre, a partire da C# 7,3, è possibile riassegnare una variabile locale Ref local o ref ReadOnly con l' operatore
di assegnazione Ref.

Variabili ref readonly


Una variabile locale ref readonly viene usata per fare riferimento ai valori restituiti dal metodo o dalla proprietà
che include ref readonly nella propria firma e usa return ref . Una variabile ref readonly combina le
proprietà di una variabile locale ref con una variabile readonly : è un alias per l'archiviazione cui è assegnata e
non può essere modificata.

Esempio di valori restituiti e variabili locali ref


Nell'esempio seguente viene definita una classe Book che ha due campi String, Title e Author . Definisce
inoltre una classe BookCollection che include una matrice privata di oggetti Book . I singoli oggetti book
vengono restituiti per riferimento chiamando il relativo metodo GetBookByTitle .

public class Book


{
public string Author;
public string Title;
}

public class BookCollection


{
private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
};
private Book nobook = null;

public ref Book GetBookByTitle(string title)


{
for (int ctr = 0; ctr < books.Length; ctr++)
{
if (title == books[ctr].Title)
return ref books[ctr];
}
return ref nobook;
}

public void ListBooks()


{
foreach (var book in books)
{
Console.WriteLine($"{book.Title}, by {book.Author}");
}
Console.WriteLine();
}
}

Quando il chiamante archivia il valore restituito dal metodo GetBookByTitle come una variabile locale ref, le
modifiche apportate al valore restituito dal chiamante vengono riflesse nell'oggetto BookCollection , come
illustrato nell'esempio seguente.

var bc = new BookCollection();


bc.ListBooks();

ref var book = ref bc.GetBookByTitle("Call of the Wild, The");


if (book != null)
book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
// Call of the Wild, The, by Jack London
// Tale of Two Cities, A, by Charles Dickens
//
// Republic, The, by Plato
// Tale of Two Cities, A, by Charles Dickens
Specifiche del linguaggio C#
Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedere anche
Scrivi codice efficiente sicuro
Valori restituiti e variabili locali ref
Espressione condizionale ref
Passaggio di parametri
Parametri di metodo
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Modificatore del parametro out (Riferimenti per C#)
02/11/2020 • 7 minutes to read • Edit Online

La parola chiave out fa sì che gli argomenti vengono passati per riferimento. Imposta il parametro formale
come alias dell'argomento, che deve essere una variabile. In altre parole, qualsiasi operazione sul parametro
viene eseguita sull'argomento. È come la parola chiave ref, con la differenza che ref richiede l'inizializzazione
della variabile prima di essere passato. È anche come la parola chiave in, con la differenza che in non consente
al metodo chiamato di modificare il valore dell'argomento. Per usare un parametro out , la definizione del
metodo e il metodo chiamante devono usare in modo esplicito la parola chiave out . Ad esempio:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44

void OutArgExample(out int number)


{
number = 44;
}

NOTE
La parola chiave out può essere usata anche con un parametro di tipo generico per specificare che il parametro tipo è
covariante. Per altre informazioni sull'uso della parola chiave out in questo contesto, vedere out (Modificatore generico).

Le variabili passate come argomenti out non devono essere inizializzate prima di essere passate in una
chiamata al metodo. È necessario tuttavia che il metodo chiamato assegni un valore prima della restituzione del
metodo.
Le parole chiave in , ref e out non sono considerate parte della firma del metodo ai fini della risoluzione
dell'overload. Non è quindi possibile eseguirne l'overload se l'unica differenza è che un metodo accetta un
argomento ref o in e l'altro un argomento out . Il codice seguente, ad esempio, non verrà compilato:

class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}

L'overload è consentito, tuttavia, se un metodo accetta un argomento ref , in o out e l'altro non ha alcuno di
questi modificatori, come illustrato di seguito:

class OutOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(out int i) => i = 5;
}

Il compilatore sceglie il miglior overload creando una corrispondenza tra i modificatori di parametro nel sito di
chiamata e i modificatori di parametro utilizzati nella chiamata al metodo.
Le proprietà non sono variabili e quindi non possono essere passate come parametri out .
Non è possibile usare le parole chiave in , ref e out per i seguenti tipi di metodi:
Metodi asincroni definiti usando il modificatore async.
Metodi iteratori che includono un'istruzione yield return o yield break .

I metodi di estensione presentano inoltre le restrizioni seguenti:


out Non è possibile usare la parola chiave sul primo argomento di un metodo di estensione.
Non ref è possibile usare la parola chiave sul primo argomento di un metodo di estensione quando
l'argomento non è uno struct oppure un tipo generico non vincolato come uno struct.
in Non è possibile usare la parola chiave a meno che il primo argomento non sia uno struct. La in parola
chiave non può essere usata in alcun tipo generico, anche quando è vincolato a essere uno struct.

Dichiarazione di parametri out


La dichiarazione di un metodo con argomenti out è un classico espediente per restituire più valori. A partire da
C# 7,0, prendere in considerazione le Tuple di valori per scenari simili. Nell'esempio seguente viene usato out
per restituire tre variabili con una sola chiamata al metodo. Il terzo argomento è assegnato a null. In questo
modo i metodi restituiscono i valori facoltativamente.

void Method(out int answer, out string message, out string stillNull)
{
answer = 44;
message = "I've been returned";
stillNull = null;
}

int argNumber;
string argMessage, argDefault;
Method(out argNumber, out argMessage, out argDefault);
Console.WriteLine(argNumber);
Console.WriteLine(argMessage);
Console.WriteLine(argDefault == null);

// The example displays the following output:


// 44
// I've been returned
// True

Chiamata a un metodo con un argomento out


In C# 6 e nelle versioni precedenti è necessario dichiarare una variabile in un'istruzione separata prima di
passarla come argomento out . L'esempio seguente dichiara una variabile denominata number prima che
venga passata al metodo Int32.TryParse3, che tenta di convertire una stringa in numero.

string numberAsString = "1640";

int number;
if (Int32.TryParse(numberAsString, out number))
Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640
A partire da C# 7.0, è possibile dichiarare la variabile out nell'elenco degli argomenti della chiamata al metodo
anziché in una dichiarazione di variabile separata. Il codice prodotto risulta più compatto e leggibile e viene
impedita l'assegnazione accidentale di un valore alla variabile prima della chiamata al metodo. L'esempio
seguente è uguale all'esempio precedente, ad eccezione del fatto che definisce la variabile number nella
chiamata al metodo Int32.TryParse.

string numberAsString = "1640";

if (Int32.TryParse(numberAsString, out int number))


Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640

Nell'esempio precedente la variabile number è fortemente tipizzata come int . È anche possibile dichiarare una
variabile locale tipizzata in modo implicito, come avviene nell'esempio seguente.

string numberAsString = "1640";

if (Int32.TryParse(numberAsString, out var number))


Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Parametri di metodo
namespace (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave namespace è usata per dichiarare un ambito che contiene un set di oggetti correlati. È possibile
usare uno spazio dei nomi per organizzare gli elementi di codice e creare tipi univoci globali.

namespace SampleNamespace
{
class SampleClass { }

interface ISampleInterface { }

struct SampleStruct { }

enum SampleEnum { a, b }

delegate void SampleDelegate(int i);

namespace Nested
{
class SampleClass2 { }
}
}

Commenti
All'interno di uno spazio dei nomi è possibile dichiarare nessuno o più di uno dei tipi elencati di seguito:
un altro spazio dei nomi
class
interface
struct
enum
delegate
Il compilatore aggiunge uno spazio dei nomi predefinito indipendentemente dal fatto che venga dichiarato o
meno uno spazio dei nomi in modo esplicito in un file di origine C#. Questo spazio dei nomi senza nome,
talvolta chiamato spazio dei nomi globale, è presente in ogni file. Qualsiasi identificatore nello spazio dei nomi
globale può essere usato all'interno di uno spazio dei nomi denominato.
Gli spazi dei nomi hanno in modo implicito accesso pubblico, non modificabile. Per informazioni sui modificatori
di accesso che è possibile assegnare agli elementi all'interno di uno spazio dei nomi, vedere Modificatori di
accesso.
È possibile definire uno spazio dei nomi in una o più dichiarazioni. L'esempio seguente definisce due classi come
parte dello spazio dei nomi MyCompany :
namespace MyCompany.Proj1
{
class MyClass
{
}
}

namespace MyCompany.Proj1
{
class MyClass1
{
}
}

Esempio
L'esempio seguente illustra come chiamare un metodo statico in uno spazio dei nomi annidato.

namespace SomeNameSpace
{
public class MyClass
{
static void Main()
{
Nested.NestedNameSpaceClass.SayHello();
}
}

// a nested namespace
namespace Nested
{
public class NestedNameSpaceClass
{
public static void SayHello()
{
Console.WriteLine("Hello");
}
}
}
}
// Output: Hello

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Spazi dei nomi della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Parole chiave di C#
usando
utilizzo di static
Qualificatore di alias dello spazio dei nomi ::
Namespaces (Spazi dei nomi)
using (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La parola chiave using ha tre usi principali:


L'istruzione using definisce un ambito alla fine del quale viene eliminato un oggetto.
La direttiva using crea un alias per uno spazio dei nomi o importa tipi definiti in altri spazi dei nomi.
La direttiva using static importa i membri di una singola classe.

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Namespaces (Spazi dei nomi)
extern
Direttiva using (Riferimenti per C#)
28/01/2021 • 5 minutes to read • Edit Online

La direttiva using ha tre usi:


Consentire l'uso di tipi in uno spazio dei nomi in modo da non dover qualificare l'uso di un tipo in tale
spazio dei nomi:

using System.Text;

Consentire l'accesso ai membri statici e ai tipi nidificati di un tipo senza dover qualificare l'accesso con il
nome del tipo.

using static System.Math;

Per altre informazioni, vedere la direttiva using static.


Creare un alias per uno spazio dei nomi o un tipo. Si tratta di una direttiva alias using.

using Project = PC.MyCompany.Project;

La parola chiave using viene usata anche per creare istruzioni using, che garantiscono che gli oggetti
IDisposable, ad esempio file e tipi di carattere, vengano gestiti correttamente. Per altre informazioni, vedere
Istruzione using.

Tipo using static


È possibile accedere ai membri statici di un tipo senza dover qualificare l'accesso con il nome del tipo:

using static System.Console;


using static System.Math;
class Program
{
static void Main()
{
WriteLine(Sqrt(3*3 + 4*4));
}
}

Commenti
L'ambito di una direttiva using è limitato al file in cui viene visualizzata.
La direttiva using può essere visualizzata:
All'inizio del file del codice sorgente, prima di eventuali definizioni di spazio dei nomi o tipo.
In qualsiasi spazio dei nomi, ma prima di un spazio dei nomi o di tipi dichiarati nello spazio dei nomi.
In caso contrario, viene generato l'errore del compilatore CS1529.
Creare una direttiva alias using per semplificare la qualifica di un identificatore in uno spazio dei nomi o un
tipo. In una direttiva using è necessario usare lo spazio dei nomi o il tipo completo indipendentemente dalle
direttive using che la precedono. Non è possibile usare alcun alias using nella dichiarazione di una direttiva
using . Ad esempio, il codice seguente genera un errore del compilatore:

using s = System.Text;
using s.RegularExpressions; // Generates a compiler error.

Creare una direttiva using per usare i tipi in uno spazio dei nomi senza dover specificare tale spazio dei nomi.
Una direttiva using non offre accesso ad alcuno spazio dei nomi annidato nello spazio dei nomi specificato.
Gli spazi dei nomi sono disponibili in due categorie: definiti dall'utente e definiti dal sistema. Gli spazi dei nomi
definiti dall'utente vengono definiti nel codice. Per un elenco di spazi dei nomi definiti dal sistema, vedere
Browser API .NET.

Esempio 1
Nell'esempio seguente viene illustrato come definire e usare un alias using per uno spazio dei nomi.

namespace PC
{
// Define an alias for the nested namespace.
using Project = PC.MyCompany.Project;
class A
{
void M()
{
// Use the alias
var mc = new Project.MyClass();
}
}
namespace MyCompany
{
namespace Project
{
public class MyClass { }
}
}
}

Una direttiva alias using non può contenere un tipo generico aperto nella parte destra. Ad esempio, non è
possibile creare un alias using per List<T> , ma è possibile crearne uno per List<int> .

Esempio 2
Nell'esempio seguente viene illustrato come definire una direttiva using e un alias using per una classe:
using System;

// Using alias directive for a class.


using AliasToMyClass = NameSpace1.MyClass;

// Using alias directive for a generic class.


using UsingAlias = NameSpace2.MyClass<int>;

namespace NameSpace1
{
public class MyClass
{
public override string ToString()
{
return "You are in NameSpace1.MyClass.";
}
}
}

namespace NameSpace2
{
class MyClass<T>
{
public override string ToString()
{
return "You are in NameSpace2.MyClass.";
}
}
}

namespace NameSpace3
{
class MainClass
{
static void Main()
{
var instance1 = new AliasToMyClass();
Console.WriteLine(instance1);

var instance2 = new UsingAlias();


Console.WriteLine(instance2);
}
}
}
// Output:
// You are in NameSpace1.MyClass.
// You are in NameSpace2.MyClass.

Specifiche del linguaggio C#


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

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Uso degli spazi dei nomi
Parole chiave di C#
Namespaces (Spazi dei nomi)
Istruzione using
Direttiva using static (Riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

La direttiva using static consente di definire un tipo i cui membri statici e i tipi nidificati sono accessibili senza
specificare un nome di tipo. La relativa sintassi è la seguente:

using static <fully-qualified-type-name>;

dove fully-qualified-type-name è il nome del tipo i cui membri statici e i tipi nidificati possono essere usati come
riferimento senza specificare un nome di tipo. Se non si specifica un nome di tipo completo (il nome completo
dello spazio dei nomi con il nome del tipo), C# genera l'errore del compilatore CS0246: "Impossibile trovare il
nome del tipo o dello spazio dei nomi 'type/namespace' (probabilmente manca una direttiva using o un
riferimento a un assembly)".
La direttiva using static si applica a qualsiasi tipo che includa membri statici (o tipi nidificati), anche qualora
siano presenti membri di istanza. Tuttavia, i membri di istanza possono essere chiamati solo tramite l'istanza del
tipo.
La direttiva using static è stata introdotta in C# 6.

Commenti
Quando si chiama un membro statico si fornisce in genere il nome del tipo e il nome del membro. Immettere
ripetutamente lo stesso nome di tipo per chiamare i membri del tipo può generare codice troppo dettagliato e
incomprensibile. Ad esempio, la seguente definizione di una classe Circle fa riferimento a un numero di
membri della classe Math.

using System;

public class Circle


{
public Circle(double radius)
{
Radius = radius;
}

public double Radius { get; set; }

public double Diameter


{
get { return 2 * Radius; }
}

public double Circumference


{
get { return 2 * Radius * Math.PI; }
}

public double Area


{
get { return Math.PI * Math.Pow(Radius, 2); }
}
}
Eliminando la necessità di fare riferimento in modo esplicito alla classe Math ogni volta che si fa riferimento a
un membro, la direttiva using static genera un codice più chiaro:

using System;
using static System.Math;

public class Circle


{
public Circle(double radius)
{
Radius = radius;
}

public double Radius { get; set; }

public double Diameter


{
get { return 2 * Radius; }
}

public double Circumference


{
get { return 2 * Radius * PI; }
}

public double Area


{
get { return PI * Pow(Radius, 2); }
}
}

using static importa solo i membri statici accessibili e i tipi annidati dichiarati nel tipo specificato. I membri
ereditati non vengono importati. È possibile eseguire l'importazione da qualsiasi tipo denominato con una
direttiva using static, inclusi i moduli Visual Basic. Se nei metadati vengono visualizzate funzioni di primo livello
F# come membri statici di un tipo denominato il cui nome è un identificatore C# valido, le funzioni F# possono
essere importate.
using static crea metodi di estensione dichiarati nel tipo specificato disponibile per la ricerca del metodo di
estensione. Tuttavia, i nomi dei metodi di estensione non vengono importati nell'ambito del riferimento non
qualificato nel codice.
I metodi con lo stesso nome importati da tipi diversi tramite direttive using static diverse nella stessa unità di
compilazione o nello stesso spazio dei nomi costituiscono un gruppo di metodi. La risoluzione dell'overload in
questi gruppi di metodi segue le normali regole C#.

Esempio
L'esempio seguente usa la direttiva using static per rendere i membri statici della classe Console, Math e
String disponibili senza dover specificare il nome del tipo.
using System;
using static System.Console;
using static System.Math;
using static System.String;

class Program
{
static void Main()
{
Write("Enter a circle's radius: ");
var input = ReadLine();
if (!IsNullOrEmpty(input) && double.TryParse(input, out var radius)) {
var c = new Circle(radius);

string s = "\nInformation about the circle:\n";


s = s + Format(" Radius: {0:N2}\n", c.Radius);
s = s + Format(" Diameter: {0:N2}\n", c.Diameter);
s = s + Format(" Circumference: {0:N2}\n", c.Circumference);
s = s + Format(" Area: {0:N2}\n", c.Area);
WriteLine(s);
}
else {
WriteLine("Invalid input...");
}
}
}

public class Circle


{
public Circle(double radius)
{
Radius = radius;
}

public double Radius { get; set; }

public double Diameter


{
get { return 2 * Radius; }
}

public double Circumference


{
get { return 2 * Radius * PI; }
}

public double Area


{
get { return PI * Pow(Radius, 2); }
}
}
// The example displays the following output:
// Enter a circle's radius: 12.45
//
// Information about the circle:
// Radius: 12.45
// Diameter: 24.90
// Circumference: 78.23
// Area: 486.95

Nell'esempio la direttiva using static può anche essere stata applicata al tipo Double. In questo caso sarebbe
stato possibile chiamare il metodo TryParse(String, Double) senza specificare un nome di tipo. Tuttavia, in questo
modo viene creato codice meno leggibile, poiché diventa necessario controllare le using static direttive per
determinare quale metodo del tipo numerico TryParse viene chiamato.
Vedere anche
direttiva using
Riferimenti per C#
Parole chiave di C#
Uso degli spazi dei nomi
Namespaces (Spazi dei nomi)
Istruzione using (Riferimenti per C#)
28/01/2021 • 6 minutes to read • Edit Online

Offre una comoda sintassi che verifica l'uso corretto degli oggetti IDisposable. A partire da C# 8,0, l' using
istruzione assicura l'uso corretto degli IAsyncDisposable oggetti.

Esempio
L'esempio seguente mostra come usare l'istruzione using .

string manyLines=@"This is line one


This is line two
Here is line three
The penultimate line is line four
This is the final, fifth line.";

using (var reader = new StringReader(manyLines))


{
string? item;
do {
item = reader.ReadLine();
Console.WriteLine(item);
} while(item != null);
}

A partire da C# 8,0, è possibile usare la sintassi alternativa seguente per l' using istruzione che non richiede
parentesi graffe:

string manyLines=@"This is line one


This is line two
Here is line three
The penultimate line is line four
This is the final, fifth line.";

using var reader = new StringReader(manyLines);


string? item;
do {
item = reader.ReadLine();
Console.WriteLine(item);
} while(item != null);

Commenti
File e Font sono esempi di tipi gestiti che accedono a risorse non gestite (in questo caso handle di file e contesti
di dispositivo). Esistono molti altri tipi di risorse non gestite e tipi della libreria di classi che le incapsulano. Tutti
questi tipi devono implementare l' IDisposable interfaccia o l' IAsyncDisposable interfaccia.
Quando la durata di un oggetto IDisposable è limitata a un singolo metodo, è necessario dichiararlo e creare
un'istanza nell'istruzione using . L'istruzione using chiama il metodo Dispose sull'oggetto in modo corretto e,
quando viene usata come illustrato in precedenza, fa in modo che l'oggetto stesso esca dall'ambito non appena
viene chiamato il metodo Dispose. All'interno del using blocco l'oggetto è di sola lettura e non può essere
modificato o riassegnato. Se l'oggetto implementa IAsyncDisposable anziché IDisposable , l' using istruzione
chiama DisposeAsync e l'oggetto awaits restituito ValueTask . Per altre informazioni su IAsyncDisposable ,
vedere implementare un metodo DisposeAsync.
L' using istruzione garantisce che Dispose (o DisposeAsync ) venga chiamato anche se si verifica un'eccezione
all'interno del using blocco. È possibile ottenere lo stesso risultato inserendo l'oggetto all'interno di un try
blocco e chiamando Dispose (o DisposeAsync ) in un finally blocco; in realtà, questo è il modo in cui l' using
istruzione viene convertita dal compilatore. L'esempio di codice precedente si espande al codice seguente in
fase di compilazione (si notino le parentesi graffe aggiuntive per creare l'ambito limitato per l'oggetto):

string manyLines=@"This is line one


This is line two
Here is line three
The penultimate line is line four
This is the final, fifth line.";

{
var reader = new StringReader(manyLines);
try {
string? item;
do {
item = reader.ReadLine();
Console.WriteLine(item);
} while(item != null);
} finally
{
reader?.Dispose();
}
}

La sintassi dell'istruzione più recente using si traduce in codice simile. Il try blocco viene aperto in cui viene
dichiarata la variabile. Il finally blocco viene aggiunto alla chiusura del blocco di inclusione, in genere alla fine
di un metodo.
Per ulteriori informazioni sull' try - finally istruzione, vedere l'articolo try-finally .
Più istanze di un tipo possono essere dichiarate in una singola using istruzione, come illustrato nell'esempio
seguente. Si noti che non è possibile usare variabili tipizzate in modo implicito ( var ) quando si dichiarano più
variabili in un'unica istruzione:

string numbers=@"One
Two
Three
Four.";
string letters=@"A
B
C
D.";

using (StringReader left = new StringReader(numbers),


right = new StringReader(letters))
{
string? item;
do {
item = left.ReadLine();
Console.Write(item);
Console.Write(" ");
item = right.ReadLine();
Console.WriteLine(item);
} while(item != null);
}

È possibile combinare più dichiarazioni dello stesso tipo usando la nuova sintassi introdotta anche con C# 8,
come illustrato nell'esempio seguente:

string numbers=@"One
Two
Three
Four.";
string letters=@"A
B
C
D.";

using StringReader left = new StringReader(numbers),


right = new StringReader(letters);
string? item;
do {
item = left.ReadLine();
Console.Write(item);
Console.Write(" ");
item = right.ReadLine();
Console.WriteLine(item);
} while(item != null);

È possibile creare un'istanza dell'oggetto risorsa e quindi passare la variabile all' using istruzione, ma questa
non è una procedura consigliata. In questo caso l'oggetto rimane nell'ambito quando il controllo lascia il blocco
using , anche se probabilmente non avrà più accesso alle relative risorse non gestite. In altre parole, non è più
completamente inizializzato. Se si tenta di usare l'oggetto di fuori del blocco using , si rischia di causare la
generazione di un'eccezione. Per questo motivo, è preferibile creare un'istanza dell'oggetto nell' using
istruzione e limitarne l'ambito al using blocco.

string manyLines=@"This is line one


This is line two
Here is line three
The penultimate line is line four
This is the final, fifth line.";

var reader = new StringReader(manyLines);


using (reader)
{
string? item;
do {
item = reader.ReadLine();
Console.WriteLine(item);
} while(item != null);
}
// reader is in scope here, but has been disposed

Per altre informazioni sull'eliminazione degli oggetti IDisposable , vedere Uso di oggetti che implementano
IDisposable.

Specifiche del linguaggio C#


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

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Direttiva using
Garbage Collection
Uso di oggetti che implementano IDisposable
Interfaccia IDisposable
istruzione using in C# 8,0
extern alias (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

È necessario far riferimento a due versioni di assembly che dispongono degli stessi nomi di tipo completi. Ad
esempio, potrebbe essere necessario usare due o più versioni di un assembly nella stessa applicazione. Con un
alias di assembly esterno, è possibile eseguire il wrapping degli spazi dei nomi di ogni assembly all'interno degli
spazi dei nomi a livello radice denominati dall'alias, consentendone l'utilizzo nello stesso file.

NOTE
La parola chiave extern viene anche usata come modificatore di metodo, che dichiara un metodo scritto in codice non
gestito.

Per far riferimento a due assembly con gli stessi nomi di tipo completi, è necessario specificare un alias al
prompt dei comandi, come indicato di seguito:
/r:GridV1=grid.dll

/r:GridV2=grid20.dll

In questo modo vengono creati gli alias extern GridV1 e GridV2 . Per usare questi alias all'interno di un
programma, far riferimento a essi tramite la parola chiave extern . Ad esempio:
extern alias GridV1;

extern alias GridV2;

Ogni dichiarazione di alias extern introduce uno spazio dei nomi aggiuntivo a livello radice che affianca lo spazio
dei nomi globale, ma non si trova al suo interno. In questo modo, è possibile far riferimento ai tipi di ogni
assembly senza ambiguità, usando il nome completo che dispone di una radice nell'alias dello spazio dei nomi
appropriato.
Nell'esempio precedente, l'oggetto GridV1::Grid rappresenta il controllo griglia dell'oggetto grid.dll ,e
GridV2::Grid rappresenta il controllo griglia dell'oggetto grid20.dll .

Con Visual Studio


Se si usa Visual Studio, gli alias possono essere forniti in modo analogo.
Aggiungere un riferimento di grid.dll e grid20.dll al progetto in Visual Studio. Aprire una scheda delle proprietà
e modificare gli alias da Global a GridV1 e GridV2 rispettivamente.
Usare questi alias nello stesso modo precedente

extern alias GridV1;

extern alias GridV2;

A questo punto è possibile creare un alias per uno spazio dei nomi o un tipo usando la direttiva alias. Per altre
informazioni, vedere direttiva using.
using Class1V1 = GridV1::Namespace.Class1;

using Class1V2 = GridV2::Namespace.Class1;

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Operatore::
-reference (opzioni del compilatore C#)
is (Riferimenti per C#)
28/01/2021 • 12 minutes to read • Edit Online

L'operatore is controlla se il risultato di un'espressione è compatibile con un determinato tipo oppure (a


partire da C# 7.0) controlla un'espressione rispetto a un criterio. Per informazioni sull'operatore di test dei tipi
is , vedere la sezione operatore is nell'articolo relativo ai test di tipo e agli operatori cast .

Criteri di ricerca con is


A partire da C# 7.0, le istruzioni is e switch supportano i criteri di ricerca. La parola chiave is supporta i
criteri seguenti:
Modello di tipo, che verifica se un'espressione può essere convertita in un tipo specificato e, se possibile,
esegue il cast della variabile a una variabile di quel tipo.
Criterio costante, che verifica se un'espressione restituisce un valore costante specificato.
Criterio var, una corrispondenza che ha sempre esito positivo e associa il valore di un'espressione a una
nuova variabile locale.
Criterio del tipo
Quando si usa il criterio del tipo per eseguire i criteri di ricerca, is verifica se un'espressione può essere
convertita in un tipo specificato e, in tal caso, esegue il cast a una variabile di quel tipo. È una semplice
estensione dell'istruzione is che consente la valutazione e la conversione concisa del tipo. Il formato generale
del criterio del tipo è is :

expr is type varname

Dove expr è un'espressione che restituisce un'istanza di un tipo, Type è il nome del tipo in cui il risultato di expr
deve essere convertito e VarName è l'oggetto in cui il risultato di expr viene convertito se il is test è true .
L' is espressione è true se expr non è null e si verifica una delle condizioni seguenti:
expr è un'istanza dello stesso tipo di type.
expr è un'istanza di un tipo che deriva da type. In altre parole, il risultato di expr può subire l'upcast a
un'istanza di type.
expr ha un tipo in fase di compilazione che è una classe di base di type e expr ha un tipo di runtime che è
type o è derivato da type. Il tipo in fase di compilazione di una variabile è il tipo della variabile come definito
nella relativa dichiarazione. Il tipo di runtime di una variabile è il tipo dell'istanza che viene assegnato alla
variabile.
expr è un'istanza di un tipo che implementa l'interfaccia type.
A partire da C# 7.1, expr può avere un tipo in fase di compilazione definito da un parametro di tipo generico e
dai relativi vincoli.
Se expr è true e is viene usato con un'istruzione if , varname viene assegnato solo all'interno dell'istruzione
if . L'ambito di varname va dall'espressione is alla fine del blocco che racchiude l'istruzione if . L'utilizzo di
VarName in qualsiasi altra posizione genera un errore in fase di compilazione per l'utilizzo di una variabile che
non è stata assegnata.
Nell'esempio seguente viene usato il criterio del tipo is per specificare l'implementazione di un metodo
IComparable.CompareTo(Object) del tipo.
using System;

public class Employee : IComparable


{
public String Name { get; set; }
public int Id { get; set; }

public int CompareTo(Object o)


{
if (o is Employee e)
{
return Name.CompareTo(e.Name);
}
throw new ArgumentException("o is not an Employee object.");
}
}

Senza criteri di ricerca, questo codice potrebbe essere scritto come segue. L'uso di criteri di ricerca del tipo
produce codice più compatto e leggibile eliminando la necessità di verificare se il risultato di una conversione è
un null .

using System;

public class Employee : IComparable


{
public String Name { get; set; }
public int Id { get; set; }

public int CompareTo(Object o)


{
var e = o as Employee;
if (e == null)
{
throw new ArgumentException("o is not an Employee object.");
}
return Name.CompareTo(e.Name);
}
}

Anche il criterio del tipo is produce codice più compatto quando determina il tipo di un tipo di valore.
Nell'esempio seguente viene usato il criterio del tipo is per determinare se un oggetto è un'istanza Person o
Dog prima di visualizzare il valore di una proprietà appropriata.
using System;

public class Example


{
public static void Main()
{
Object o = new Person("Jane");
ShowValue(o);

o = new Dog("Alaskan Malamute");


ShowValue(o);
}

public static void ShowValue(object o)


{
if (o is Person p) {
Console.WriteLine(p.Name);
}
else if (o is Dog d) {
Console.WriteLine(d.Breed);
}
}
}

public struct Person


{
public string Name { get; set; }

public Person(string name) : this()


{
Name = name;
}
}

public struct Dog


{
public string Breed { get; set; }

public Dog(string breedName) : this()


{
Breed = breedName;
}
}
// The example displays the following output:
// Jane
// Alaskan Malamute

Il codice equivalente senza criteri di ricerca richiede un'assegnazione separata che include un cast esplicito.
using System;

public class Example


{
public static void Main()
{
Object o = new Person("Jane");
ShowValue(o);

o = new Dog("Alaskan Malamute");


ShowValue(o);
}

public static void ShowValue(object o)


{
if (o is Person) {
Person p = (Person) o;
Console.WriteLine(p.Name);
}
else if (o is Dog) {
Dog d = (Dog) o;
Console.WriteLine(d.Breed);
}
}
}

public struct Person


{
public string Name { get; set; }

public Person(string name) : this()


{
Name = name;
}
}

public struct Dog


{
public string Breed { get; set; }

public Dog(string breedName) : this()


{
Breed = breedName;
}
}
// The example displays the following output:
// Jane
// Alaskan Malamute

Criterio costante
Quando si eseguono criteri di ricerca con il criterio costante, is verifica se un'espressione è uguale a una
costante specificata. In C# 6 e versioni precedenti, il criterio costante è supportato per l'istruzione switch. A
partire da C# 7.0 è supportato anche dall'istruzione is . La relativa sintassi è la seguente:

expr is constant

dove expr è l'espressione da valutare e constant è il valore da testare. constant può essere una delle espressioni
costanti seguenti:
Valore letterale.
Il nome di una variabile const dichiarata.
Una costante di enumerazione.
L'espressione costante viene valutata nel modo seguente:
Se expr e constant sono tipi integrali, l'operatore di uguaglianza C# determina se l'espressione restituisce
true (ovvero, se expr == constant ).

In caso contrario, il valore dell'espressione è determinato da una chiamata al metodo Object.Equals(expr,


constant) statico.
Nell'esempio seguente vengono combinati i criteri di tipo e costante per verificare se un oggetto è un'istanza
Dice e, in tal caso, per determinare se il valore di un tiro di dadi è 6.

using System;

public class Dice


{
Random rnd = new Random();
public Dice()
{
}
public int Roll()
{
return rnd.Next(1, 7);
}
}

class Program
{
static void Main(string[] args)
{
var d1 = new Dice();
ShowValue(d1);
}

private static void ShowValue(object o)


{
const int HIGH_ROLL = 6;

if (o is Dice d && d.Roll() is HIGH_ROLL)


Console.WriteLine($"The value is {HIGH_ROLL}!");
else
Console.WriteLine($"The dice roll is not a {HIGH_ROLL}!");
}
}
// The example displays output like the following:
// The value is 6!

Verifica che null possa essere eseguito usando il criterio costante. La parola chiave null è supportata
dall'istruzione is . La relativa sintassi è la seguente:

expr is null

L'esempio seguente illustra un confronto di controlli null :


using System;

class Program
{
static void Main(string[] args)
{
object o = null;

if (o is null)
{
Console.WriteLine("o does not have a value");
}
else
{
Console.WriteLine($"o is {o}");
}

int? x = 10;

if (x is null)
{
Console.WriteLine("x does not have a value");
}
else
{
Console.WriteLine($"x is {x.Value}");
}

// 'null' check comparison


Console.WriteLine($"'is' constant pattern 'null' check result : { o is null }");
Console.WriteLine($"object.ReferenceEquals 'null' check result : { object.ReferenceEquals(o, null)
}");
Console.WriteLine($"Equality operator (==) 'null' check result : { o == null }");
}

// The example displays the following output:


// o does not have a value
// x is 10
// 'is' constant pattern 'null' check result : True
// object.ReferenceEquals 'null' check result : True
// Equality operator (==) 'null' check result : True
}

L'espressione x is null viene calcolata in modo diverso per i tipi di riferimento e i tipi di valore Nullable. Per i
tipi di valore Nullable, viene utilizzato Nullable<T>.HasValue . Per i tipi di riferimento, viene utilizzato
(object)x == null .

Criterio var
Una corrispondenza con il var modello ha sempre esito positivo. La relativa sintassi è la seguente:

expr is var varname

Dove il valore di expr viene sempre assegnato a una variabile locale denominata VarName. VarName è una
variabile dello stesso tipo del tipo in fase di compilazione di expr.
Se expr restituisce null , l' is espressione produce true e assegna null a VarName. Il modello var è uno
dei pochi usi di is che produce true per un null valore.
È possibile usare il var modello per creare una variabile temporanea in un'espressione booleana, come
illustrato nell'esempio seguente:
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
static void Main()
{
int[] testSet = { 100271, 234335, 342439, 999683 };

var primes = testSet.Where(n => Factor(n).ToList() is var factors


&& factors.Count == 2
&& factors.Contains(1)
&& factors.Contains(n));

foreach (int prime in primes)


{
Console.WriteLine($"Found prime: {prime}");
}
}

static IEnumerable<int> Factor(int number)


{
int max = (int)Math.Sqrt(number);
for (int i = 1; i <= max; i++)
{
if (number % i == 0)
{
yield return i;
if (i != number / i)
{
yield return number / i;
}
}
}
}
}
// The example displays the following output:
// Found prime: 100271
// Found prime: 999683

Nell'esempio precedente, la variabile temporanea viene utilizzata per archiviare il risultato di un'operazione
costosa. La variabile può quindi essere usata più volte.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatore is della specifica del linguaggio C# e le proposte di
linguaggio C# seguenti:
Criteri di ricerca
Criteri di ricerca con generics

Vedi anche
Informazioni di riferimento su C#
Parole chiave di C#
Operatori di cast e di test del tipo
Vincolo new (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Il vincolo new specifica che un argomento tipo in una dichiarazione di classe generica deve avere un costruttore
pubblico senza parametri. Per usare il vincolo new , il tipo non può essere astratto.
Applicare il vincolo new a un parametro di tipo quando una classe generica crea nuove istanze del tipo, come
illustrato nell'esempio seguente:

class ItemFactory<T> where T : new()


{
public T GetNewItem()
{
return new T();
}
}

Quando il vincolo new() viene usato con altri vincoli, è necessario specificarlo per ultimo:

public class ItemFactory2<T>


where T : IComparable, new()
{ }

Per altre informazioni, vedere Vincoli sui parametri di tipo.


È anche possibile usare la parola chiave new per creare un'istanza di un tipo o come modificatore di
dichiarazione di membro.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Vincoli del parametro di tipo della specifica del linguaggio C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Generics
where (vincolo di tipo generico) (Riferimenti per C#)
02/11/2020 • 8 minutes to read • Edit Online

La clausola where in una definizione generica specifica i vincoli per i tipi che vengono usati come argomenti per
i parametri di tipo in un tipo generico, metodo, delegato o funzione locale. I vincoli possono specificare
interfacce, classi base o richiedere che un tipo generico sia un riferimento, un valore o un tipo non gestito.
Dichiarano le funzionalità che l'argomento di tipo deve avere.
Ad esempio, una classe generica, MyGenericClass , può essere dichiarata in modo che tramite il parametro di tipo
T venga implementata l'interfaccia IComparable<T>:

public class AGenericClass<T> where T : IComparable<T> { }

NOTE
Per altre informazioni sulla clausola where in un'espressione di query, vedere Clausola where.

La clausola where può inoltre includere un vincolo di classe di base. Il vincolo della classe base dichiara che un
tipo da usare come argomento di tipo per il tipo generico ha la classe specificata come classe di base o è tale
classe base. Se viene usato il vincolo della classe di base, deve apparire prima di tutti gli altri vincoli per quel
parametro di tipo. Alcuni tipi non sono consentiti come vincoli di classe di base: Object, Array e ValueType. Prima
di C# 7,3,, Enum Delegate e non MulticastDelegate sono consentiti come vincoli di classe di base. L'esempio
seguente illustra i tipi possono ora essere specificati come una classe di base:

public class UsingEnum<T> where T : System.Enum { }

public class UsingDelegate<T> where T : System.Delegate { }

public class Multicaster<T> where T : System.MulticastDelegate { }

In un contesto nullable in C# 8,0 e versioni successive, viene applicato il supporto di valori null del tipo di classe
base. Se la classe base è non Nullable (ad esempio Base ), l'argomento di tipo deve essere non nullable. Se la
classe di base ammette i valori null (ad esempio Base? ), l'argomento di tipo può essere un tipo di riferimento
nullable o non nullable. Il compilatore genera un avviso se l'argomento di tipo è un tipo di riferimento Nullable
quando la classe base è non nullable.
La clausola where può specificare che il tipo è un oggetto class o struct . Il vincolo struct elimina la
necessità di specificare un vincolo di classe di base di System.ValueType . Il tipo System.ValueType non può
essere usato come vincolo di classe di base. Nell'esempio seguente vengono illustrati i vincoli class e struct :

class MyClass<T, U>


where T : class
where U : struct
{ }

In un contesto nullable in C# 8,0 e versioni successive, il class vincolo richiede che un tipo sia un tipo di
riferimento non nullable. Per consentire i tipi di riferimento Nullable, usare il class? vincolo, che consente i tipi
di riferimento nullable e non nullable.
La where clausola può includere il notnull vincolo. Il notnull vincolo limita il parametro di tipo ai tipi che non
ammettono valori null. Il tipo può essere un tipo di valore o un tipo di riferimento non nullable. Il notnull
vincolo è disponibile a partire da C# 8,0 per il codice compilato in un nullable enable contesto. Diversamente
da altri vincoli, se un argomento di tipo viola il notnull vincolo, il compilatore genera un avviso anziché un
errore. Gli avvisi vengono generati solo in un nullable enable contesto.

IMPORTANT
Le dichiarazioni generiche che includono il notnull vincolo possono essere utilizzate in un contesto ignaro Nullable, ma
il compilatore non impone il vincolo.

#nullable enable
class NotNullContainer<T>
where T : notnull
{
}
#nullable restore

La clausola where può anche includere un vincolo unmanaged . Il vincolo unmanaged limita il parametro di tipo ai
tipi noti come tipi non gestiti. Il vincolo unmanaged rende più semplice la scrittura di codice di interoperabilità di
basso livello in C#. Questo vincolo abilita le routine riutilizzabili in tutti i tipi non gestiti. Il vincolo unmanaged non
può essere combinato con il vincolo class o struct . Il vincolo unmanaged impone che il tipo deve essere un
elemento struct :

class UnManagedWrapper<T>
where T : unmanaged
{ }

La clausola where può anche includere un vincolo di costruttore, new() . Tale vincolo consente di creare
un'istanza di un parametro di tipo usando l'operatore new . Il vincolo New () consente al compilatore di
verificare che qualsiasi argomento di tipo fornito disponga di un costruttore senza parametri accessibile. Ad
esempio:

public class MyGenericClass<T> where T : IComparable<T>, new()


{
// The following line is not possible without new() constraint:
T item = new T();
}

Il vincolo new() viene visualizzato per ultimo nella clausola where . Il vincolo new() non può essere combinato
con i vincoli struct o unmanaged . Tutti i tipi che soddisfano i vincoli devono avere un costruttore senza
parametri accessibile, per rendere il vincolo new() ridondante.
Con più parametri di tipo, usare una clausola where per ogni parametro di tipo, ad esempio:
public interface IMyInterface { }

namespace CodeExample
{
class Dictionary<TKey, TVal>
where TKey : IComparable<TKey>
where TVal : IMyInterface
{
public void Add(TKey key, TVal val) { }
}
}

È anche possibile associare vincoli ai parametri di tipo di metodi generici, come illustrato nell'esempio seguente:

public void MyMethod<T>(T t) where T : IMyInterface { }

Si noti che la sintassi usata per descrivere i vincoli dei parametri di tipo per i delegati è uguale a quella dei
metodi:

delegate T MyDelegate<T>() where T : new();

Per informazioni sui delegati generici, vedere Delegati generici.


Per informazioni dettagliate sulla sintassi e sull'uso dei vincoli, vedere Vincoli sui parametri di tipo.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Introduzione ai generics
nuovo vincolo
Vincoli sui parametri di tipo
base (Riferimenti per C#)
18/03/2020 • 3 minutes to read • Edit Online

La parola chiave base viene usata per accedere ai membri della classe di base dall'interno di una classe
derivata:
Chiamare un metodo sulla classe di base che è stato sostituito da un altro metodo.
Specificare quale costruttore di classe di base deve essere chiamato durante la creazione di istanze della
classe derivata.
Una classe di base di accesso è consentita solo in un costruttore, in un metodo di istanza o in una funzione di
accesso alla proprietà dell'istanza.
Non è possibile usare la parola chiave base dall'interno di un metodo statico.
La classe di base alla quale si accede è la classe di base specificata nella dichiarazione di classe. Ad esempio, se si
specifica class ClassB : ClassA , i membri di ClassA sono accessibili da ClassB, indipendentemente dalla classe
di base di ClassA.

Esempio
In questo esempio, la classe di base Person e la classe derivata Employee hanno un metodo denominato
Getinfo . Tramite la parola chiave base è possibile chiamare il meotod Getinfo sulla classe di base, dall'interno
della classe derivata.
public class Person
{
protected string ssn = "444-55-6666";
protected string name = "John L. Malgraine";

public virtual void GetInfo()


{
Console.WriteLine("Name: {0}", name);
Console.WriteLine("SSN: {0}", ssn);
}
}
class Employee : Person
{
public string id = "ABC567EFG";
public override void GetInfo()
{
// Calling the base class GetInfo method:
base.GetInfo();
Console.WriteLine("Employee ID: {0}", id);
}
}

class TestClass
{
static void Main()
{
Employee E = new Employee();
E.GetInfo();
}
}
/*
Output
Name: John L. Malgraine
SSN: 444-55-6666
Employee ID: ABC567EFG
*/

Per altri esempi, vedere new, virtual e override.

Esempio
Questo esempio illustra come specificare il costruttore della classe di base chiamato durante la creazione di
istanze di una classe derivata.
public class BaseClass
{
int num;

public BaseClass()
{
Console.WriteLine("in BaseClass()");
}

public BaseClass(int i)
{
num = i;
Console.WriteLine("in BaseClass(int i)");
}

public int GetNum()


{
return num;
}
}

public class DerivedClass : BaseClass


{
// This constructor will call BaseClass.BaseClass()
public DerivedClass() : base()
{
}

// This constructor will call BaseClass.BaseClass(int i)


public DerivedClass(int i) : base(i)
{
}

static void Main()


{
DerivedClass md = new DerivedClass();
DerivedClass md1 = new DerivedClass(1);
}
}
/*
Output:
in BaseClass()
in BaseClass(int i)
*/

Specifiche del linguaggio C#


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

Vedere anche
Guida di riferimento a C
Guida per programmatori C#
Parole chiave di C#
Questo
this (Riferimenti per C#)
18/03/2020 • 2 minutes to read • Edit Online

La parola chiave this fa riferimento all'istanza corrente della classe e viene anche usata come modificatore del
primo parametro di un metodo di estensione.

NOTE
Questo articolo illustra l'uso di this con istanze della classe. Per altre informazioni sull'uso nei metodi di estensione,
vedere Metodi di estensione.

Di seguito sono riportati gli usi comuni di this :


Per qualificare i membri nascosti da nomi simili, ad esempio:

public class Employee


{
private string alias;
private string name;

public Employee(string name, string alias)


{
// Use this to qualify the members of the class
// instead of the constructor parameters.
this.name = name;
this.alias = alias;
}
}

Per passare un oggetto come parametro ad altri metodi, ad esempio:

CalcTax(this);

Per dichiarare gli indicizzatori, ad esempio:

public int this[int param]


{
get { return array[param]; }
set { array[param] = value; }
}

Le funzioni del membro statico, perché esistono a livello di classe e non come parte di un oggetto, non
dispongono di un puntatore this . È un errore fare riferimento a this in un metodo statico.

Esempio
In questo esempio, this viene usato per qualificare i membri della classe Employee , name e alias , che sono
nascosti da nomi simili. Viene anche usato per passare un oggetto al metodo CalcTax , che appartiene a un'altra
classe.
class Employee
{
private string name;
private string alias;
private decimal salary = 3000.00m;

// Constructor:
public Employee(string name, string alias)
{
// Use this to qualify the fields, name and alias:
this.name = name;
this.alias = alias;
}

// Printing method:
public void printEmployee()
{
Console.WriteLine("Name: {0}\nAlias: {1}", name, alias);
// Passing the object to the CalcTax method by using this:
Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
}

public decimal Salary


{
get { return salary; }
}
}

class Tax
{
public static decimal CalcTax(Employee E)
{
return 0.08m * E.Salary;
}
}

class MainClass
{
static void Main()
{
// Create objects:
Employee E1 = new Employee("Mingda Pan", "mpan");

// Display results:
E1.printEmployee();
}
}
/*
Output:
Name: Mingda Pan
Alias: mpan
Taxes: $240.00
*/

Specifiche del linguaggio C#


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

Vedere anche
Guida di riferimento a C
Guida per programmatori C#
Parole chiave di C#
base
Metodi
null (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave null è un valore letterale che rappresenta un riferimento Null, ovvero un riferimento che non
fa riferimento a un oggetto. null è il valore predefinito delle variabili di tipo riferimento. I tipi di valore normali
non possono essere null, ad eccezione dei tipi di valore Nullable.
Nell'esempio seguente vengono illustrati alcuni comportamenti della null parola chiave:
class Program
{
class MyClass
{
public void MyMethod() { }
}

static void Main(string[] args)


{
// Set a breakpoint here to see that mc = null.
// However, the compiler considers it "unassigned."
// and generates a compiler error if you try to
// use the variable.
MyClass mc;

// Now the variable can be used, but...


mc = null;

// ... a method call on a null object raises


// a run-time NullReferenceException.
// Uncomment the following line to see for yourself.
// mc.MyMethod();

// Now mc has a value.


mc = new MyClass();

// You can call its method.


mc.MyMethod();

// Set mc to null again. The object it referenced


// is no longer accessible and can now be garbage-collected.
mc = null;

// A null string is not the same as an empty string.


string s = null;
string t = String.Empty; // Logically the same as ""

// Equals applied to any null object returns false.


bool b = (t.Equals(s));
Console.WriteLine(b);

// Equality operator also returns false when one


// operand is null.
Console.WriteLine("Empty string {0} null string", s == t ? "equals": "does not equal");

// Returns true.
Console.WriteLine("null == null is {0}", null == null);

// A value type cannot be null


// int i = null; // Compiler error!

// Use a nullable value type instead:


int? i = null;

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Informazioni di riferimento su C#
Parole chiave di C#
Valori predefiniti dei tipi C#
Nothing (Visual Basic)
bool (riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La bool parola chiave Type è un alias per il System.Boolean tipo di struttura .NET che rappresenta un valore
booleano, che può essere true o false .
Per eseguire operazioni logiche con valori di bool tipo, utilizzare operatori logici booleani . Il bool tipo è il tipo
di risultato degli operatori di confronto e di uguaglianza . Un' bool espressione può essere un'espressione
condizionale di controllo nelle istruzioni if, do, whilee for e nell' operatore ?: condizionale .
Il valore predefinito del bool tipo è false .

Valori letterali
È possibile usare i true false valori letterali e per inizializzare una bool variabile o per passare un bool
valore:

bool check = true;


Console.WriteLine(check ? "Checked" : "Not checked"); // output: Checked

Console.WriteLine(false ? "Checked" : "Not checked"); // output: Not checked

Logica booleana a tre valori


Usare il bool? tipo nullable, se è necessario supportare la logica a tre valori, ad esempio quando si lavora con
database che supportano un tipo booleano a tre valori. Per gli operandi bool? , gli operatori & e | predefiniti
supportano la logica a tre valori. Per altre informazioni, vedere la sezione Operatori logici booleani nullable
dell'articolo Operatori logici booleani.
Per ulteriori informazioni sui tipi di valore Nullable, vedere tipi di valore Nullable.

Conversioni
In C# sono disponibili solo due conversioni che coinvolgono il bool tipo. Si tratta di una conversione implicita
nel tipo nullable corrispondente bool? e di una conversione esplicita dal bool? tipo. .NET fornisce tuttavia
metodi aggiuntivi che è possibile usare per eseguire la conversione da o verso il bool tipo. Per ulteriori
informazioni, vedere la sezione conversione da e verso valori booleani della System.Boolean pagina di
riferimento all'API.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione tipo bool della specifica del linguaggio C#.

Vedi anche
Informazioni di riferimento su C#
Tipi valore
Operatori true e false
default (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave default può essere usata in due modi:


Per specificare l'etichetta predefinita nell' switch istruzione.
Come operatore default o valore letterale per produrre il valore predefinito di un tipo.

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Parole chiave contestuali (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Una parola chiave contestuale viene usata per conferire un significato particolare nel codice, ma non è una
parola riservata in C#. Questa sezione presenta le seguenti parole chiave contestuali:

PA RO L A C H IAVE DESC RIZ IO N E

add Definisce una funzione di accesso eventi personalizzata che


viene chiamata quando il codice client esegue la
sottoscrizione all'evento.

async Indica che il metodo specificato, l'espressione lambda o il


metodo anonimo è asincrono.

await Sospende un metodo asincrono finché non viene completata


un'attività attesa.

dinamico Definisce un tipo di riferimento che abilita operazioni in cui il


tipo appare per ignorare il controllo del tipo in fase di
compilazione.

get Definisce un metodo di accesso per una proprietà o un


indicizzatore.

globale Alias dello spazio dei nomi globale, che altrimenti non è
provvisto di nome.

parziale Definisce classi, struct e interfacce parziali all'interno della


stessa unità di compilazione.

remove Definisce una funzione di accesso eventi personalizzata che


viene chiamata quando il codice client annulla la
sottoscrizione all'evento.

set Definisce un metodo di accesso per una proprietà o un


indicizzatore.

value Viene usata per impostare metodi di accesso e per


aggiungere o rimuovere gestori eventi.

var Consente che il tipo di una variabile dichiarata nell'ambito


del metodo sia determinato dal compilatore.

Quando Specifica una condizione di filtro per un blocco catch o


l'etichetta case di un'istruzione switch .

where Aggiunge vincoli a una dichiarazione generica. (Vedere anche


where).
PA RO L A C H IAVE DESC RIZ IO N E

yield Viene usata in un blocco iteratore per la restituzione di un


valore all'oggetto enumeratore o per segnalare la fine
dell'iterazione.

Anche tutte le parole chiave di query introdotte in C# 3.0 sono contestuali. Per altre informazioni, vedere Parole
chiave di query (LINQ).

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
add (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale add viene usata per definire una funzione di accesso a eventi personalizzata che
viene chiamata quando il codice client sottoscrive l'evento. Se si specifica una funzione di accesso add
personalizzata, è necessario specificare anche una funzione di accesso remove.

Esempio
L'esempio seguente mostra un evento con le funzioni di accesso add personalizzata e remove. Per l'esempio
completo, vedere come implementare eventi di interfaccia.

class Events : IDrawingObject


{
event EventHandler PreDrawEvent;

event EventHandler IDrawingObject.OnDraw


{
add => PreDrawEvent += value;
remove => PreDrawEvent -= value;
}
}

In genere, non è necessario fornire funzioni di accesso a eventi personalizzate. Le funzioni di accesso generate
automaticamente dal compilatore quando si dichiara un evento sono sufficienti per la maggior parte degli
scenari.

Vedere anche
Eventi
get (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La parola chiave get definisce un metodo funzione di accesso in una proprietà o indicizzatore che restituisce il
valore della proprietà o l'elemento dell'indicizzatore. Per altre informazioni, vedere Proprietà, Proprietà
implementate automaticamente e Indicizzatori.
L'esempio seguente definisce le funzioni di accesso get e set per una proprietà denominata Seconds . Usa il
campo privato denominato _seconds per portare in secondo piano il valore della proprietà.

class TimePeriod
{
private double _seconds;

public double Seconds


{
get { return _seconds; }
set { _seconds = value; }
}
}

Spesso la funzione di accesso get è costituita da una singola istruzione che restituisce un valore, come
nell'esempio precedente. A partire da C# 7.0, è possibile implementare la funzione di accesso get come
membro con corpo di espressione. L'esempio seguente implementa entrambe le funzioni di accesso get e set
come membri con corpo di espressione.

class TimePeriod
{
private double _seconds;

public double Seconds


{
get => _seconds;
set => _seconds = value;
}
}

Per i casi semplici in cui le funzioni di accesso get e set di una proprietà non eseguono operazioni diverse
dall'impostazione o recupero di un valore in un campo sottostante, è possibile sfruttare il supporto del
compilatore C# per le proprietà implementate automaticamente. L'esempio seguente implementa Hours come
una proprietà implementata automaticamente.

class TimePeriod2
{
public double Hours { get; set; }
}

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Proprietà
partial (Tipo) (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Le definizioni dei tipi parziali consentono la suddivisione in più file della definizione di una classe, di una
struttura, di un'interfaccia o di un record.
In file1.cs:

namespace PC
{
partial class A
{
int num = 0;
void MethodA() { }
partial void MethodC();
}
}

In file2.cs la dichiarazione:

namespace PC
{
partial class A
{
void MethodB() { }
partial void MethodC() { }
}
}

Osservazioni
La suddivisione di un tipo di classe, struttura o interfaccia in più file può essere utile per progetti di grandi
dimensioni o quando si usa codice generato automaticamente come quello fornito da Progettazione Windows
Form. Un tipo parziale può contenere un metodo parziale. Per altre informazioni, vedere Classi e metodi parziali.

Specifiche del linguaggio C#


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

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Modificatori
Introduzione ai generics
Metodo parziale (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Un metodo parziale ha la firma definita in una parte di un tipo parziale e l'implementazione definita in un'altra
parte del tipo. I metodi parziali consentono a Progettazione classi di fornire gli hook del metodo, analoghi ai
gestori eventi, che gli sviluppatori possono decidere se implementare o meno. Se lo sviluppatore non fornisce
un'implementazione, il compilatore rimuove la firma in fase di compilazione. Ai metodi parziali si applicano le
condizioni seguenti:
Le firme nelle due parti del tipo parziale devono corrispondere.
Il metodo deve restituire void.
Non è consentito alcun modificatore di accesso. I metodi parziali sono implicitamente privati.
Nell'esempio seguente viene illustrato un metodo parziale definito in due parti di una classe parziale:

namespace PM
{
partial class A
{
partial void OnSomethingHappened(string s);
}

// This part can be in a separate file.


partial class A
{
// Comment out this method and the program
// will still compile.
partial void OnSomethingHappened(String s)
{
Console.WriteLine("Something happened: {0}", s);
}
}
}

Per altre informazioni, vedere Classi e metodi parziali.

Vedere anche
Riferimenti per C#
tipo parziale
remove (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale remove viene usata per definire una funzione di accesso a eventi personalizzata
che viene chiamata quando il codice client annulla la sottoscrizione all'evento. Se si specifica una funzione di
accesso remove personalizzata, è necessario specificare anche una funzione di accesso add.

Esempio
L'esempio seguente mostra un evento con le funzioni di accesso add personalizzata e remove . Per l'esempio
completo, vedere come implementare eventi di interfaccia.

class Events : IDrawingObject


{
event EventHandler PreDrawEvent;

event EventHandler IDrawingObject.OnDraw


{
add => PreDrawEvent += value;
remove => PreDrawEvent -= value;
}
}

In genere, non è necessario fornire funzioni di accesso a eventi personalizzate. Le funzioni di accesso generate
automaticamente dal compilatore quando si dichiara un evento sono sufficienti per la maggior parte degli
scenari.

Vedere anche
Eventi
set (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave set definisce un metodo funzione di accesso in una proprietà o indicizzatore che assegna un
valore alla proprietà o all'elemento dell'indicizzatore. Per altre informazioni ed esempi, vedere Proprietà,
Proprietà implementate automaticamente e Indicizzatori.
L'esempio seguente definisce le funzioni di accesso get e set per una proprietà denominata Seconds . Usa il
campo privato denominato _seconds per portare in secondo piano il valore della proprietà.

class TimePeriod
{
private double _seconds;

public double Seconds


{
get { return _seconds; }
set { _seconds = value; }
}
}

Spesso la funzione di accesso set è costituita da una singola istruzione che assegna un valore, come
nell'esempio precedente. A partire da C# 7.0, è possibile implementare la funzione di accesso set come
membro con corpo di espressione. L'esempio seguente implementa entrambe le funzioni di accesso get e set
come membri con corpo di espressione.

class TimePeriod
{
private double _seconds;

public double Seconds


{
get => _seconds;
set => _seconds = value;
}
}

Per i casi semplici in cui le funzioni di accesso get e set di una proprietà non eseguono operazioni diverse
dall'impostazione o recupero di un valore in un campo sottostante, è possibile sfruttare il supporto del
compilatore C# per le proprietà implementate automaticamente. L'esempio seguente implementa Hours come
una proprietà implementata automaticamente.

class TimePeriod2
{
public double Hours { get; set; }
}

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
Proprietà
when (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

È possibile utilizzare la when parola chiave contestuale per specificare una condizione di filtro nei contesti
seguenti:
Nell'istruzione catch di un blocco try/catch o try/catch/finally.
Nell'etichetta case di un'istruzione switch.
Nell' switch espressione.

when in un'istruzione catch


A partire da C# 6 when può essere usata in un'istruzione catch per specificare una condizione che deve essere
vera per eseguire il gestore di una determinata eccezione. La relativa sintassi è la seguente:

catch (ExceptionType [e]) when (expr)

dove expr è un'espressione che dà come risultato un valore booleano. Se restituisce true , il gestore di eccezioni
viene eseguito, se restituisce false , non viene eseguito.
Nell'esempio seguente viene usata la parola chiave when per eseguire in modo condizionale i gestori per un
elemento HttpRequestException in base al testo del messaggio dell'eccezione.
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
static void Main()
{
Console.WriteLine(MakeRequest().Result);
}

public static async Task<string> MakeRequest()


{
var client = new HttpClient();
var streamTask = client.GetStringAsync("https://localHost:10000");
try
{
var responseText = await streamTask;
return responseText;
}
catch (HttpRequestException e) when (e.Message.Contains("301"))
{
return "Site Moved";
}
catch (HttpRequestException e) when (e.Message.Contains("404"))
{
return "Page Not Found";
}
catch (HttpRequestException e)
{
return e.Message;
}
}
}

when in un'istruzione switch


A partire da C# 7.0 non è più necessario che le etichette case siano reciprocamente esclusive e l'ordine in cui le
etichette case appaiono in un'istruzione switch può determinare quale blocco switch eseguire. La parola
chiave when può essere usata per specificare una condizione di filtro che fa sì che l'etichetta case associata sia
vera solo se è vera anche la condizione di filtro. La relativa sintassi è la seguente:

case (expr) when (when-condition):

dove expr è un modello costante o un modello del tipo che viene confrontato con l'espressione di
corrispondenza e when-condition è qualsiasi espressione booleana.
Nell'esempio seguente viene usata la parola chiave when per testare gli oggetti Shape che hanno un'area pari a
zero, nonché una varietà di oggetti Shape che hanno un'area maggiore di zero.

using System;

public abstract class Shape


{
public abstract double Area { get; }
public abstract double Circumference { get; }
}

public class Rectangle : Shape


{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}

public double Length { get; set; }


public double Width { get; set; }

public override double Area


{
get { return Math.Round(Length * Width,2); }
}

public override double Circumference


{
get { return (Length + Width) * 2; }
}
}

public class Square : Rectangle


{
public Square(double side) : base(side, side)
{
Side = side;
}

public double Side { get; set; }


}

public class Example


{
public static void Main()
{
Shape sh = null;
Shape[] shapes = { new Square(10), new Rectangle(5, 7),
new Rectangle(10, 10), sh, new Square(0) };
foreach (var shape in shapes)
ShowShapeInfo(shape);
}

private static void ShowShapeInfo(Object obj)


{
switch (obj)
{
case Shape shape when shape.Area == 0:
Console.WriteLine($"The shape: {shape.GetType().Name} with no dimensions");
break;
case Square sq when sq.Area > 0:
Console.WriteLine("Information about the square:");
Console.WriteLine($" Length of a side: {sq.Side}");
Console.WriteLine($" Area: {sq.Area}");
break;
case Rectangle r when r.Area > 0:
Console.WriteLine("Information about the rectangle:");
Console.WriteLine($" Dimensions: {r.Length} x {r.Width}");
Console.WriteLine($" Area: {r.Area}");
break;
case Shape shape:
Console.WriteLine($"A {shape.GetType().Name} shape");
break;
case null:
Console.WriteLine($"The {nameof(obj)} variable is uninitialized.");
break;
default:
Console.WriteLine($"The {nameof(obj)} variable does not represent a Shape.");
break;
}
}
}
}
// The example displays the following output:
// Information about the square:
// Length of a side: 10
// Area: 100
// Information about the rectangle:
// Dimensions: 5 x 7
// Area: 35
// Information about the rectangle:
// Dimensions: 10 x 10
// Area: 100
// The obj variable is uninitialized.
// The shape: Square with no dimensions

Vedere anche
istruzione switch
Istruzione try/catch
istruzione try/catch/finally
value (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale value viene usata nella set funzione di accesso nelle dichiarazioni di proprietà e
indicizzatori . È simile a un parametro di input di un metodo. La parola value fa riferimento al valore che il
codice client sta tentando di assegnare alla proprietà o all'indicizzatore. Nell'esempio seguente, MyDerivedClass
ha una proprietà denominata Name che usa il parametro value per assegnare una nuova stringa al campo
sottostante name . Dal punto di vista del codice client, l'operazione viene scritta come assegnazione semplice.

class MyBaseClass
{
// virtual auto-implemented property. Overrides can only
// provide specialized behavior if they implement get and set accessors.
public virtual string Name { get; set; }

// ordinary virtual property with backing field


private int num;
public virtual int Number
{
get { return num; }
set { num = value; }
}
}

class MyDerivedClass : MyBaseClass


{
private string name;

// Override auto-implemented property with ordinary property


// to provide specialized accessor behavior.
public override string Name
{
get
{
return name;
}
set
{
if (!string.IsNullOrEmpty(value))
{
name = value;
}
else
{
name = "Unknown";
}
}
}
}

Per ulteriori informazioni, vedere gli articoli Proprietà e indicizzatori .

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Parole chiave di C#
yield (Riferimenti per C#)
28/01/2021 • 7 minutes to read • Edit Online

Quando si usa la parola chiave contestuale yield in un'istruzione, si indica che il metodo, l'operatore o la
funzione di accesso get in cui appare è un iteratore. Utilizzando yield per definire un iteratore, si elimina la
necessità di una classe esplicita aggiuntiva (la classe che contiene lo stato per un'enumerazione, vedere
IEnumerator<T> per un esempio) quando si implementano i modelli IEnumerable e di IEnumerator per un tipo
di raccolta personalizzato.
Nell'esempio seguente vengono illustrate le due forme dell'istruzione yield .

yield return <expression>;


yield break;

Commenti
Si utilizza un'istruzione yield return per restituire un elemento alla volta.
La sequenza restituita da un metodo iteratore può essere usata con un'istruzione foreach o una query LINQ.
Ogni iterazione del ciclo foreach chiama il metodo iteratore. Quando si raggiunge un'istruzione yield return
nel metodo iteratore, viene restituito expression e viene mantenuta la posizione corrente nel codice.
L'esecuzione viene riavviata a partire da quella posizione la volta successiva che viene chiamata la funzione
iteratore.
È possibile utilizzare un'istruzione yield break per terminare l'iterazione.
Per altre informazioni sugli iteratori, vedere Iteratori.

Metodi e funzioni di accesso get dell'iteratore


La dichiarazione di un iteratore deve soddisfare i seguenti requisiti:
Il tipo restituito deve essere IEnumerable, IEnumerable<T>, IEnumerator o IEnumerator<T>.
La dichiarazione non può contenere parametri in, refo out .
Il tipo yield di un iteratore che restituisce IEnumerable o IEnumerator è object . Se l'iteratore restituisce
IEnumerable<T> o IEnumerator<T>, deve essere presente una conversione implicita dal tipo dell'espressione
nell'istruzione yield return al parametro di tipo generico.
Non è possibile includere un'istruzione yield return o yield break in:
Espressioni lambda e metodi anonimi.
Metodi contenenti blocchi unsafe. Per altre informazioni, vedere unsafe.

Gestione delle eccezioni


Un'istruzione yield return non può essere inclusa in un blocco try-catch. Un'istruzione yield return può
essere inclusa nel blocco try di un'istruzione try-finally.
Un'istruzione yield break può essere inclusa in un blocco try o in un blocco catch ma non in un blocco finally.
Se il corpo di foreach (esterno al metodo iteratore) genera un'eccezione, viene eseguito un blocco finally nel
metodo iteratore.

Implementazione tecnica
Il codice seguente restituisce IEnumerable<string> da un metodo iteratore e quindi scorre i relativi elementi.

IEnumerable<string> elements = MyIteratorMethod();


foreach (string element in elements)
{
...
}

La chiamata a MyIteratorMethod non esegue il corpo del metodo. La chiamata restituisce invece
IEnumerable<string> nella variabile elements .
In un'iterazione del ciclo foreach , il metodo MoveNext viene chiamato per elements . Questa chiamata esegue il
corpo di MyIteratorMethod fino a quando non viene raggiunta l'istruzione yield return successiva.
L'espressione restituita dall'istruzione yield return determina non solo il valore della variabile element per
l'utilizzo da parte del corpo del ciclo, ma anche la proprietà Current di elements , che è IEnumerable<string> .
In ogni iterazione successiva del ciclo foreach , l'esecuzione del corpo dell'iteratore continua da dove è stata
interrotta, fermandosi ancora quando raggiunge un'istruzione yield return . Il ciclo foreach termina quando si
raggiunge la fine del metodo iteratore o un'istruzione yield break .

Esempio
L'esempio seguente contiene un'istruzione yield return all'interno di un ciclo for . Ogni iterazione del corpo
dell'istruzione foreach nel metodo Main crea una chiamata alla funzione iteratore Power . Ogni chiamata alla
funzione iteratore procede fino alla prossima esecuzione dell'istruzione yield return , che si verifica durante
l'iterazione successiva del ciclo for .
Il tipo restituito del metodo iteratore è IEnumerable, ovvero un tipo di interfaccia iteratore. Quando il metodo
iteratore viene chiamato, restituisce un oggetto enumerabile che contiene le potenze di un numero.
public class PowersOf2
{
static void Main()
{
// Display powers of 2 up to the exponent of 8:
foreach (int i in Power(2, 8))
{
Console.Write("{0} ", i);
}
}

public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent)


{
int result = 1;

for (int i = 0; i < exponent; i++)


{
result = result * number;
yield return result;
}
}

// Output: 2 4 8 16 32 64 128 256


}

Esempio
Nell'esempio seguente viene illustrata una funzione di accesso get che è un iteratore. Nell'esempio, ogni
istruzione yield return restituisce un'istanza di una classe definita dall'utente.

public static class GalaxyClass


{
public static void ShowGalaxies()
{
var theGalaxies = new Galaxies();
foreach (Galaxy theGalaxy in theGalaxies.NextGalaxy)
{
Debug.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears.ToString());
}
}

public class Galaxies


{

public System.Collections.Generic.IEnumerable<Galaxy> NextGalaxy


{
get
{
yield return new Galaxy { Name = "Tadpole", MegaLightYears = 400 };
yield return new Galaxy { Name = "Pinwheel", MegaLightYears = 25 };
yield return new Galaxy { Name = "Milky Way", MegaLightYears = 0 };
yield return new Galaxy { Name = "Andromeda", MegaLightYears = 3 };
}
}
}

public class Galaxy


{
public String Name { get; set; }
public int MegaLightYears { get; set; }
}
}
Specifiche del linguaggio C#
Per ulteriori informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il
riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
foreach, in
Iterators
Parole chiave di query (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa sezione contiene le parole chiave contestuali usate nelle espressioni di query.

Contenuto della sezione


C L A USO L A DESC RIZ IO N E

from Specifica un'origine dati e una variabile di intervallo (simile a


una variabile di iterazione).

where Filtra gli elementi di origine in base a una o più espressioni


booleane separate da operatori logici AND e OR ( && o ||
).

select Specifica il tipo e la forma che avranno gli elementi nella


sequenza restituita quando verrà eseguita la query.

utenti Raggruppa i risultati delle query in base a un valore di chiave


specificato.

into Fornisce un identificatore che può servire come riferimento


ai risultati di una clausola join, group o select.

OrderBy Ordina i risultati delle query in ordine crescente o


decrescente in base all'operatore di confronto predefinito per
il tipo di elemento.

join Unisce due origini dati in base a un confronto di uguaglianza


tra due criteri di corrispondenza specificati.

let Introduce una variabile di intervallo per archiviare i risultati


delle sottoespressioni in un'espressione di query.

in Parola chiave contestuale in una clausola join.

on Parola chiave contestuale in una clausola join.

equals Parola chiave contestuale in una clausola join.

by Parola chiave contestuale in una clausola group.

ascending Parola chiave contestuale in una clausola orderby.

descending Parola chiave contestuale in una clausola orderby.

Vedere anche
Parole chiave di C#
LINQ (Language-Integrated Query)
LINQ in C#
Clausola from (Riferimento C#)
28/01/2021 • 8 minutes to read • Edit Online

Un'espressione di query deve iniziare con una clausola from . Inoltre, un'espressione di query può contenere
sottoquery che iniziano anch'esse con una clausola from . La clausola from specifica gli elementi seguenti:
Origine dati su cui verrà eseguita la query o la sottoquery.
Variabile di intervallo locale che rappresenta ogni elemento nella sequenza di origine.
Sia la variabile di intervallo che l'origine dati sono fortemente tipizzate. L'origine dati a cui si fa riferimento nella
clausola from deve essere di tipo IEnumerable, IEnumerable<T> o di un tipo derivato, IQueryable<T>.
Nell'esempio seguente numbers è l'origine dati e num è la variabile di intervallo. Si noti che entrambe le
variabili sono fortemente tipizzate anche se viene usata la parola chiave var.

class LowNums
{
static void Main()
{
// A simple data source.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// Create the query.


// lowNums is an IEnumerable<int>
var lowNums = from num in numbers
where num < 5
select num;

// Execute the query.


foreach (int i in lowNums)
{
Console.Write(i + " ");
}
}
}
// Output: 4 1 3 2 0

Variabile di intervallo
Tramite l'inferenza, il compilatore deriva il tipo della variabile di intervallo quando l'origine dati implementa
IEnumerable<T>. Se, ad esempio, l'origine è di tipo IEnumerable<Customer> , la variabile di intervallo derivata
tramite inferenza sarà Customer . È necessario specificare il tipo in modo esplicito solo quando l'origine è un tipo
IEnumerable non generico, ad esempio ArrayList. Per ulteriori informazioni, vedere come eseguire una query su
un ArrayList con LINQ.
Nell'esempio precedente si deriva tramite inferenza che num è di tipo int . Poiché la variabile di intervallo è
fortemente tipizzata, è possibile chiamare metodi su di essa o usarla in altre operazioni. Ad esempio, invece di
scrivere select num , è possibile scrivere select num.ToString() per fare in modo che l'espressione di query
restituisca una sequenza di stringhe invece che di numeri interi. Oppure è possibile scrivere select num + 10
per fare in modo che l'espressione restituisca la sequenza 14, 11, 13, 12 10. Per ulteriori informazioni, vedere
clausola SELECT.
La variabile di intervallo è analoga a una variabile di iterazione in un'istruzione foreach eccetto che per una
differenza molto importante: una variabile di intervallo non archivia effettivamente mai i dati dall'origine. Si
tratta semplicemente di un pratico aspetto sintattico che consente alla query di descrivere ciò che si verificherà
alla sua esecuzione. Per altre informazioni, vedere Introduzione alle query LINQ (C#).

Clausole from composte


In alcuni casi, ogni elemento nella sequenza di origine può essere esso stesso una sequenza o contenere una
sequenza. Ad esempio, l'origine dati può essere un oggetto IEnumerable<Student> in cui ogni oggetto studente
della sequenza contiene un elenco di punteggi dei test. Per accedere all'elenco interno in ogni elemento
Student è possibile usare clausole from composte. La tecnica è analoga all'uso di istruzioni foreach nidificate. È
possibile aggiungere clausole where o orderby a una delle due clausole from per filtrare i risultati. L'esempio
seguente mostra una sequenza di oggetti Student , ognuno dei quali contiene un oggetto List interno di
numeri interi che rappresentano i punteggi dei test. Per accedere all'elenco interno, usare una clausola from
composta. Se necessario, è possibile inserire clausole tra le due clausole from .
class CompoundFrom
{
// The element type of the data source.
public class Student
{
public string LastName { get; set; }
public List<int> Scores {get; set;}
}

static void Main()


{

// Use a collection initializer to create the data source. Note that


// each element in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {LastName="Omelchenko", Scores= new List<int> {97, 72, 81, 60}},
new Student {LastName="O'Donnell", Scores= new List<int> {75, 84, 91, 39}},
new Student {LastName="Mortensen", Scores= new List<int> {88, 94, 65, 85}},
new Student {LastName="Garcia", Scores= new List<int> {97, 89, 85, 82}},
new Student {LastName="Beebe", Scores= new List<int> {35, 72, 91, 70}}
};

// Use a compound from to access the inner sequence within each element.
// Note the similarity to a nested foreach statement.
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };

// Execute the queries.


Console.WriteLine("scoreQuery:");
// Rest the mouse pointer on scoreQuery in the following line to
// see its type. The type is IEnumerable<'a>, where 'a is an
// anonymous type defined as new {string Last, int score}. That is,
// each instance of this anonymous type has two members, a string
// (Last) and an int (score).
foreach (var student in scoreQuery)
{
Console.WriteLine("{0} Score: {1}", student.Last, student.score);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/*
scoreQuery:
Omelchenko Score: 97
O'Donnell Score: 91
Mortensen Score: 94
Garcia Score: 97
Beebe Score: 91
*/

Uso di più clausole from per eseguire join


Una clausola from composta viene usata per accedere a raccolte interne in una singola origine dati. Una query
può tuttavia contenere anche più clausole from che generano query supplementari da origini dati indipendenti.
Questa tecnica consente di eseguire determinati tipi di operazioni di join che non sono possibili usando la
clausola join.
L'esempio seguente mostra in che modo due clausole from possono essere usate per formare un cross join
completo di due origini dati.

class CompoundFrom2
{
static void Main()
{
char[] upperCase = { 'A', 'B', 'C' };
char[] lowerCase = { 'x', 'y', 'z' };

// The type of joinQuery1 is IEnumerable<'a>, where 'a


// indicates an anonymous type. This anonymous type has two
// members, upper and lower, both of type char.
var joinQuery1 =
from upper in upperCase
from lower in lowerCase
select new { upper, lower };

// The type of joinQuery2 is IEnumerable<'a>, where 'a


// indicates an anonymous type. This anonymous type has two
// members, upper and lower, both of type char.
var joinQuery2 =
from lower in lowerCase
where lower != 'x'
from upper in upperCase
select new { lower, upper };

// Execute the queries.


Console.WriteLine("Cross join:");
// Rest the mouse pointer on joinQuery1 to verify its type.
foreach (var pair in joinQuery1)
{
Console.WriteLine("{0} is matched to {1}", pair.upper, pair.lower);
}

Console.WriteLine("Filtered non-equijoin:");
// Rest the mouse pointer over joinQuery2 to verify its type.
foreach (var pair in joinQuery2)
{
Console.WriteLine("{0} is matched to {1}", pair.lower, pair.upper);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Cross join:
A is matched to x
A is matched to y
A is matched to z
B is matched to x
B is matched to y
B is matched to z
C is matched to x
C is matched to y
C is matched to z
Filtered non-equijoin:
y is matched to A
y is matched to B
y is matched to C
z is matched to A
z is matched to B
z is matched to C
*/
Per altre informazioni sulle operazioni di join che usano più clausole from , vedere Eseguire left outer join.

Vedere anche
Parole chiave di query (LINQ)
LINQ (Language-Integrated Query)
Clausola where (Riferimento C#)
28/01/2021 • 4 minutes to read • Edit Online

La clausola where viene usata in un'espressione di query per specificare quali elementi dell'origine dati
verranno restituiti nell'espressione di query. Viene applicata una condizione booleana (predicato) a ogni
elemento di origine (a cui fa riferimento la variabile di intervallo) e viene restituita quella per cui la condizione
specificata è vera. Una singola espressione di query può contenere più clausole where e una singola clausola
può contenere più sottoespressioni di predicato.

Esempio
Nell'esempio seguente la clausola where esclude tutti i numeri tranne quelli minori di cinque. Se si rimuove la
clausola where , vengono restituiti tutti i numeri dell'origine dati. L'espressione num < 5 è il predicato che viene
applicato a ogni elemento.

class WhereSample
{
static void Main()
{
// Simple data source. Arrays support IEnumerable<T>.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// Simple query with one predicate in where clause.


var queryLowNums =
from num in numbers
where num < 5
select num;

// Execute the query.


foreach (var s in queryLowNums)
{
Console.Write(s.ToString() + " ");
}
}
}
//Output: 4 1 3 2 0

Esempio
All'interno di una singola where clausola è possibile specificare tutti i predicati necessari usando gli &&
operatori e || . Nell'esempio seguente la query specifica due predicati per selezionare solo i numeri pari minori
di cinque.
class WhereSample2
{
static void Main()
{
// Data source.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// Create the query with two predicates in where clause.


var queryLowNums2 =
from num in numbers
where num < 5 && num % 2 == 0
select num;

// Execute the query


foreach (var s in queryLowNums2)
{
Console.Write(s.ToString() + " ");
}
Console.WriteLine();

// Create the query with two where clause.


var queryLowNums3 =
from num in numbers
where num < 5
where num % 2 == 0
select num;

// Execute the query


foreach (var s in queryLowNums3)
{
Console.Write(s.ToString() + " ");
}
}
}
// Output:
// 4 2 0
// 4 2 0

Esempio
Una clausola where può contenere uno o più metodi che restituiscono valori booleani. Nell'esempio seguente la
clausola where usa un metodo per determinare se il valore corrente della variabile di intervallo è pari o dispari.
class WhereSample3
{
static void Main()
{
// Data source
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// Create the query with a method call in the where clause.


// Note: This won't work in LINQ to SQL unless you have a
// stored procedure that is mapped to a method by this name.
var queryEvenNums =
from num in numbers
where IsEven(num)
select num;

// Execute the query.


foreach (var s in queryEvenNums)
{
Console.Write(s.ToString() + " ");
}
}

// Method may be instance method or static method.


static bool IsEven(int i)
{
return i % 2 == 0;
}
}
//Output: 4 8 6 2 0

Osservazioni
La clausola where è un meccanismo di filtro. Può essere posizionata praticamente ovunque in un'espressione di
query, ma non può essere la prima o l'ultima clausola. Una clausola where la può apparire prima o dopo una
clausola group a seconda se gli elementi di origine devono essere filtrati prima o dopo essere stati raggruppati.
Se un predicato specificato non è valido per gli elementi nell'origine dati, si verificherà un errore in fase di
compilazione. Questo è un vantaggio del controllo del tipo forte fornito da LINQ.
In fase di compilazione, la parola chiave where viene convertita in una chiamata al metodo Where
dell'operatore query standard.

Vedere anche
Parole chiave di query (LINQ)
Clausola from
clausola SELECT
Filtraggio dei dati
LINQ in C#
LINQ (Language-Integrated Query)
Clausola select (Riferimento C#)
28/01/2021 • 7 minutes to read • Edit Online

In un'espressione di query, la clausola select specifica il tipo di valori che verranno prodotti quando viene
eseguita la query. Il risultato è basato sulla valutazione di tutte le clausole precedenti e su qualsiasi espressione
nella clausola select stessa. Un'espressione di query deve terminare con una clausola select o una clausola
group.
Nell'esempio seguente viene illustrata una semplice clausola select in un'espressione di query.

class SelectSample1
{
static void Main()
{
//Create the data source
List<int> Scores = new List<int>() { 97, 92, 81, 60 };

// Create the query.


IEnumerable<int> queryHighScores =
from score in Scores
where score > 80
select score;

// Execute the query.


foreach (int i in queryHighScores)
{
Console.Write(i + " ");
}
}
}
//Output: 97 92 81

Il tipo della sequenza prodotto dalla clausola select determina il tipo della variabile di query queryHighScores .
Nel caso più semplice, la clausola select specifica solo la variabile di intervallo. In tal modo, la sequenza
restituita contiene elementi dello stesso tipo dell'origine dati. Per altre informazioni, vedere relazioni tra i tipi
nelle operazioni di query LINQ. Tuttavia, la clausola select fornisce anche un potente meccanismo per la
trasformazione (o la proiezione) dell'origine dati in nuovi tipi. Per altre informazioni, vedere Trasformazioni dati
con LINQ (C#).

Esempio
Nell'esempio seguente sono illustrate tutte le diverse forme che una clausola select può avere. In ogni query
si noti la relazione tra la select clausola e il tipo della variabile di query ( studentQuery1 , studentQuery2 e così
via).

class SelectSample2
{
// Define some classes
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
public ContactInfo GetContactInfo(SelectSample2 app, int id)
{
{
ContactInfo cInfo =
(from ci in app.contactList
where ci.ID == id
select ci)
.FirstOrDefault();

return cInfo;
}

public override string ToString()


{
return First + " " + Last + ":" + ID;
}
}

public class ContactInfo


{
public int ID { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public override string ToString() { return Email + "," + Phone; }
}

public class ScoreInfo


{
public double Average { get; set; }
public int ID { get; set; }
}

// The primary data source


List<Student> students = new List<Student>()
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int>() {97, 92, 81,
60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int>() {75, 84, 91,
39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int>() {88, 94, 65, 91}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int>() {97, 89, 85, 82}},
};

// Separate data source for contact info.


List<ContactInfo> contactList = new List<ContactInfo>()
{
new ContactInfo {ID=111, Email="[email protected]", Phone="206-555-0108"},
new ContactInfo {ID=112, Email="[email protected]", Phone="206-555-0298"},
new ContactInfo {ID=113, Email="[email protected]", Phone="206-555-1130"},
new ContactInfo {ID=114, Email="[email protected]", Phone="206-555-0521"}
};

static void Main(string[] args)


{
SelectSample2 app = new SelectSample2();

// Produce a filtered sequence of unmodified Students.


IEnumerable<Student> studentQuery1 =
from student in app.students
where student.ID > 111
select student;

Console.WriteLine("Query1: select range_variable");


foreach (Student s in studentQuery1)
{
Console.WriteLine(s.ToString());
}

// Produce a filtered sequence of elements that contain


// only one property of each Student.
IEnumerable<String> studentQuery2 =
from student in app.students
from student in app.students
where student.ID > 111
select student.Last;

Console.WriteLine("\r\n studentQuery2: select range_variable.Property");


foreach (string s in studentQuery2)
{
Console.WriteLine(s);
}

// Produce a filtered sequence of objects created by


// a method call on each Student.
IEnumerable<ContactInfo> studentQuery3 =
from student in app.students
where student.ID > 111
select student.GetContactInfo(app, student.ID);

Console.WriteLine("\r\n studentQuery3: select range_variable.Method");


foreach (ContactInfo ci in studentQuery3)
{
Console.WriteLine(ci.ToString());
}

// Produce a filtered sequence of ints from


// the internal array inside each Student.
IEnumerable<int> studentQuery4 =
from student in app.students
where student.ID > 111
select student.Scores[0];

Console.WriteLine("\r\n studentQuery4: select range_variable[index]");


foreach (int i in studentQuery4)
{
Console.WriteLine("First score = {0}", i);
}

// Produce a filtered sequence of doubles


// that are the result of an expression.
IEnumerable<double> studentQuery5 =
from student in app.students
where student.ID > 111
select student.Scores[0] * 1.1;

Console.WriteLine("\r\n studentQuery5: select expression");


foreach (double d in studentQuery5)
{
Console.WriteLine("Adjusted first score = {0}", d);
}

// Produce a filtered sequence of doubles that are


// the result of a method call.
IEnumerable<double> studentQuery6 =
from student in app.students
where student.ID > 111
select student.Scores.Average();

Console.WriteLine("\r\n studentQuery6: select expression2");


foreach (double d in studentQuery6)
{
Console.WriteLine("Average = {0}", d);
}

// Produce a filtered sequence of anonymous types


// that contain only two properties from each Student.
var studentQuery7 =
from student in app.students
where student.ID > 111
select new { student.First, student.Last };

Console.WriteLine("\r\n studentQuery7: select new anonymous type");


Console.WriteLine("\r\n studentQuery7: select new anonymous type");
foreach (var item in studentQuery7)
{
Console.WriteLine("{0}, {1}", item.Last, item.First);
}

// Produce a filtered sequence of named objects that contain


// a method return value and a property from each Student.
// Use named types if you need to pass the query variable
// across a method boundary.
IEnumerable<ScoreInfo> studentQuery8 =
from student in app.students
where student.ID > 111
select new ScoreInfo
{
Average = student.Scores.Average(),
ID = student.ID
};

Console.WriteLine("\r\n studentQuery8: select new named type");


foreach (ScoreInfo si in studentQuery8)
{
Console.WriteLine("ID = {0}, Average = {1}", si.ID, si.Average);
}

// Produce a filtered sequence of students who appear on a contact list


// and whose average is greater than 85.
IEnumerable<ContactInfo> studentQuery9 =
from student in app.students
where student.Scores.Average() > 85
join ci in app.contactList on student.ID equals ci.ID
select ci;

Console.WriteLine("\r\n studentQuery9: select result of join clause");


foreach (ContactInfo ci in studentQuery9)
{
Console.WriteLine("ID = {0}, Email = {1}", ci.ID, ci.Email);
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output
Query1: select range_variable
Claire O'Donnell:112
Sven Mortensen:113
Cesar Garcia:114

studentQuery2: select range_variable.Property


O'Donnell
Mortensen
Garcia

studentQuery3: select range_variable.Method


[email protected],206-555-0298
[email protected],206-555-1130
[email protected],206-555-0521

studentQuery4: select range_variable[index]


First score = 75
First score = 88
First score = 97

studentQuery5: select expression


Adjusted first score = 82.5
Adjusted first score = 96.8
Adjusted first score = 106.7
studentQuery6: select expression2
Average = 72.25
Average = 84.5
Average = 88.25

studentQuery7: select new anonymous type


O'Donnell, Claire
Mortensen, Sven
Garcia, Cesar

studentQuery8: select new named type


ID = 112, Average = 72.25
ID = 113, Average = 84.5
ID = 114, Average = 88.25

studentQuery9: select result of join clause


ID = 114, Email = [email protected]
*/

Come illustrato in studentQuery8 nell'esempio precedente, in alcuni casi è preferibile che gli elementi della
sequenza restituita contengano solo un subset delle proprietà degli elementi di origine. Riducendo al minimo le
dimensioni della sequenza restituita, è possibile ridurre i requisiti di memoria e aumentare la velocità di
esecuzione della query. A tale scopo, è possibile creare un tipo anonimo nella clausola select e usare un
inizializzatore di oggetto per inizializzarlo con le proprietà appropriate dall'elemento di origine. Per un esempio
di come eseguire questa operazione, vedere Inizializzatori di oggetto e di raccolta.

Osservazioni
In fase di compilazione, la clausola select viene convertita in una chiamata al metodo per l'operatore query
standard Select.

Vedere anche
Riferimenti per C#
Parole chiave di query (LINQ)
Clausola from
parziale (Metodo) (Riferimenti per C#)
Tipi anonimi
LINQ in C#
LINQ (Language-Integrated Query)
Clausola group (Riferimento C#)
28/01/2021 • 12 minutes to read • Edit Online

La clausola group restituisce una sequenza di oggetti IGrouping<TKey,TElement> che contengono zero o più
elementi corrispondenti al valore chiave per il gruppo. Ad esempio, è possibile raggruppare una sequenza di
stringhe in base alla prima lettera di ogni stringa. In questo caso, la prima lettera è la chiave con tipo char e
viene archiviata nella proprietà Key di ogni oggetto IGrouping<TKey,TElement>. Il compilatore deduce
automaticamente il tipo della chiave.
È possibile terminare un'espressione di query con una clausola group , come illustrato nell'esempio seguente:

// Query variable is an IEnumerable<IGrouping<char, Student>>


var studentQuery1 =
from student in students
group student by student.Last[0];

Per eseguire operazioni di query aggiuntive per ogni gruppo, è possibile specificare un identificatore
temporaneo usando la parola chiave contestuale into. Quando si usa into , è necessario continuare a eseguire
la query ed eventualmente terminarla con un'istruzione select o un'altra clausola group , come illustrato nel
seguente estratto di codice:

// Group students by the first letter of their last name


// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
from student in students
group student by student.Last[0] into g
orderby g.Key
select g;

Esempi di utilizzo di più completi di group con e senza into sono disponibili nella sezione Esempio di questo
articolo.

Enumerazione dei risultati di una query con raggruppamento


Poiché gli oggetti IGrouping<TKey,TElement> prodotti da una query group sono essenzialmente un elenco di
elenchi, è necessario usare un ciclo foreach nidificato per accedere agli elementi di ogni gruppo. Il ciclo esterno
esegue l'iterazione delle chiavi del gruppo e il ciclo interno esegue l'iterazione di ogni voce nel gruppo. Un
gruppo può avere una chiave ma non elementi. Di seguito è riportato il ciclo foreach che esegue la query negli
esempi di codice precedenti:

// Iterate group items with a nested foreach. This IGrouping encapsulates


// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
// Explicit type for student could also be used here.
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}
Tipi di chiave
Le chiavi di raggruppamento possono essere di qualsiasi tipo, ad esempio una stringa, un tipo numerico
incorporato, un tipo con nome definito dall'utente o un tipo anonimo.
Raggruppamento per stringa
Negli esempi di codice precedenti è stato usato un oggetto char . Si potrebbe specificare in alternativa una
chiave di stringa, ad esempio il cognome completo:

// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;

Raggruppamento per valore booleano


L'esempio seguente illustra l'uso di un valore booleano per una chiave in modo da dividere i risultati in due
gruppi. Si noti che il valore è generato da una sottoespressione nella clausola group .
class GroupSample1
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}

public static List<Student> GetStudents()


{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81,
60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};

return students;
}

static void Main()


{
// Obtain the data source.
List<Student> students = GetStudents();

// Group by true or false.


// Query variable is an IEnumerable<IGrouping<bool, Student>>
var booleanGroupQuery =
from student in students
group student by student.Scores.Average() >= 80; //pass or fail!

// Execute the query and access items in each group


foreach (var studentGroup in booleanGroupQuery)
{
Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Low averages
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
High averages
Mortensen, Sven:93.5
Garcia, Debra:88.25
*/

Raggruppamento per intervallo numerico


Nell'esempio seguente viene usata un'espressione per creare le chiavi di raggruppamento numeriche che
rappresentano un intervallo percentile. Si noti l'uso di let come comoda posizione di archiviazione del risultato
della chiamata a un metodo, in modo da non dover chiamare il metodo due volte nella clausola group . Per altre
informazioni su come usare in modo sicuro i metodi nelle espressioni di query, vedere gestire le eccezioni nelle
espressioni di query.

class GroupSample2
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}

public static List<Student> GetStudents()


{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81,
60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};

return students;
}

// This method groups students into percentile ranges based on their


// grade average. The Average method returns a double, so to produce a whole
// number it is necessary to cast to int before dividing by 10.
static void Main()
{
// Obtain the data source.
List<Student> students = GetStudents();

// Write the query.


var studentQuery =
from student in students
let avg = (int)student.Scores.Average()
group student by (avg / 10) into g
orderby g.Key
select g;

// Execute the query.


foreach (var studentGroup in studentQuery)
{
int temp = studentGroup.Key * 10;
Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Students with an average between 70 and 80
Students with an average between 70 and 80
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
Students with an average between 80 and 90
Garcia, Debra:88.25
Students with an average between 90 and 100
Mortensen, Sven:93.5
*/

Raggruppamento per chiavi composte


Usare una chiave composta se si vuole raggruppare gli elementi in base a più di una chiave. Per creare una
chiave composta, usare un tipo anonimo o un tipo denominato per contenere l'elemento key. Nell'esempio
seguente si suppone che una classe Person sia stata dichiarata con i membri denominati surname e city . La
clausola group determina la creazione di un gruppo separato per ogni insieme di persone con lo stesso
cognome e la stessa città.

group person by new {name = person.surname, city = person.city};

Usare un tipo denominato se è necessario passare la variabile di query a un altro metodo. Creare una classe
speciale usando proprietà implementate automaticamente per le chiavi e quindi eseguire l'override dei metodi
Equals e GetHashCode. È anche possibile usare uno struct e in questo caso non è strettamente necessario
eseguire l'override dei metodi. Per ulteriori informazioni, vedere come implementare una classe Lightweight con
proprietà implementate automaticamente e come eseguire una query per i file duplicati in un albero di
directory. Il secondo articolo contiene un esempio di codice che illustra come usare una chiave composta con un
tipo denominato.

Esempio
Nell'esempio seguente viene illustrato il modello standard per l'ordinamento dei dati di origine nei gruppi
quando non viene applicata una logica di query aggiuntiva ai gruppi. L'operazione è definita raggruppamento
senza continuazione. Gli elementi in una matrice di stringhe vengono raggruppati in base alla prima lettera. Il
risultato della query è un tipo IGrouping<TKey,TElement> che contiene una proprietà pubblica Key di tipo
char e una raccolta IEnumerable<T> che contiene ogni elemento nel raggruppamento.

Il risultato di una clausola group è una sequenza di sequenze. Di conseguenza, per accedere ai singoli elementi
all'interno di ogni gruppo restituito, usare un ciclo foreach nidificato all'interno del ciclo che esegue l'iterazione
delle chiavi di raggruppamento, come illustrato nell'esempio seguente.
class GroupExample1
{
static void Main()
{
// Create a data source.
string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" };

// Create the query.


var wordGroups =
from w in words
group w by w[0];

// Execute the query.


foreach (var wordGroup in wordGroups)
{
Console.WriteLine("Words that start with the letter '{0}':", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(word);
}
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Words that start with the letter 'b':
blueberry
banana
Words that start with the letter 'c':
chimpanzee
cheese
Words that start with the letter 'a':
abacus
apple
*/

Esempio
In questo esempio viene illustrato come eseguire la logica aggiuntiva per i gruppi dopo che sono stati creati,
usando una continuazione con into . Per altre informazioni, vedere into. Nell'esempio seguente viene eseguita
una query di ogni gruppo per selezionare solo quelli il cui valore della chiave è una vocale.
class GroupClauseExample2
{
static void Main()
{
// Create the data source.
string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant",
"umbrella", "anteater" };

// Create the query.


var wordGroups2 =
from w in words2
group w by w[0] into grps
where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
|| grps.Key == 'o' || grps.Key == 'u')
select grps;

// Execute the query.


foreach (var wordGroup in wordGroups2)
{
Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(" {0}", word);
}
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Groups that start with a vowel: a
abacus
apple
anteater
Groups that start with a vowel: e
elephant
Groups that start with a vowel: u
umbrella
*/

Osservazioni
In fase di compilazione le clausole group vengono convertite in chiamate al metodo GroupBy.

Vedere anche
IGrouping<TKey,TElement>
GroupBy
ThenBy
ThenByDescending
Parole chiave per le query
LINQ (Language-Integrated Query)
Creare un gruppo annidato
Raggruppare i risultati di una query
Eseguire una sottoquery su un'operazione di raggruppamento
into (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La parola chiave contestuale into può essere usata per creare un identificatore temporaneo al fine di archiviare
i risultati di una clausola group, join o select in un nuovo identificatore. Questo identificatore può essere un
generatore per altri comandi di query. Se usato in una clausola group o select , l'utilizzo del nuovo
identificatore viene talvolta definito continuazione.

Esempio
Nell'esempio seguente viene illustrato l'utilizzo della parola chiave into per attivare un identificatore
temporaneo fruitGroup che contiene un tipo dedotto di IGrouping . Usando l'identificatore, è possibile
richiamare il metodo Count su ogni gruppo e selezionare solo i gruppi che contengono due o più parole.

class IntoSample1
{
static void Main()
{

// Create a data source.


string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};

// Create the query.


var wordGroups1 =
from w in words
group w by w[0] into fruitGroup
where fruitGroup.Count() >= 2
select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };

// Execute the query. Note that we only iterate over the groups,
// not the items in each group
foreach (var item in wordGroups1)
{
Console.WriteLine(" {0} has {1} elements.", item.FirstLetter, item.Words);
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
a has 2 elements.
b has 2 elements.
*/

L'utilizzo di into in una clausola group è necessario solo quando si vuole eseguire operazioni di query
aggiuntive in ogni gruppo. Per altre informazioni, vedere Clausola group.
Per un esempio di utilizzo di into in una clausola join , vedere Clausola join.

Vedere anche
Parole chiave di query (LINQ)
LINQ in C#
Clausola group
Clausola orderby (Riferimento C#)
02/11/2020 • 3 minutes to read • Edit Online

In un'espressione di query, la clausola orderby fa in modo che la sequenza o la sottosequenza (gruppo)


restituita venga ordinata in modo crescente o decrescente. È possibile specificare più chiavi per eseguire una o
più operazioni di ordinamento secondarie. L'ordinamento viene eseguito dall'operatore di confronto predefinito
per il tipo dell'elemento. Per impostazione predefinita, l'ordinamento è crescente. È possibile specificare anche
un operatore di confronto personalizzato, che sarà tuttavia disponibile solo se si usa la sintassi basata sul
metodo. Per altre informazioni, vedere Ordinamento dei dati.

Esempio
Nell'esempio seguente, la prima query ordina le parole alfabeticamente a partire da A, la seconda ordina le
stesse parole in ordine decrescente. La parola chiave ascending è il valore di ordinamento predefinito e può
essere omessa.
class OrderbySample1
{
static void Main()
{
// Create a delicious data source.
string[] fruits = { "cherry", "apple", "blueberry" };

// Query for ascending sort.


IEnumerable<string> sortAscendingQuery =
from fruit in fruits
orderby fruit //"ascending" is default
select fruit;

// Query for descending sort.


IEnumerable<string> sortDescendingQuery =
from w in fruits
orderby w descending
select w;

// Execute the query.


Console.WriteLine("Ascending:");
foreach (string s in sortAscendingQuery)
{
Console.WriteLine(s);
}

// Execute the query.


Console.WriteLine(Environment.NewLine + "Descending:");
foreach (string s in sortDescendingQuery)
{
Console.WriteLine(s);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Ascending:
apple
blueberry
cherry

Descending:
cherry
blueberry
apple
*/

Esempio
Nell'esempio seguente viene eseguito un ordinamento primario sui cognomi degli studenti e quindi un
ordinamento secondario sui nomi.

class OrderbySample2
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
}
public static List<Student> GetStudents()
{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111},
new Student {First="Claire", Last="O'Donnell", ID=112},
new Student {First="Sven", Last="Mortensen", ID=113},
new Student {First="Cesar", Last="Garcia", ID=114},
new Student {First="Debra", Last="Garcia", ID=115}
};

return students;
}
static void Main(string[] args)
{
// Create the data source.
List<Student> students = GetStudents();

// Create the query.


IEnumerable<Student> sortedStudents =
from student in students
orderby student.Last ascending, student.First ascending
select student;

// Execute the query.


Console.WriteLine("sortedStudents:");
foreach (Student student in sortedStudents)
Console.WriteLine(student.Last + " " + student.First);

// Now create groups and sort the groups. The query first sorts the names
// of all students so that they will be in alphabetical order after they are
// grouped. The second orderby sorts the group keys in alpha order.
var sortedGroups =
from student in students
orderby student.Last, student.First
group student by student.Last[0] into newGroup
orderby newGroup.Key
select newGroup;

// Execute the query.


Console.WriteLine(Environment.NewLine + "sortedGroups:");
foreach (var studentGroup in sortedGroups)
{
Console.WriteLine(studentGroup.Key);
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}

// Keep the console window open in debug mode


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
sortedStudents:
Garcia Cesar
Garcia Debra
Mortensen Sven
O'Donnell Claire
Omelchenko Svetlana

sortedGroups:
G
Garcia, Cesar
Garcia, Debra
Garcia, Debra
M
Mortensen, Sven
O
O'Donnell, Claire
Omelchenko, Svetlana
*/

Commenti
In fase di compilazione, la clausola orderby viene convertita in una chiamata al metodo OrderBy. Eventuali
chiavi multiple nella clausola orderby vengono convertite in chiamate al metodo ThenBy.

Vedere anche
Riferimenti per C#
Parole chiave di query (LINQ)
LINQ in C#
Clausola group
LINQ (Language-Integrated Query)
Clausola join (Riferimento C#)
02/11/2020 • 14 minutes to read • Edit Online

Il clausola join è utile per l'associazione di elementi di sequenze di origine diverse che non hanno una
relazione diretta nel modello a oggetti. L'unico requisito è che gli elementi in ogni origine condividano alcuni
valori di cui può essere verificata l'uguaglianza. Un distributore di prodotti alimentari, ad esempio, potrebbe
avere un elenco di fornitori di un determinato prodotto e un elenco di acquirenti. Con una clausola join è ad
esempio possibile creare un elenco dei fornitori e degli acquirenti di tale prodotto che si trovano tutti nella
stessa area specificata.
Una clausola join accetta due sequenze di origine come input. Gli elementi in ogni sequenza devono essere o
devono contenere una proprietà che può essere confrontata con una proprietà corrispondente nell'altra
sequenza. La clausola join verifica l'uguaglianza delle chiavi specificate usando la speciale parola chiave
equals . Tutti i join eseguiti dalla clausola join sono equijoin. La forma dell'output di una clausola join
dipende dal tipo specifico di join che si sta eseguendo. Di seguito vengono riportati i tre tipi di join più comuni:
Inner join
Group join
Left outer join

Inner join
In questo esempio viene illustrato un semplice inner equijoin. Questa query genera una sequenza semplice di
coppie "nome prodotto / categoria". La stessa stringa della categoria verrà visualizzata in più elementi. Se per un
elemento di categories non esistono products corrispondenti, tale categoria non verrà visualizzata nei
risultati.

var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

Per altre informazioni, vedere Eseguire inner join.

Group join
Una clausola join con un'espressione into viene detta group join.

var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };

Un group join genera una sequenza di risultati gerarchici, che associa gli elementi nella sequenza di origine a
sinistra con uno o più elementi corrispondenti nella sequenza di origine a destra. Un group join non ha
equivalente in termini relazionali. Si tratta essenzialmente di una sequenza di matrici di oggetti.
Se nessun elemento della sequenza di origine a destra corrisponde a un elemento nell'origine a sinistra, la
clausola join genererà una matrice vuota per tale elemento. Il group join rimane fondamentalmente un inner
equijoin tranne per il fatto che la sequenza di risultati è organizzata in gruppi.
Se si selezionano i risultati di un group join, è possibile accedere agli elementi, ma non è possibile identificare la
chiave alla quale corrispondono. È quindi generalmente più utile selezionare i risultati del group join in un tipo
nuovo che ha anche il nome della chiave, come illustrato nell'esempio precedente.
È ovviamente anche possibile usare il risultato di un group join come generatore di un'altra sottoquery:

var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;

Per altre informazioni, vedere Eseguire join raggruppati.

Left outer join


In un left outer join vengono restituiti tutti gli elementi nella sequenza di origine a sinistra, anche se non sono
disponibili elementi corrispondenti nella sequenza a destra. Per eseguire una left outer join in LINQ, usare il
DefaultIfEmpty metodo in combinazione con un join di gruppo per specificare un elemento di destra
predefinito da produrre se un elemento di sinistra non ha corrispondenze. È possibile usare null come valore
predefinito per qualsiasi tipo di riferimento oppure specificare un tipo predefinito definito dall'utente.
Nell'esempio seguente viene illustrato un tipo predefinito definito dall'utente:

var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
select new { CatName = category.Name, ProdName = item.Name };

Per altre informazioni, vedere Eseguire left outer join.

Operatore di uguaglianza
Una clausola join esegue un equijoin. In altre parole, è possibile basare le corrispondenze solo
sull'uguaglianza di due chiavi. Altri tipi di confronti, quale "maggiore di" o "diverso da", non sono supportati. Per
specificare chiaramente che tutti i join sono equijoin, la clausola join usa la parola chiave equals anziché
l'operatore == . La parola chiave equals può essere usata solo in una clausola join e differisce dall'operatore
== per un aspetto importante. Con equals la chiave a sinistra usa la sequenza di origine esterna, mentre la
chiave a destra usa l'origine interna. L'origine esterna si trova solo nell'ambito sul lato sinistro di equals e la
sequenza di origine interna si trova solo nell'ambito sul lato destro.

Non equijoin
È possibile eseguire non equijoin, cross join e altre operazioni di join personalizzate usando più clausole from
per introdurre indipendentemente nuove sequenze in una query. Per altre informazioni, vedere Eseguire
operazioni di join personalizzate.

Join su raccolte di oggetti e tabelle relazionali


In un'espressione di query LINQ, le operazioni di join vengono eseguite sulle raccolte di oggetti. Le raccolte di
oggetti non possono essere "aggiunte" nello stesso modo in cui si aggiungono due tabelle relazionali. In LINQ
join le clausole esplicite sono necessarie solo se due sequenze di origine non sono collegate da alcuna
relazione. Quando si usa LINQ to SQL, le tabelle di chiavi esterne vengono rappresentate nel modello a oggetti
come proprietà della tabella primaria. Nel database Northwind, ad esempio, la tabella Customer ha una
relazione di chiavi esterne con la tabella Orders. Quando si esegue il mapping delle tabelle al modello a oggetti,
la classe Customer presenta una proprietà Orders che contiene la raccolta degli ordini associati a tale cliente. In
effetti, il join è già stato automaticamente eseguito.
Per altre informazioni sull'esecuzione di una query in tabelle correlate nel contesto di LINQ to SQL, vedere
Procedura: Eseguire il mapping delle relazioni di database.

Chiavi composte
È possibile verificare l'uguaglianza di più valori usando una chiave composta. Per altre informazioni, vedere
Eseguire un join usando chiavi composte. Le chiavi composte possono essere usate anche nella clausola group .

Esempio
Nell'esempio seguente vengono confrontati i risultati di un inner join, di un group join e di un left outer join
nelle stesse origini dati usando le medesime chiavi corrispondenti. A questi esempi viene aggiunto altro codice
per chiarire i risultati nella visualizzazione della console.

class JoinDemonstration
{
#region Data

class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}

class Category
{
public string Name { get; set; }
public int ID { get; set; }
}

// Specify the first data source.


List<Category> categories = new List<Category>()
{
new Category {Name="Beverages", ID=001},
new Category {Name="Condiments", ID=002},
new Category {Name="Vegetables", ID=003},
new Category {Name="Grains", ID=004},
new Category {Name="Fruit", ID=005}
};

// Specify the second data source.


List<Product> products = new List<Product>()
{
new Product {Name="Cola", CategoryID=001},
new Product {Name="Tea", CategoryID=001},
new Product {Name="Mustard", CategoryID=002},
new Product {Name="Pickles", CategoryID=002},
new Product {Name="Carrots", CategoryID=003},
new Product {Name="Bok Choy", CategoryID=003},
new Product {Name="Peaches", CategoryID=005},
new Product {Name="Melons", CategoryID=005},
};
#endregion

static void Main(string[] args)


{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };

Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}

void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;

// Store the count of total items (for demonstration only).


int totalItems = 0;

Console.WriteLine("Simple GroupJoin:");

// A nested foreach statement is required to access group items.


foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems,
groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}

void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};

//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;

Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems,
groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}

void GroupJoin3()
{

var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };

//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;

Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine(" {0}:{1}", item.ProductName, item.Category);
}

Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems);


Console.WriteLine(System.Environment.NewLine);
}

void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });

// Store the count of total items (for demonstration only).


int totalItems = 0;

Console.WriteLine("Left Outer Join:");

// A nested foreach statement is required to access group items


foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}

void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };

Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());


// Store the count of total items
int totalItems = 0;

Console.WriteLine("Left Outer Join 2:");

// Groups have been flattened.


foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);
}
}
/*Output:

InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.

Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups

GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups

GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group

Left Outer Join:


Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups

LeftOuterJoin2: 9 items in 1 group


Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/

Commenti
Una clausola join non seguita da into viene traslata in una chiamata al metodo Join. Una clausola join
seguita da into viene traslata in una chiamata al metodo GroupJoin.

Vedere anche
Parole chiave di query (LINQ)
LINQ (Language-Integrated Query)
Operazioni di join
Clausola group
Eseguire left outer join
Eseguire inner join
Eseguire join raggruppati
Ordinare i risultati di una clausola join
Eseguire un join usando chiavi composte
Sistemi di database compatibili per Visual Studio
Clausola let (Riferimento C#)
02/11/2020 • 2 minutes to read • Edit Online

In un'espressione di query, è utile talvolta archiviare il risultato di una sottoespressione per poterlo usare in
clausole successive. È possibile eseguire questa operazione con la parola chiave let , che crea una nuova
variabile di intervallo e la inizializza con il risultato dell'espressione fornita. Dopo essere stata inizializzata con un
valore, la variabile di intervallo non può essere usata per archiviare un altro valore. Può essere tuttavia
sottoposta a query, se contiene un tipo che può essere sottoposto a query.

Esempio
Nell'esempio seguente, let viene usato in due modi:
1. Per creare un tipo enumerabile che può essere sottoposto a query.
2. Per consentire alla query di chiamare ToLower solo una volta sulla variabile di intervallo word . Senza
usare let , si dovrebbe chiamare ToLower in ogni predicato della clausola where .
class LetSample1
{
static void Main()
{
string[] strings =
{
"A penny saved is a penny earned.",
"The early bird catches the worm.",
"The pen is mightier than the sword."
};

// Split the sentence into an array of words


// and select those whose first letter is a vowel.
var earlyBirdQuery =
from sentence in strings
let words = sentence.Split(' ')
from word in words
let w = word.ToLower()
where w[0] == 'a' || w[0] == 'e'
|| w[0] == 'i' || w[0] == 'o'
|| w[0] == 'u'
select word;

// Execute the query.


foreach (var v in earlyBirdQuery)
{
Console.WriteLine("\"{0}\" starts with a vowel", v);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
"A" starts with a vowel
"is" starts with a vowel
"a" starts with a vowel
"earned." starts with a vowel
"early" starts with a vowel
"is" starts with a vowel
*/

Vedere anche
Riferimenti per C#
Parole chiave di query (LINQ)
LINQ in C#
LINQ (Language-Integrated Query)
Gestire le eccezioni nelle espressioni di query
ascending (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale ascending viene usata nella clausola orderby nelle espressioni di query per
specificare l'ordinamento dal più piccolo al più grande. Dato che ascending è l'ordinamento predefinito non è
necessario specificarlo.

Esempio
L'esempio seguente mostra l'uso di ascending in una clausola orderby.

IEnumerable<string> sortAscendingQuery =
from vegetable in vegetables
orderby vegetable ascending
select vegetable;

Vedere anche
Riferimenti per C#
LINQ in C#
descending
descending (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale descending viene usata nella clausola orderby nelle espressioni di query per
specificare l'ordinamento dal più grande al più piccolo.

Esempio
L'esempio seguente mostra l'uso di descending in una clausola orderby.

IEnumerable<string> sortDescendingQuery =
from vegetable in vegetables
orderby vegetable descending
select vegetable;

Vedere anche
Riferimenti per C#
LINQ in C#
ascending
on (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale on viene usata nella clausola join di un'espressione di query per specificare la
condizione di join.

Esempio
Nell'esempio seguente viene illustrato l'utilizzo di on in una clausola join .

var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name };

Vedere anche
Riferimenti per C#
LINQ (Language-Integrated Query)
equals (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale equals viene usata in una clausola join in un'espressione di query per
confrontare gli elementi di due sequenze. Per ulteriori informazioni, vedere clausola join.

Esempio
L'esempio seguente illustra l'uso della parola chiave equals in una clausola join .

var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name };

Vedere anche
LINQ (Language-Integrated Query)
by (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La parola chiave contestuale by viene usata nella clausola group in un'espressione di query per specificare la
modalità di raggruppamento degli elementi restituiti. Per altre informazioni, vedere Clausola group.

Esempio
L'esempio seguente illustra l'uso della parola chiave contestuale by in una clausola group per specificare che
gli studenti devono essere raggruppati in base alla prima lettera del cognome.

var query = from student in students


group student by student.LastName[0];

Vedere anche
LINQ in C#
in (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

La in parola chiave viene usata nei contesti seguenti:


in parametri di tipo generico all'interno di interfacce e delegati generici.
Come modificatore di parametro, che consente di passare un argomento a un metodo per riferimento e non
per valore.
in istruzioni foreach.
in clausole from nelle espressioni di query LINQ.
in clausole join nelle espressioni di query LINQ.

Vedere anche
Parole chiave di C#
Riferimenti per C#
Operatori ed espressioni c# (riferimenti per C#)
28/01/2021 • 9 minutes to read • Edit Online

C# fornisce diversi operatori. Molti di essi sono supportati dai tipi predefiniti e consentono di eseguire
operazioni di base con i valori di tali tipi. Questi operatori includono i gruppi seguenti:
Operatori aritmetici che eseguono operazioni aritmetiche con operandi numerici
Operatori di confronto che confrontano operandi numerici
Operatori logici booleani che eseguono operazioni logiche con bool operandi
Operatori di spostamento and bit per bit che eseguono operazioni di spostamento or bit per bit con operandi
dei tipi integrali
Operatori di uguaglianza che controllano se gli operandi sono uguali o meno
In genere, è possibile eseguire l' Overload di questi operatori, ovvero specificare il comportamento
dell'operatore per gli operandi di un tipo definito dall'utente.
Le espressioni C# più semplici sono valori letterali (ad esempio, numeri interi e reali ) e nomi di variabili. È
possibile combinarli in espressioni complesse usando gli operatori. La precedenza e l' associatività degli
operatori determinano l'ordine in cui vengono eseguite le operazioni in un'espressione. È possibile usare le
parentesi per cambiare l'ordine di valutazione imposto dalla precedenza e dall'associatività degli operatori.
Nel codice seguente, esempi di espressioni si trovano sul lato destro delle assegnazioni:

int a, b, c;
a = 7;
b = a;
c = b++;
b = a + b * c;
c = a >= 100 ? b : c / 10;
a = (int)Math.Sqrt(b * b + c * c);

string s = "String literal";


char l = s[s.Length - 1];

var numbers = new List<int>(new[] { 1, 2, 3 });


b = numbers.FindLast(n => n > 1);

In genere, un'espressione produce un risultato e può essere inclusa in un'altra espressione. Una void chiamata
al metodo è un esempio di espressione che non produce alcun risultato. Può essere usato solo come istruzione,
come illustrato nell'esempio seguente:

Console.WriteLine("Hello, world!");

Ecco alcuni altri tipi di espressioni disponibili in C#:


Espressioni stringa interpolate che forniscono una sintassi pratica per creare stringhe formattate:

var r = 2.3;
var message = $"The area of a circle with radius {r} is {Math.PI * r * r:F3}.";
Console.WriteLine(message);
// Output:
// The area of a circle with radius 2.3 is 16.619.
Espressioni lambda che consentono di creare funzioni anonime:

int[] numbers = { 2, 3, 4, 5 };
var maximumSquare = numbers.Max(x => x * x);
Console.WriteLine(maximumSquare);
// Output:
// 25

Espressioni di query che consentono di usare le funzionalità di query direttamente in C#:

var scores = new[] { 90, 97, 78, 68, 85 };


IEnumerable<int> highScoresQuery =
from score in scores
where score > 80
orderby score descending
select score;
Console.WriteLine(string.Join(" ", highScoresQuery));
// Output:
// 97 90 85

È possibile usare una definizione di corpo dell'espressione per fornire una definizione concisa per un metodo,
un costruttore, una proprietà, un indicizzatore o un finalizzatore.

Precedenza tra gli operatori


In un'espressione con più operatori, gli operatori con precedenza superiore vengono valutati prima degli
operatori con precedenza più bassa. Nell'esempio seguente, la moltiplicazione viene eseguita per prima perché
ha una precedenza più alta rispetto all'addizione:

var a = 2 + 2 * 2;
Console.WriteLine(a); // output: 6

Usare le parentesi per cambiare l'ordine di valutazione imposto dalla precedenza tra gli operatori:

var a = (2 + 2) * 2;
Console.WriteLine(a); // output: 8

La tabella seguente elenca gli operatori C# in ordine decrescente di precedenza. Gli operatori nella stessa riga
hanno la stessa precedenza.

O P ERATO RI C AT EGO RIA O N O M E

x. y, f (x), a[i], x?.y , x?[y] , x + +, x--, x!, New, typeof, Principale


checked, dechecked, default, NameOf, delegate, sizeof,
stackalloc, x->y

+ x, -x, ! x, ~ x, + + x, --x, ^ x, (T) x, await, &x, * x, true e false Unario

x.. y Range

switch Espressione switch

con Espressione with


O P ERATO RI C AT EGO RIA O N O M E

x * y, x / y, x % y Moltiplicazione

x + y, x – y Additive

x << y, x >> y MAIUSC

x < y, x > y, x <= y, x > = y, is, As Operatori relazionali e operatori di test del tipo

x = = y, x! = y Uguaglianza

x & y AND logico booleano o AND bit per bit

x ^ y XOR logico booleano o XOR bit per bit

x | y OR logico booleano o OR bit per bit

x && y AND condizionale

x || y OR condizionale

x ?? y Operatore null-coalescing

c?t:f Operatore condizionale

x = y, x + = y, x-= y, x * = y, x/= y, x% = y, x &= y, x |= y, x Assegnazione e dichiarazione lambda


^ = y, x <<= y, x >>= y, x? = y, =>

Associatività degli operatori


Quando gli operatori hanno la stessa precedenza, l'associatività degli operatori determina l'ordine di esecuzione
delle operazioni:
Gli operatori associativi a sinistra vengono valutati nell'ordine da sinistra a destra. Ad eccezione degli
operatori di assegnazione e degli operatori che uniscono valori null, tutti gli operatori binari sono associativi
a sinistra. L'espressione a + b - c viene ad esempio valutata come (a + b) - c .
Gli operatori associativi a destra vengono valutati nell'ordine da destra a sinistra. Gli operatori di
assegnazione, gli operatori che uniscono valori null e l' operatore ?: condizionale sono associativi a destra.
L'espressione x = y = z viene ad esempio valutata come x = (y = z) .

Usare le parentesi per cambiare l'ordine di valutazione imposto dall'associatività degli operatori:

int a = 13 / 5 / 2;
int b = 13 / (5 / 2);
Console.WriteLine($"a = {a}, b = {b}"); // output: a = 1, b = 6

Valutazione dell'operando
Senza correlazione con la precedenza e l'associatività degli operatori, gli operandi in un'espressione vengono
valutati da sinistra a destra. Gli esempi seguenti dimostrano l'ordine di valutazione degli operatori e degli
operandi:
EXP RESSIO N O RDIN E DI VA L UTA Z IO N E

a + b a, b, +

a + b * c a, b, c, *, +

a / b + c * d a, b, /, c, d, *, +

a / (b + c) * d a, b, c, +, /, d, *

In genere, vengono valutati tutti gli operandi dell'operatore. Tuttavia, alcuni operatori valutano gli operandi in
modo condizionale. Ovvero, il valore dell'operando più a sinistra di tale operatore definisce se (o quali) devono
essere valutati altri operandi. Questi operatori sono gli operatori logici and ( && ) e OR ( || ) condizionali, gli
operatori con Unione null ?? e ??= , gli operatori condizionali null ?. e ?[] e l' operatore ?: condizionale .
Per ulteriori informazioni, vedere la descrizione di ogni operatore.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Espressioni
Operatori

Vedere anche
Informazioni di riferimento su C#
Overload degli operatori
Alberi delle espressioni
Operatori aritmetici (Riferimenti per C#)
02/11/2020 • 17 minutes to read • Edit Online

Gli operatori seguenti eseguono operazioni aritmetiche con operandi di tipi numerici:
Operatori unari ++ (Increment), -- (Decrement), + (Plus)e - (meno)
Operatori binari * (moltiplicazione), / (divisione), % (resto), + (addizione)e - (sottrazione)
Questi operatori sono supportati da tutti i tipi numerici integrali e a virgola mobile .
Nel caso dei tipi integrali, gli operatori (ad eccezione degli ++ -- operatori e) vengono definiti per i int
uint tipi,, long e ulong . Quando gli operandi sono di altri tipi integrali ( sbyte , byte , short , ushort o
char ), i relativi valori vengono convertiti nel int tipo, che è anche il tipo di risultato di un'operazione. Quando
gli operandi sono di tipi integrali o a virgola mobile diversi, i relativi valori vengono convertiti nel tipo
contenitore più vicino, se tale tipo esiste. Per altre informazioni, vedere la sezione Promozioni numeriche della
specifica del linguaggio C#. Gli ++ -- operatori e sono definiti per tutti i tipi numerici integrali e a virgola
mobile e il tipo char .

Operatore di incremento ++
L'operatore di incremento unario ++ incrementa il suo operando di 1. L'operando deve essere una variabile, un
accesso a una proprietà o un accesso a un indicizzatore.
L'operatore di incremento è supportato in due forme: l'operatore di incremento suffisso, x++ , e l'operatore di
incremento prefisso, ++x .
Operatore di incremento suffisso
Il risultato di x++ è il valore di x prima dell'operazione, come illustrato nell'esempio seguente:

int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i++); // output: 3
Console.WriteLine(i); // output: 4

Operatore di incremento prefisso


Il risultato di ++x è il valore di x dopo l'operazione, come illustrato nell'esempio seguente:

double a = 1.5;
Console.WriteLine(a); // output: 1.5
Console.WriteLine(++a); // output: 2.5
Console.WriteLine(a); // output: 2.5

Operatore di decremento --
L'operatore di decremento unario -- decrementa il proprio operando di 1. L'operando deve essere una
variabile, un accesso a una proprietà o un accesso a un indicizzatore.
L'operatore di decremento è supportato in due forme: l'operatore di decremento suffisso, x-- , e l'operatore di
decremento prefisso, --x .
Operatore di decremento suffisso
Il risultato di x-- è il valore di x prima dell'operazione, come illustrato nell'esempio seguente:

int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i--); // output: 3
Console.WriteLine(i); // output: 2

Operatore di decremento prefisso


Il risultato di --x è il valore di x dopo l'operazione, come illustrato nell'esempio seguente:

double a = 1.5;
Console.WriteLine(a); // output: 1.5
Console.WriteLine(--a); // output: 0.5
Console.WriteLine(a); // output: 0.5

Operatori più e meno unari


L'operatore + unario restituisce il valore del proprio operando. L'operatore - unario calcola la negazione
numerica del proprio operando.

Console.WriteLine(+4); // output: 4

Console.WriteLine(-4); // output: -4
Console.WriteLine(-(-4)); // output: 4

uint a = 5;
var b = -a;
Console.WriteLine(b); // output: -5
Console.WriteLine(b.GetType()); // output: System.Int64

Console.WriteLine(-double.NaN); // output: NaN

Il tipo ULONG non supporta l'operatore unario - .

Operatore di moltiplicazione *
L'operatore di moltiplicazione * calcola il prodotto degli operandi:

Console.WriteLine(5 * 2); // output: 10


Console.WriteLine(0.5 * 2.5); // output: 1.25
Console.WriteLine(0.1m * 23.4m); // output: 2.34

L'operatore * unario è l'operatore di riferimento indiretto a puntatore.

Operatore di divisione /
L'operatore di divisione / divide l'operando di sinistra per l'operando di destra.
Divisione di interi
Per gli operandi di tipo Integer, il risultato dell'operatore / è di tipo Integer ed è uguale al quoziente dei due
operandi arrotondato allo zero:
Console.WriteLine(13 / 5); // output: 2
Console.WriteLine(-13 / 5); // output: -2
Console.WriteLine(13 / -5); // output: -2
Console.WriteLine(-13 / -5); // output: 2

Per ottenere il quoziente dei due operandi come numero a virgola mobile, usare il tipo float , double ,o
decimal :

Console.WriteLine(13 / 5.0); // output: 2.6

int a = 13;
int b = 5;
Console.WriteLine((double)a / b); // output: 2.6

Divisione a virgola mobile


Per i tipi float , double e decimal , il risultato dell'operatore / è il quoziente dei due operandi:

Console.WriteLine(16.8f / 4.1f); // output: 4.097561


Console.WriteLine(16.8d / 4.1d); // output: 4.09756097560976
Console.WriteLine(16.8m / 4.1m); // output: 4.0975609756097560975609756098

Se uno degli operandi è decimal , un altro operando non può essere né float né double , perché né float né
double è convertibile implicitamente in decimal . È necessario convertire esplicitamente l'operando float o
double nel tipo decimal . Per ulteriori informazioni sulle conversioni tra tipi numerici, vedere conversioni
numeriche predefinite.

Operatore di resto %
L'operatore di resto % calcola il resto dopo aver diviso l'operando di sinistra per l'operando di destra.
Resto intero
Per gli operandi di tipi interi, il risultato di a % b è il valore prodotto da a - (a / b) * b . Il segno del resto
diverso da zero è uguale a quello dell'operando di sinistra, come illustrato nell'esempio seguente:

Console.WriteLine(5 % 4); // output: 1


Console.WriteLine(5 % -4); // output: 1
Console.WriteLine(-5 % 4); // output: -1
Console.WriteLine(-5 % -4); // output: -1

Usare il metodo Math.DivRem per calcolare sia la divisione di numeri interi, sia i risultati del resto.
Resto a virgola mobile
Per gli operandi float e double , il risultato di x % y per x e y finiti è il valore z tale che
Il segno di z , se diverso da zero, è uguale a quello di x .
Il valore assoluto di z è il valore prodotto da |x| - n * |y| in cui n è l'intero più grande possibile, che è
minore o uguale a |x| / |y| e |x| e |y| sono i valori assoluti di x e y , rispettivamente.

NOTE
Questo metodo di calcolo del resto è analogo a quello usato per gli operandi Integer, ma diverso dalla specifica IEEE 754.
Se è necessaria l'operazione di resto conforme alla specifica IEEE 754, usare il Math.IEEERemainder metodo.
Per informazioni sul comportamento dell'operatore % in caso di operandi non finiti, vedere la sezione
Operatore di resto della specifica del linguaggio C#.
Per gli operandi decimal , l'operatore di resto % equivale all'operatore di resto di tipo System.Decimal.
L'esempio seguente illustra il comportamento dell'operatore di resto con operandi a virgola mobile:

Console.WriteLine(-5.2f % 2.0f); // output: -1.2


Console.WriteLine(5.9 % 3.1); // output: 2.8
Console.WriteLine(5.9m % 3.1m); // output: 2.8

Operatore di addizione +
L'operatore di addizione + calcola la somma degli operandi:

Console.WriteLine(5 + 4); // output: 9


Console.WriteLine(5 + 4.3); // output: 9.3
Console.WriteLine(5.1m + 4.2m); // output: 9.3

È anche possibile usare l' + operatore per la concatenazione di stringhe e la combinazione di delegati. Per
ulteriori informazioni, vedere l'articolo + += operatori e .

Operatore di sottrazione -
L'operatore di sottrazione - sottrae l'operando di destra dall'operando di sinistra:

Console.WriteLine(47 - 3); // output: 44


Console.WriteLine(5 - 4.3); // output: 0.7
Console.WriteLine(7.5m - 2.3m); // output: 5.2

È anche possibile usare l' - operatore per la rimozione dei delegati. Per ulteriori informazioni, vedere l'articolo
- -= operatori e .

Assegnazione composta
Per un operatore binario op , un'espressione di assegnazione composta in formato

x op= y

equivale a

x = x op y

con la differenza che x viene valutato una sola volta.


L'esempio seguente illustra l'uso dell'assegnazione composta con gli operatori aritmetici:
int a = 5;
a += 9;
Console.WriteLine(a); // output: 14

a -= 4;
Console.WriteLine(a); // output: 10

a *= 2;
Console.WriteLine(a); // output: 20

a /= 4;
Console.WriteLine(a); // output: 5

a %= 3;
Console.WriteLine(a); // output: 2

A causa delle promozioni numeriche il risultato dell'operazione op potrebbe non essere convertibile in modo
implicito nel tipo T di x . In questo caso, se op è un operatore già definito e il risultato dell'operazione è
convertibile in modo esplicito nel tipo T di x , un'espressione di assegnazione composta nel formato x op= y
equivale a x = (T)(x op y) , con la differenza che x viene valutato una sola volta. L'esempio seguente illustra
questo comportamento:

byte a = 200;
byte b = 100;

var c = a + b;
Console.WriteLine(c.GetType()); // output: System.Int32
Console.WriteLine(c); // output: 300

a += b;
Console.WriteLine(a); // output: 44

È anche possibile usare += gli -= operatori e per sottoscrivere e annullare la sottoscrizione di un evento,
rispettivamente. Per ulteriori informazioni, vedere come sottoscrivere e annullare la sottoscrizione di eventi.

Precedenza e associatività degli operatori


Nell'elenco seguente gli operatori aritmetici sono ordinati dalla precedenza più elevata a quella più bassa:
Operatori di incremento x++ e decremento x-- in forma suffissa
Operatori di incremento ++x e decremento --x e unari + e -
Operatori moltiplicativi * , / e %
Operatori di addizione + e -

Gli operatori aritmetici binari prevedono l'associazione all'operando sinistro. Ciò significa che gli operatori con
lo stesso livello di precedenza vengono valutati da sinistra a destra.
Usare le parentesi, () , per cambiare l'ordine di valutazione imposto dalla precedenza e dall'associatività degli
operatori.

Console.WriteLine(2 + 2 * 2); // output: 6


Console.WriteLine((2 + 2) * 2); // output: 8

Console.WriteLine(9 / 5 / 2); // output: 0


Console.WriteLine(9 / (5 / 2)); // output: 4

Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .

Overflow aritmetico e divisione per zero


Quando il risultato di un'operazione aritmetica non rientra nell'intervallo di valori finiti possibili del tipo
numerico interessato, il comportamento di un operatore aritmetico dipende dal tipo dei relativi operandi.
Overflow aritmetico di numeri interi
La divisione di interi per zero genera sempre un'eccezione DivideByZeroException.
In caso di overflow aritmetico di interi, un contesto di verifica overflow, che può essere verificato o non
verificato, controlla il comportamento risultante:
In un contesto verificato, se l'overflow si verifica in un'espressione costante, verrà restituito un errore in fase
di compilazione. Quando invece l'operazione avviene in fase di esecuzione, viene generata una
OverflowException.
In un contesto non verificato, il risultato viene troncato tramite la rimozione dei bit più significativi che non
rientrano nel tipo di destinazione.
Oltre alle istruzioni checked e unchecked, è possibile usare gli operatori checked e unchecked per controllare il
contesto di verifica overflow in cui viene valutata un'espressione:

int a = int.MaxValue;
int b = 3;

Console.WriteLine(unchecked(a + b)); // output: -2147483646


try
{
int d = checked(a + b);
}
catch(OverflowException)
{
Console.WriteLine($"Overflow occurred when adding {a} to {b}.");
}

Per impostazione predefinita, le operazioni aritmetiche vengono eseguite in un contesto non verificato.
Overflow aritmetico di numeri a virgola mobile
Le operazioni aritmetiche con i tipi float e double non generano mai un'eccezione. Il risultato delle operazioni
aritmetiche con questi tipi può essere uno dei valori speciali che rappresentano un numero infinito e non un
numero:

double a = 1.0 / 0.0;


Console.WriteLine(a); // output: Infinity
Console.WriteLine(double.IsInfinity(a)); // output: True

Console.WriteLine(double.MaxValue + double.MaxValue); // output: Infinity

double b = 0.0 / 0.0;


Console.WriteLine(b); // output: NaN
Console.WriteLine(double.IsNaN(b)); // output: True

Per gli operandi di tipo decimal , l'overflow aritmetico genera sempre una OverflowException e la divisione per
zero genera sempre una DivideByZeroException.

Errori di arrotondamento
A causa delle limitazioni generali della rappresentazione a virgola mobile dei numeri reali e dell'aritmetica a
virgola mobile, è possibile che si verifichino errori di arrotondamento nei calcoli con tipi a virgola mobile. Ossia,
il risultato prodotto di un'espressione può essere diverso dal risultato matematico previsto. L'esempio seguente
illustra molti di questi casi:

Console.WriteLine(.41f % .2f); // output: 0.00999999

double a = 0.1;
double b = 3 * a;
Console.WriteLine(b == 0.3); // output: False
Console.WriteLine(b - 0.3); // output: 5.55111512312578E-17

decimal c = 1 / 3.0m;
decimal d = 3 * c;
Console.WriteLine(d == 1.0m); // output: False
Console.WriteLine(d); // output: 0.9999999999999999999999999999

Per ulteriori informazioni, vedere la sezione osservazioni nelle pagine di riferimento System. Double, System.
Singleo System. Decimal .

Overload degli operatori


Un tipo definito dall'utente può eseguire l' Overload degli ++ operatori aritmetici unari (,, -- + e - ) e binari
( * ,, / % , + e - ). Quando viene eseguito l'overload di un operatore binario, viene anche eseguito in
modo implicito l'overload dell'operatore di assegnazione composta corrispondente. Un tipo definito dall'utente
non può eseguire in modo esplicito l'overload di un operatore di assegnazione composta.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Operatori di incremento e decremento suffisso
Operatori di incremento e decremento in forma prefissa
Operatore unario più
Operatore unario meno
Operatore di moltiplicazione
Operatore di divisione
Operatore resto
Operatore di addizione
Operatore di sottrazione
Assegnazione composta
Operatori Checked e Unchecked
Promozioni numeriche

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
System.Math
System.MathF
Valori numerici in .NET
Operatori logici booleani (Riferimenti per C#)
02/11/2020 • 13 minutes to read • Edit Online

Gli operatori seguenti eseguono operazioni logiche con operandi bool :


Operatore unario ! (negazione logica) .
Operatori di tipo binario & (and logico), | (OR logico)e ^ (OR esclusivo logico) . Questi operatori valutano
sempre entrambi gli operandi.
Operatori binari && (and logico condizionale) e || (OR logico condizionale) . Questi operatori valutano
l'operando di destra solo se è necessario.
Per gli operandi dei tipi numerici integrali, gli & | operatori, e ^ eseguono operazioni logiche bit per bit. Per
altre informazioni, vedere Operatori di scorrimento e bit per bit.

Operatore di negazione logica !


L'operatore di prefisso unario ! Calcola la negazione logica del relativo operando. Vale a dire, produce true
se l'operando restituisce false e false se l'operando restituisce true :

bool passed = false;


Console.WriteLine(!passed); // output: True
Console.WriteLine(!true); // output: False

A partire da C# 8,0, l'operatore unario di suffisso ! è l' operatore che perdona i valori null.

Operatore AND logico&


L'operatore & calcola l'AND logico dei relativi operandi. Il risultato di x & y è true se x e y restituiscono
true . In caso contrario, il risultato è false .

L' & operatore valuta entrambi gli operandi anche se l'operando sinistro restituisce false , in modo che il
risultato dell'operazione sia false indipendentemente dal valore dell'operando destro.
Nell'esempio seguente l'operando di destra dell'operatore & è una chiamata a un metodo, che viene eseguita
indipendentemente dal valore dell'operando di sinistra:

bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}

bool a = false & SecondOperand();


Console.WriteLine(a);
// Output:
// Second operand is evaluated.
// False

bool b = true & SecondOperand();


Console.WriteLine(b);
// Output:
// Second operand is evaluated.
// True
L'operatore AND condizionale logico && calcola anche l'AND logico dei relativi operandi, ma non valuta
l'operando di destra se l'operando di sinistra restituisce false .
Per gli operandi dei tipi numerici integrali, l' & operatore calcola l' and logico bit per bit dei relativi operandi.
L'operatore unario & è l'operatore address-of.

Operatore OR esclusivo logico: ^


L'operatore ^ calcola l'OR esclusivo logico, noto anche come XOR logico, dei relativi operandi. Il risultato di
x ^ y è true se x restituisce true e y restituisce false oppure x restituisce false e y restituisce
true . In caso contrario, il risultato è false . Vale a dire, per gli operandi bool , l'operatore ^ calcola lo stesso
risultato dell'operatore di disuguaglianza != .

Console.WriteLine(true ^ true); // output: False


Console.WriteLine(true ^ false); // output: True
Console.WriteLine(false ^ true); // output: True
Console.WriteLine(false ^ false); // output: False

Per gli operandi dei tipi numerici integrali, l' ^ operatore calcola l' OR esclusivo logico bit per bit dei relativi
operandi.

Operatore OR logico |
L'operatore | calcola l'OR logico dei relativi operandi. Il risultato di x | y è true se x o y restituisce true .
In caso contrario, il risultato è false .
L' | operatore valuta entrambi gli operandi anche se l'operando sinistro restituisce true , in modo che il
risultato dell'operazione sia true indipendentemente dal valore dell'operando destro.
Nell'esempio seguente l'operando di destra dell'operatore | è una chiamata a un metodo, che viene eseguita
indipendentemente dal valore dell'operando di sinistra:

bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}

bool a = true | SecondOperand();


Console.WriteLine(a);
// Output:
// Second operand is evaluated.
// True

bool b = false | SecondOperand();


Console.WriteLine(b);
// Output:
// Second operand is evaluated.
// True

L'operatore OR condizionale logico || calcola anche l'OR logico dei relativi operandi, ma non valuta
l'operando di destra se l'operando di sinistra restituisce true .
Per gli operandi dei tipi numerici integrali, l' | operatore calcola l' OR logico bit per bit dei relativi operandi.

Operatore logico AND condizionale&&


L'operatore AND condizionale logico && , chiamato anche operatore AND logico di "corto circuito", calcola
l'AND logico dei relativi operandi. Il risultato di x && y è true se x e y restituiscono true . In caso contrario,
il risultato è false . Se x è false , y non viene valutato.
Nell'esempio seguente l'operando di destra dell'operatore && è una chiamata a un metodo, che non viene
eseguita se l'operando di sinistra restituisce false :

bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}

bool a = false && SecondOperand();


Console.WriteLine(a);
// Output:
// False

bool b = true && SecondOperand();


Console.WriteLine(b);
// Output:
// Second operand is evaluated.
// True

L' operatore logico and & Calcola anche l'and logico degli operandi, ma valuta sempre entrambi gli operandi.

Operatore OR condizionale logico ||


L'operatore OR condizionale logico || , chiamato anche operatore OR logico di "corto circuito", calcola l'OR
logico dei relativi operandi. Il risultato di x || y è true se x o y restituisce true . In caso contrario, il
risultato è false . Se x è true , y non viene valutato.
Nell'esempio seguente l'operando di destra dell'operatore || è una chiamata a un metodo, che non viene
eseguita se l'operando di sinistra restituisce true :

bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}

bool a = true || SecondOperand();


Console.WriteLine(a);
// Output:
// True

bool b = false || SecondOperand();


Console.WriteLine(b);
// Output:
// Second operand is evaluated.
// True

L' operatore OR logico | Calcola anche l'OR logico degli operandi, ma valuta sempre entrambi gli operandi.

Operatori logici booleani nullable


Per gli bool? operandi, gli operatori & (and logico) e | (OR logico) supportano la logica a tre valori come
segue:
L' & operatore produce true solo se entrambi gli operandi restituiscono true . Se x o y restituisce
false , x & y produce false (anche se un altro operando restituisce null ). In caso contrario, il
risultato di x & y è null .
L' | operatore produce false solo se entrambi gli operandi restituiscono false . Se x o y
restituisce true , x | y produce true (anche se un altro operando restituisce null ). In caso contrario,
il risultato di x | y è null .

Nella tabella seguente viene illustrata la semantica:

X Y X& Y X|Y

true true true true

true false false true

true Null Null true

false true false true

false false false false

false Null false Null

Null true Null true

Null false false Null

Null Null Null Null

Il comportamento di questi operatori è diverso dal comportamento tipico degli operatori con tipi valore
nullable. In genere un operatore definito per gli operandi di un tipo valore può essere usato anche con gli
operandi del tipo valore nullable corrispondente. Questo operatore produce null se uno qualsiasi degli
operandi restituisce null . Tuttavia, gli & | operatori e possono produrre un valore diverso da null anche se
uno degli operandi restituisce null . Per ulteriori informazioni sul comportamento dell'operatore con i tipi di
valore Nullable, vedere la sezione operatori rimossi dell'articolo tipi di valore Nullable .
È anche possibile usare gli ! ^ operatori e con bool? operandi, come illustrato nell'esempio seguente:

bool? test = null;


Display(!test); // output: null
Display(test ^ false); // output: null
Display(test ^ null); // output: null
Display(true ^ null); // output: null

void Display(bool? b) => Console.WriteLine(b is null ? "null" : b.Value.ToString());

Gli operatori logici condizionali && e || non supportano gli bool? operandi.

Assegnazione composta
Per un operatore binario op , un'espressione di assegnazione composta in formato
x op= y

equivale a

x = x op y

con la differenza che x viene valutato una sola volta.


Gli operatori & , | e ^ supportano l'assegnazione composta, come illustrato nell'esempio seguente:

bool test = true;


test &= false;
Console.WriteLine(test); // output: False

test |= true;
Console.WriteLine(test); // output: True

test ^= false;
Console.WriteLine(test); // output: True

NOTE
Gli operatori condizionali logici && e || non supportano l'assegnazione composta.

Precedenza degli operatori


Nell'elenco seguente gli operatori logici sono ordinati dalla precedenza più elevata a quella più bassa:
Operatore di negazione logico !
Operatore AND logico &
Operatore OR esclusivo logico ^
Operatore OR logico |
Operatore AND condizionale logico &&
Operatore OR condizionale logico ||

Usare le parentesi, () , per cambiare l'ordine di valutazione imposto dalla precedenza tra gli operatori:
Console.WriteLine(true | true & false); // output: True
Console.WriteLine((true | true) & false); // output: False

bool Operand(string name, bool value)


{
Console.WriteLine($"Operand {name} is evaluated.");
return value;
}

var byDefaultPrecedence = Operand("A", true) || Operand("B", true) && Operand("C", false);


Console.WriteLine(byDefaultPrecedence);
// Output:
// Operand A is evaluated.
// True

var changedOrder = (Operand("A", true) || Operand("B", true)) && Operand("C", false);


Console.WriteLine(changedOrder);
// Output:
// Operand A is evaluated.
// Operand C is evaluated.
// False

Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .

Overload degli operatori


Un tipo definito dall'utente può eseguire l' Overload degli ! operatori, & , | e ^ . Quando viene eseguito
l'overload di un operatore binario, viene anche eseguito in modo implicito l'overload dell'operatore di
assegnazione composta corrispondente. Un tipo definito dall'utente non può eseguire in modo esplicito
l'overload di un operatore di assegnazione composta.
Un tipo definito dall'utente non può eseguire l'overload degli operatori condizionali logici && e || . Tuttavia, se
un tipo definito dall'utente esegue l'overload degli operatori true e false e dell'operatore & o | in un
determinato modo, l'operazione && o || può essere valutata per gli operandi di quel tipo. Per altre
informazioni, vedere la sezione Operatori logici condizionali definiti dall'utente di Specifica del linguaggio C#.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Operatore di negazione logica
Operatori logici
Operatori logici condizionali
Assegnazione composta

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatori di spostamento e bit per bit
Operatori di scorrimento e bit per bit (Riferimenti
per C#)
02/11/2020 • 13 minutes to read • Edit Online

Gli operatori seguenti eseguono operazioni di spostamento or bit per bit con operandi dei tipi numerici integrali
o del tipo char :
Operatore unario ~ (complemento bit per bit)
Operatori di spostamento binario << (spostamento a sinistra) e >> (spostamento a destra)
Operatori di tipo binario & (and logico), | (OR logico)e ^ (OR esclusivo logico)

Questi operatori sono definiti per i tipi int , uint , long e ulong . Quando entrambi gli operandi sono di altri
tipi integrali ( sbyte , byte , short , ushort o char ), i relativi valori vengono convertiti nel tipo int , che è
anche il tipo di risultato di un'operazione. Quando gli operandi sono di tipi integrali diversi, i relativi valori
vengono convertiti nel tipo integrale più vicino. Per altre informazioni, vedere la sezione Promozioni numeriche
della specifica del linguaggio C#.
Gli & | operatori, e ^ sono definiti anche per gli operandi del bool tipo. Per altre informazioni, vedere
Operatori logici booleani.
Le operazioni di scorrimento non causano mai overflow e producono gli stessi risultati nei contesti Checked e
Unchecked.

Operatore di complemento bit per bit ~


L'operatore ~ produce un complemento bit per bit del relativo operando invertendo ogni bit:

uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011

È anche possibile usare il simbolo ~ per dichiarare i finalizzatori. Per altre informazioni, vedere Finalizzatori.

Operatore di scorrimento a sinistra <<


L' << operatore sposta l'operando sinistro a sinistra del numero di bit definito dall'operando destro.
L'operazione di scorrimento a sinistra rimuove i bit più significativi che non rientrano nell'intervallo del tipo di
risultato e imposta le posizioni dei bit vuoti meno significativi su zero, come illustrato nell'esempio seguente:

uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2)}");

uint y = x << 4;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2)}");
// Output:
// Before: 11001001000000000000000000010001
// After: 10010000000000000000000100010000
Poiché gli operatori di scorrimento sono definiti solo per i tipi int , uint , long e ulong , il risultato di
un'operazione contiene almeno 32 bit. Se l'operando di sinistra e di un tipo integrale diverso ( sbyte , byte ,
short , ushort o char ), il relativo valore viene convertito nel tipo int , come illustrato nell'esempio seguente:

byte a = 0b_1111_0001;

var b = a << 8;
Console.WriteLine(b.GetType());
Console.WriteLine($"Shifted byte: {Convert.ToString(b, toBase: 2)}");
// Output:
// System.Int32
// Shifted byte: 1111000100000000

Per informazioni su come l'operando di destra dell'operatore << definisce il conteggio degli scorrimenti, vedere
la sezione Conteggio degli scorrimenti degli operatori di scorrimento.

Operatore di scorrimento a destra >>


L' >> operatore sposta l'operando sinistro a destra del numero di bit definito dall'operando destro.
L'operazione di scorrimento a destra rimuove i bit meno significativi, come illustrato nell'esempio seguente:

uint x = 0b_1001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2), 4}");

uint y = x >> 2;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2), 4}");
// Output:
// Before: 1001
// After: 10

Le posizioni dei bit vuoti più significativi vengono impostate in base al tipo dell'operando di sinistra come segue:
Se l'operando sinistro è di tipo int o long , l'operatore di spostamento a destra esegue un turno
aritmetico : il valore del bit più significativo (il bit di segno) dell'operando di sinistra viene propagato alle
posizioni di bit vuote di ordine superiore. Vale a dire, le posizioni dei bit vuoti più significativi vengono
impostate su zero se l'operando di sinistra è un valore non negativo e impostate su uno se è negativo.

int a = int.MinValue;
Console.WriteLine($"Before: {Convert.ToString(a, toBase: 2)}");

int b = a >> 3;
Console.WriteLine($"After: {Convert.ToString(b, toBase: 2)}");
// Output:
// Before: 10000000000000000000000000000000
// After: 11110000000000000000000000000000

Se l'operando sinistro è di tipo uint o ulong , l'operatore di spostamento a destra esegue un


spostamento logico : le posizioni dei bit vuote di ordine superiore sono sempre impostate su zero.
uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
Console.WriteLine($"Before: {Convert.ToString(c, toBase: 2), 32}");

uint d = c >> 3;
Console.WriteLine($"After: {Convert.ToString(d, toBase: 2), 32}");
// Output:
// Before: 10000000000000000000000000000000
// After: 10000000000000000000000000000

Per informazioni su come l'operando di destra dell'operatore >> definisce il conteggio degli scorrimenti, vedere
la sezione Conteggio degli scorrimenti degli operatori di scorrimento.

Operatore AND logico &


L' & operatore calcola l'and logico bit per bit dei relativi operandi integrali:

uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000

Per gli bool operandi, l' & operatore calcola l' and logico degli operandi. L'operatore unario & è l'operatore
address-of.

Operatore OR esclusivo logico: ^


L' ^ operatore calcola l'OR esclusivo logico bit per bit, noto anche come XOR logico bit per bit, dei relativi
operandi integrali:

uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100

Per bool gli operandi, l' ^ operatore calcola l' OR esclusivo logico degli operandi.

Operatore OR logico |
L' | operatore calcola l'OR logico bit per bit dei relativi operandi integrali:

uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001

Per gli bool operandi, l' | operatore calcola l' OR logico degli operandi.

Assegnazione composta
Per un operatore binario op , un'espressione di assegnazione composta in formato

x op= y

equivale a

x = x op y

con la differenza che x viene valutato una sola volta.


L'esempio seguente illustra l'uso dell'assegnazione composta con gli operatori di scorrimento e bit per bit:

uint a = 0b_1111_1000;
a &= 0b_1001_1101;
Display(a); // output: 10011000

a |= 0b_0011_0001;
Display(a); // output: 10111001

a ^= 0b_1000_0000;
Display(a); // output: 111001

a <<= 2;
Display(a); // output: 11100100

a >>= 4;
Display(a); // output: 1110

void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 8}");

A causa delle promozioni numeriche il risultato dell'operazione op potrebbe non essere convertibile in modo
implicito nel tipo T di x . In questo caso, se op è un operatore già definito e il risultato dell'operazione è
convertibile in modo esplicito nel tipo T di x , un'espressione di assegnazione composta nel formato x op= y
equivale a x = (T)(x op y) , con la differenza che x viene valutato una sola volta. L'esempio seguente illustra
questo comportamento:

byte x = 0b_1111_0001;

int b = x << 8;
Console.WriteLine($"{Convert.ToString(b, toBase: 2)}"); // output: 1111000100000000

x <<= 8;
Console.WriteLine(x); // output: 0

Precedenza degli operatori


Nell'elenco seguente gli operatori di scorrimento e bit per bit sono ordinati dalla precedenza più elevata a quella
più bassa:
Operatore di complemento bit per bit ~
Operatori di scorrimento << e >>
Operatore AND logico &
Operatore OR esclusivo logico ^
Operatore OR logico |
Usare le parentesi, () , per cambiare l'ordine di valutazione imposto dalla precedenza tra gli operatori:

uint a = 0b_1101;
uint b = 0b_1001;
uint c = 0b_1010;

uint d1 = a | b & c;
Display(d1); // output: 1101

uint d2 = (a | b) & c;
Display(d2); // output: 1000

void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 4}");

Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .

Conteggio degli scorrimenti degli operatori di scorrimento


Per gli operatori shift << e >> , il tipo dell'operando destro deve essere int o un tipo con una conversione
numerica implicita predefinita in int .
Per le espressioni x << count e x >> count , il conteggio effettivo degli scorrimenti varia a seconda del tipo di
x come segue:

Se il tipo di x è int o uint , il conteggio dello spostamento viene definito dai cinque bit di ordine
inferiore dell'operando destro. Vale a dire, il conteggio degli scorrimenti viene calcolato da count & 0x1F
(o count & 0b_1_1111 ).
Se il tipo di x è long o ulong , il conteggio dello spostamento viene definito dai sei bit di ordine
inferiore dell'operando destro. Vale a dire, il conteggio degli scorrimenti viene calcolato da count & 0x3F
(o count & 0b_11_1111 ).

L'esempio seguente illustra questo comportamento:

int count1 = 0b_0000_0001;


int count2 = 0b_1110_0001;

int a = 0b_0001;
Console.WriteLine($"{a} << {count1} is {a << count1}; {a} << {count2} is {a << count2}");
// Output:
// 1 << 1 is 2; 1 << 225 is 2

int b = 0b_0100;
Console.WriteLine($"{b} >> {count1} is {b >> count1}; {b} >> {count2} is {b >> count2}");
// Output:
// 4 >> 1 is 2; 4 >> 225 is 2

NOTE
Come illustrato nell'esempio precedente, il risultato di un'operazione di spostamento può essere diverso da zero, anche se
il valore dell'operando destro è maggiore del numero di bit nell'operando sinistro.

Operatori logici di enumerazione


Gli ~ & operatori,, | e ^ sono supportati anche da qualsiasi tipo di enumerazione . Per gli operandi dello
stesso tipo di enumerazione, viene eseguita un'operazione logica sui valori corrispondenti del tipo integrale
sottostante. Ad esempio, per qualsiasi x e y di un tipo di enumerazione T con un tipo sottostante U ,
l'espressione x & y produce lo stesso risultato dell'espressione (T)((U)x & (U)y) .

Gli operatori logici bit per bit vengono in genere usati con un tipo di enumerazione definito con l'attributo Flags
. Per altre informazioni, vedere la sezione Tipi di enumerazione come flag di bit dell'articolo Tipi di
enumerazione.

Overload degli operatori


Un tipo definito dall'utente può eseguire l' Overload degli ~ operatori, << , >> , & , | e ^ . Quando viene
eseguito l'overload di un operatore binario, viene anche eseguito in modo implicito l'overload dell'operatore di
assegnazione composta corrispondente. Un tipo definito dall'utente non può eseguire in modo esplicito
l'overload di un operatore di assegnazione composta.
Se un tipo definito dall'utente T esegue l'overload dell'operatore << o >> , il tipo dell'operando di sinistra
deve essere T e il tipo dell'operando di destra deve essere int .

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Operatore di complemento bit per bit
Operatori shift
Operatori logici
Assegnazione composta
Promozioni numeriche

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatori logici booleani
Operatori di uguaglianza (Riferimenti per C#)
02/11/2020 • 7 minutes to read • Edit Online

Gli operatori == (uguaglianza) e != (disuguaglianza) verificano se gli operandi sono uguali o meno.

Operatore di uguaglianza ==
L'operatore di uguaglianza == restituisce true se gli operandi sono uguali, false in caso contrario.
Uguaglianza dei tipi valore
Gli operandi dei tipi valore predefiniti sono uguali se i relativi valori sono uguali:

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b); // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2); // output: False
Console.WriteLine(c1 == char.ToLower(c2)); // output: True

NOTE
Per gli == operatori, < , > , <= e >= , se uno degli operandi non è un numero ( Double.NaN o Single.NaN ), il
risultato dell'operazione è false . Questo significa che il valore NaN non è maggiore di, minore di, né uguale a qualsiasi
altro valore double (o float ), incluso NaN . Per altre informazioni ed esempi, vedere l'articolo di riferimento per
Double.NaN o Single.NaN.

Due operandi dello stesso tipo enum sono uguali se i valori corrispondenti del tipo integrale sottostante sono
uguali.
I tipi struct definiti dall'utente non supportano l'operatore == per impostazione predefinita. Per supportare
l'operatore == , un tipo struct definito dall'utente deve eseguirne l'overload.
A partire da C# 7.3, gli operatori == e != sono supportati da tuple C#. Per altre informazioni, vedere la sezione
uguaglianza di Tuple nell'articolo tipi di tupla .
Uguaglianza dei tipi riferimento
Per impostazione predefinita, due operandi di tipo riferimento non record sono uguali se fanno riferimento allo
stesso oggetto:
public class ReferenceTypesEquality
{
public class MyClass
{
private int id;

public MyClass(int id) => this.id = id;


}

public static void Main()


{
var a = new MyClass(1);
var b = new MyClass(1);
var c = a;
Console.WriteLine(a == b); // output: False
Console.WriteLine(a == c); // output: True
}
}

Come illustrato nell'esempio, i tipi riferimento definiti dall'utente supportano l'operatore == per impostazione
predefinita. Tuttavia, un tipo riferimento può eseguire l'overload dell'operatore == . Se un tipo riferimento
esegue l'overload dell'operatore == , usare il metodo Object.ReferenceEquals per verificare se due riferimenti di
quel tipo fanno riferimento allo stesso oggetto.
Uguaglianza di tipi di record
Disponibile in C# 9,0 e versioni successive, i tipi di record supportano gli == != operatori e che per
impostazione predefinita forniscono la semantica di uguaglianza dei valori. Ovvero, due operandi di record sono
uguali quando entrambi sono null o i valori corrispondenti di tutti i campi e le proprietà implementate
automaticamente sono uguali.

public class RecordTypesEquality


{
public record Point(int X, int Y, string Name);
public record TaggedNumber(int Number, List<string> Tags);

public static void Main()


{
var p1 = new Point(2, 3, "A");
var p2 = new Point(1, 3, "B");
var p3 = new Point(2, 3, "A");

Console.WriteLine(p1 == p2); // output: False


Console.WriteLine(p1 == p3); // output: True

var n1 = new TaggedNumber(2, new List<string>() { "A" });


var n2 = new TaggedNumber(2, new List<string>() { "A" });
Console.WriteLine(n1 == n2); // output: False
}
}

Come illustrato nell'esempio precedente, in caso di membri di tipo non di record di riferimento, vengono
confrontati i valori di riferimento, non le istanze a cui si fa riferimento.
Uguaglianza di stringhe
Due operandi stringa sono uguali quando entrambi sono null o entrambe le istanze di stringa sono della
stessa lunghezza e contengono caratteri identici in ogni posizione di carattere:
string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower()); // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3); // output: False

Si tratta di un confronto ordinale con distinzione tra maiuscole e minuscole. Per altre informazioni sul confronto
di stringhe, vedere Come confrontare stringhe in C#.
Delegare l'uguaglianza
Due operandi delegati dello stesso tipo di runtime sono uguali quando entrambi sono null o i relativi elenchi
di chiamate hanno la stessa lunghezza e voci uguali in ogni posizione:

Action a = () => Console.WriteLine("a");

Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c)); // output: False
Console.WriteLine(b == c); // output: True

Per altre informazioni, vedere la sezione Delegare gli operatori di uguaglianza dell'articolo Specifiche del
linguaggio C#.
I delegati prodotti dalla valutazione di espressioni lambda semanticamente identiche non sono uguali, come
mostra l'esempio seguente:

Action a = () => Console.WriteLine("a");


Action b = () => Console.WriteLine("a");

Console.WriteLine(a == b); // output: False


Console.WriteLine(a + b == a + b); // output: True
Console.WriteLine(b + a == a + b); // output: False

Operatore di disuguaglianza !=
L'operatore di disuguaglianza != restituisce true se gli operandi sono diversi, false in caso contrario. Per gli
operandi dei tipi predefiniti, l'espressione x != y produce lo stesso risultato dell'espressione !(x == y) . Per
altre informazioni sull'uguaglianza dei tipi, vedere la sezione Operatore di uguaglianza.
Nell'esempio seguente viene illustrato l'uso dell'operatore != :

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b); // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2); // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2); // output: True

Overload degli operatori


Un tipo definito dall'utente può eseguire l'overload degli operatori == e != . Se un tipo sovraccarica uno dei
due operatori, deve anche eseguire l'overload dell'altro.
Un tipo di record non può eseguire in modo esplicito l'overload degli == != operatori e. Se è necessario
modificare il comportamento degli == != operatori e per il tipo di record T , implementare il
IEquatable<T>.Equals metodo con la firma seguente:

public virtual bool Equals(T? other);

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatori relazionali e di test del tipo della specifica del linguaggio C#.
Per ulteriori informazioni sull'uguaglianza dei tipi di record, vedere la sezione membri di uguaglianza della Nota
relativa alla caratteristica dei record.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
System.IEquatable<T>
Object.Equals
Object.ReferenceEquals
Confronti di uguaglianza
Operatori di confronto
Operatori di confronto (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Gli operatori < (minore di), > (maggiore di), <= (minore o uguale)e >= (maggiore o uguale) , noti anche
come relazionali, comparano gli operandi. Questi operatori sono supportati da tutti i tipi numerici integrali e a
virgola mobile .

NOTE
Per gli operatori == , < , > , <= e >= , se uno degli operandi non è un numero (Double.NaN oppure Single.NaN) il
risultato dell'operazione è false . Questo significa che il valore NaN non è maggiore di, minore di, né uguale a qualsiasi
altro valore double (o float ), incluso NaN . Per altre informazioni ed esempi, vedere l'articolo di riferimento per
Double.NaN o Single.NaN.

Il tipo char supporta anche gli operatori di confronto. Nel caso degli char operandi, vengono confrontati i
codici carattere corrispondenti.
Anche i tipi di enumerazione supportano gli operatori di confronto. Per gli operandi dello stesso tipo enum, i
valori corrispondenti del tipo integrale sottostante vengono confrontati.
Gli == != operatori e controllano se gli operandi sono uguali o meno.

Operatore "minore di" <


L'operatore < restituisce true se l'operando di sinistra è minore dell'operando di destra, false in caso
contrario:

Console.WriteLine(7.0 < 5.1); // output: False


Console.WriteLine(5.1 < 5.1); // output: False
Console.WriteLine(0.0 < 5.1); // output: True

Console.WriteLine(double.NaN < 5.1); // output: False


Console.WriteLine(double.NaN >= 5.1); // output: False

Operatore "maggiore di" >


L'operatore > restituisce true se l'operando di sinistra è maggiore dell'operando di destra, false in caso
contrario:

Console.WriteLine(7.0 > 5.1); // output: True


Console.WriteLine(5.1 > 5.1); // output: False
Console.WriteLine(0.0 > 5.1); // output: False

Console.WriteLine(double.NaN > 5.1); // output: False


Console.WriteLine(double.NaN <= 5.1); // output: False

Operatore "minore o uguale a" <=


L'operatore <= restituisce true se l'operando di sinistra è minore o uguale all'operatore di destra, false in
caso contrario:
Console.WriteLine(7.0 <= 5.1); // output: False
Console.WriteLine(5.1 <= 5.1); // output: True
Console.WriteLine(0.0 <= 5.1); // output: True

Console.WriteLine(double.NaN > 5.1); // output: False


Console.WriteLine(double.NaN <= 5.1); // output: False

Operatore "maggiore o uguale a" >=


L'operatore >= restituisce true se l'operando di sinistra è maggiore o uguale all'operatore di destra, false in
caso contrario:

Console.WriteLine(7.0 >= 5.1); // output: True


Console.WriteLine(5.1 >= 5.1); // output: True
Console.WriteLine(0.0 >= 5.1); // output: False

Console.WriteLine(double.NaN < 5.1); // output: False


Console.WriteLine(double.NaN >= 5.1); // output: False

Overload degli operatori


Un tipo definito dall'utente può eseguire l' Overload degli < operatori, > , <= e >= .
Se un tipo esegue l'overload di uno degli operatori < o > , deve eseguire l'overload sia di < che di > . Se un
tipo esegue l'overload di uno degli operatori <= o >= , deve eseguire l'overload sia di <= che di >= .

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatori relazionali e di test del tipo della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
System.IComparable<T>
Operatori di uguaglianza
Operatori di accesso ai membri ed espressioni
(riferimenti per C#)
28/01/2021 • 14 minutes to read • Edit Online

Quando si accede a un membro di un tipo, è possibile utilizzare gli operatori e le espressioni seguenti:
. (accesso ai membri): per accedere a un membro di uno spazio dei nomi o di un tipo
[] (accesso all'elemento della matrice o all'indicizzatore): per accedere a un elemento di matrice o a un
indicizzatore di tipo
?. and ?[] (operatori condizionali null): per eseguire un'operazione di accesso a un membro o a un
elemento solo se un operando è non null
() (chiamata): per chiamare un metodo a cui si accede o richiamare un delegato
^ (indice dalla fine): per indicare che la posizione dell'elemento è dalla fine di una sequenza
.. (intervallo): per specificare un intervallo di indici che è possibile usare per ottenere un intervallo di
elementi di sequenza

Espressione di accesso ai membri.


Si usa il token . per accedere a un membro di uno spazio dei nomi o di un tipo, come illustrano gli esempi
seguenti:
Utilizzare . per accedere a uno spazio dei nomi annidato all'interno di uno spazio dei nomi, come
illustrato nell'esempio seguente di una using direttiva :

using System.Collections.Generic;

Usare . per formare un nome qualificato per accedere a un tipo in uno spazio dei nomi, come illustra il
codice seguente:

System.Collections.Generic.IEnumerable<int> numbers = new int[] { 1, 2, 3 };

Usare una using direttiva per rendere facoltativo l'uso di nomi qualificati.
Usare . per accedere ai membri dei tipi, statici e non statici, come illustra il codice seguente:

var constants = new List<double>();


constants.Add(Math.PI);
constants.Add(Math.E);
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905

È anche possibile usare . per accedere a un metodo di estensione.

Operatore indicizzatore []
Le parentesi quadre [] vengono in genere usate per l'accesso agli elementi matrice, indicizzatore o puntatore.
Accesso a matrici
Nell'esempio seguente viene illustrato come accedere agli elementi matrice:

int[] fib = new int[10];


fib[0] = fib[1] = 1;
for (int i = 2; i < fib.Length; i++)
{
fib[i] = fib[i - 1] + fib[i - 2];
}
Console.WriteLine(fib[fib.Length - 1]); // output: 55

double[,] matrix = new double[2,2];


matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
matrix[1,0] = matrix[1,1] = 3.0;
var determinant = matrix[0,0] * matrix[1,1] - matrix[1,0] * matrix[0,1];
Console.WriteLine(determinant); // output: -3

Se un indice di matrice è fuori dai limiti della dimensione corrispondente di una matrice, viene generata
un'eccezione IndexOutOfRangeException.
Come illustrato nell'esempio precedente, si usano le parentesi quadre anche per dichiarare un tipo di matrice o
creare un'istanza di matrice.
Per altre informazioni sulle matrici, vedere Matrici.
Accesso all'indicizzatore
Nell'esempio seguente viene usato il Dictionary<TKey,TValue> tipo .NET per dimostrare l'accesso
dell'indicizzatore:

var dict = new Dictionary<string, double>();


dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]); // output: 4.14159265358979

Gli indicizzatori consentono di indicizzare le istanze di un tipo definito dall'utente con modalità simili a quelle
dell'indicizzazione della matrice. A differenza degli indici di matrice, che devono essere numeri interi, i parametri
dell'indicizzatore possono essere dichiarati come di qualsiasi tipo.
Per altre informazioni sugli indicizzatori, vedere Indicizzatori.
Altri utilizzi di []
Per informazioni sull'accesso agli elementi del puntatore, vedere la sezione Operatore [] (accesso agli elementi
del puntatore) dell'articolo Operatori relativi al puntatore.
Le parentesi quadre possono anche essere usate per specificare attributi:

[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}

Operatori condizionali Null ?. e ?[]


Disponibile in C# 6 e versioni successive, un operatore condizionale null applica un'operazione di accesso ai
membri, ?. o l'accesso all'elemento, ?[] , all'operando solo se tale operando restituisce un valore non null. in
caso contrario, restituisce null . Cioè
Se a restituisce null , il risultato di a?.x o a?[x] è null .
Se a restituisce un valore diverso da null, il risultato di a?.x o a?[x] è uguale al risultato di a.x o
a[x] , rispettivamente.

NOTE
Se a.x o a[x] genera un'eccezione a?.x o genera a?[x] la stessa eccezione per un valore diverso da null
a . Se, ad esempio, a è un'istanza di matrice non null ed x è all'esterno dei limiti di a , a?[x] genera un'
IndexOutOfRangeException .

Gli operatori condizionali Null causano corto circuiti. Vale a dire, se un'operazione in una catena di operazioni
condizionali di accesso a un membro o a un elemento restituisce null , l'esecuzione delle altre operazioni della
catena viene interrotta. Nell'esempio seguente B non viene valutato se A restituisce null e C non viene
valutato se A oppure B restituisce null :

A?.B?.Do(C);
A?.B?[C];

Nell'esempio seguente viene illustrato l'uso degli operatori ?. e ?[] :

double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)


{
return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}

var sum1 = SumNumbers(null, 0);


Console.WriteLine(sum1); // output: NaN

var numberSets = new List<double[]>


{
new[] { 1.0, 2.0, 3.0 },
null
};

var sum2 = SumNumbers(numberSets, 0);


Console.WriteLine(sum2); // output: 6

var sum3 = SumNumbers(numberSets, 1);


Console.WriteLine(sum3); // output: NaN

Nell'esempio precedente viene inoltre usato l' operatore ?? di Unione null per specificare un'espressione
alternativa da valutare se il risultato di un'operazione condizionale null è null .
Se a.x o a[x] è di un tipo di valore non Nullable T a?.x oppure a?[x] è del tipo di valore Nullable
corrispondente T? . Se è necessaria un'espressione di tipo T , applicare l'operatore di Unione null ?? a
un'espressione condizionale null, come illustrato nell'esempio seguente:
int GetSumOfFirstTwoOrDefault(int[] numbers)
{
if ((numbers?.Length ?? 0) < 2)
{
return 0;
}
return numbers[0] + numbers[1];
}

Console.WriteLine(GetSumOfFirstTwoOrDefault(null)); // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new int[0])); // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new[] { 3, 4, 5 })); // output: 7

Nell'esempio precedente, se non si usa l' ?? operatore, numbers?.Length < 2 restituisce false quando
numbers è null .

L'operatore di accesso ai membri condizionale Null ?. è anche noto come operatore Elvis.
Chiamata a delegati thread-safe
Usare l'operatore ?. per controllare se un delegato è diverso da Null e richiamarlo in un modo thread-safe, ad
esempio, quando si genera un evento, come illustrato nel codice seguente:

PropertyChanged?.Invoke(…)

Il codice equivale alla versione C# 5 o precedente del codice seguente:

var handler = this.PropertyChanged;


if (handler != null)
{
handler(…);
}

Si tratta di un metodo thread-safe per assicurarsi che venga richiamato solo un valore non null handler . Poiché
le istanze di delegati non sono modificabili, nessun thread può modificare l'oggetto a cui fa riferimento la
handler variabile locale. In particolare, se il codice eseguito da un altro thread annulla la sottoscrizione dell'
PropertyChanged evento e PropertyChanged diventa null prima che handler venga richiamato, l'oggetto a cui
fa riferimento handler rimane inalterato. L' ?. operatore valuta l'operando sinistro non più di una volta,
garantendo che non possa essere modificato in null dopo essere stato verificato come non null.

Espressione di chiamata ()
Usare le parentesi, () , per chiamare un metodo oppure richiamare un delegato.
L'esempio seguente illustra come chiamare un metodo, con o senza argomenti, e come richiamare un delegato:

Action<int> display = s => Console.WriteLine(s);

var numbers = new List<int>();


numbers.Add(10);
numbers.Add(17);
display(numbers.Count); // output: 2

numbers.Clear();
display(numbers.Count); // output: 0

Le parentesi si usano anche per richiamare un costruttore con l'operatore new .


Altri utilizzi di ()
È anche possibile usare le parentesi per specificare l'ordine in cui valutare le operazioni in un'espressione. Per
altre informazioni, vedere Operatori C#.
Anche le espressioni cast, che eseguono conversioni dei tipi esplicite, usano le parentesi.

Index from End Operator ^


Disponibile in C# 8,0 e versioni successive, l' ^ operatore indica la posizione dell'elemento alla fine di una
sequenza. Per una sequenza di lunghezza length , ^n punta all'elemento con offset length - n dall'inizio di
una sequenza. Ad esempio, ^1 punta all'ultimo elemento di una sequenza e ^length punta al primo elemento
di una sequenza.

int[] xs = new[] { 0, 10, 20, 30, 40 };


int last = xs[^1];
Console.WriteLine(last); // output: 40

var lines = new List<string> { "one", "two", "three", "four" };


string prelast = lines[^2];
Console.WriteLine(prelast); // output: three

string word = "Twenty";


Index toFirst = ^word.Length;
char first = word[toFirst];
Console.WriteLine(first); // output: T

Come illustrato nell'esempio precedente, Expression ^e è del System.Index tipo. Nell'espressione ^e , il


risultato di e deve essere convertibile in modo implicito in int .
^ Per creare un intervallo di indici, è anche possibile usare l'operatore con l' operatore Range . Per altre
informazioni, vedere indici e intervalli.

Operatore di intervallo..
Disponibile in C# 8,0 e versioni successive, l' .. operatore specifica l'inizio e la fine di un intervallo di indici
come operandi. L'operando sinistro è un inizio inclusivo di un intervallo. L'operando destro è una fine esclusiva
di un intervallo. Uno degli operandi può essere un indice dall'inizio o dalla fine di una sequenza, come illustrato
nell'esempio seguente:

int[] numbers = new[] { 0, 10, 20, 30, 40, 50 };


int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
Display(subset); // output: 10 20 30

int margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner); // output: 10 20 30 40

string line = "one two three";


int amountToTakeFromEnd = 5;
Range endIndices = ^amountToTakeFromEnd..^0;
string end = line[endIndices];
Console.WriteLine(end); // output: three

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

Come illustrato nell'esempio precedente, Expression a..b è del System.Range tipo. Nell'espressione a..b i
risultati di a e b devono essere convertibili in modo implicito in int o Index .
È possibile omettere gli operandi dell' .. operatore per ottenere un intervallo aperto:
a.. equivale a a..^0
..b equivale a 0..b
.. equivale a 0..^0

int[] numbers = new[] { 0, 10, 20, 30, 40, 50 };


int amountToDrop = numbers.Length / 2;

int[] rightHalf = numbers[amountToDrop..];


Display(rightHalf); // output: 30 40 50

int[] leftHalf = numbers[..^amountToDrop];


Display(leftHalf); // output: 0 10 20

int[] all = numbers[..];


Display(all); // output: 0 10 20 30 40 50

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

Per altre informazioni, vedere indici e intervalli.

Overload degli operatori


. () ^ .. Non è possibile eseguire l'overload degli operatori,, e. Anche l'operatore [] viene considerato
un operatore che non supporta l'overload. Per il supporto dell'indicizzazione con tipi definiti dall'utente, usare
gli indicizzatori.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Accesso ai membri
Accesso a elementi
Operatori condizionali Null
Espressioni di chiamata
Per ulteriori informazioni sugli indici e sugli intervalli, vedere la Nota relativa alla proposta di funzionalità.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
?? (null-coalescing operator) ?? (operatore null-coalescing)
:: (operatore)
Operatori di test del tipo ed espressione cast
(riferimenti per C#)
02/11/2020 • 10 minutes to read • Edit Online

È possibile utilizzare gli operatori e le espressioni seguenti per eseguire il controllo dei tipi o la conversione dei
tipi:
operatore is: per controllare se il tipo di runtime di un'espressione è compatibile con un determinato tipo
operatore as: per convertire in modo esplicito un'espressione in un tipo specificato se il tipo di runtime è
compatibile con esso
espressione cast: per eseguire una conversione esplicita
operatore typeof: per ottenere l'istanza System.Type per un tipo

Operatore is
L' operatore is controlla se il tipo di runtime del risultato di un'espressione è compatibile con un determinato
tipo. A partire da C# 7,0, l' is operatore verifica anche il risultato di un'espressione rispetto a un criterio.
L'espressione con l'operatore di test del tipo is ha il formato seguente:

E is T

in cui E è un'espressione che restituisce un valore e T è il nome di un tipo o un parametro di tipo. E non può
essere un metodo anonimo o un'espressione lambda.
L'espressione E is T restituisce true se il risultato di E è diverso da null e può essere convertito nel tipo T
da una conversione di riferimento, una conversione boxing o una conversione unboxing; in caso contrario,
restituisce false . L'operatore is non considera le conversioni definite dall'utente.
L'esempio seguente dimostra che l'operatore is restituisce true se il tipo di runtime del risultato di
un'espressione deriva da un determinato tipo, ovvero se esiste una conversione di riferimento tra i tipi:

public class Base { }

public class Derived : Base { }

public static class IsOperatorExample


{
public static void Main()
{
object b = new Base();
Console.WriteLine(b is Base); // output: True
Console.WriteLine(b is Derived); // output: False

object d = new Derived();


Console.WriteLine(d is Base); // output: True
Console.WriteLine(d is Derived); // output: True
}
}

Nell'esempio seguente viene illustrato che l' is operatore prende in considerazione le conversioni boxing e
unboxing, ma non considera le conversioni numeriche:
int i = 27;
Console.WriteLine(i is System.IFormattable); // output: True

object iBoxed = i;
Console.WriteLine(iBoxed is int); // output: True
Console.WriteLine(iBoxed is long); // output: False

Per informazioni sulle conversioni C#, vedere il capito Conversioni della specifica del linguaggio C#.
Test del tipo con criteri di ricerca
A partire da C# 7,0, l' is operatore verifica anche il risultato di un'espressione rispetto a un criterio. In
particolare, supporta il criterio del tipo nel formato seguente:

E is T v

in cui E è un'espressione che restituisce un valore, T è il nome di un tipo o un parametro di tipo e v è una
nuova variabile locale di tipo T . Se il risultato di E è diverso da null e può essere convertito in T con una
conversione di riferimento, boxing o unboxing, l'espressione E is T v restituisce true e il valore convertito
del risultato di E viene assegnato alla variabile v .
L'esempio seguente illustra l'uso dell'operatore is con un criterio del tipo:

int i = 23;
object iBoxed = i;
int? jNullable = 7;
if (iBoxed is int a && jNullable is int b)
{
Console.WriteLine(a + b); // output 30
}

Per altre informazioni sul criterio del tipo e sugli altri criteri supportati, vedere Criteri di ricerca con is.

Operatore as
L'operatore as converte in modo esplicito il risultato di un'espressione in un tipo di valore nullable o di
riferimento specificato. Se la conversione non è possibile, l'operatore as restituisce null . Diversamente da un'
espressione cast, l' as operatore non genera mai un'eccezione.
Un'espressione nel formato

E as T

in cui E è un'espressione che restituisce un valore e T è il nome di un tipo o un parametro di tipo, produce lo
stesso risultato di

E is T ? (T)(E) : (T)null

con la differenza che E viene valutato una sola volta.


L'operatore as considera solo conversioni di riferimenti, nullable, boxing e unboxing. Non è possibile usare
l'operatore as per eseguire una conversione definita dall'utente. A tale scopo, usare un' espressione cast.
Nell'esempio seguente viene illustrato l'uso dell'operatore as :
IEnumerable<int> numbers = new[] { 10, 20, 30 };
IList<int> indexable = numbers as IList<int>;
if (indexable != null)
{
Console.WriteLine(indexable[0] + indexable[indexable.Count - 1]); // output: 40
}

NOTE
Come mostrato nell'esempio precedente, è necessario confrontare il risultato dell'espressione as con null per
verificare se la conversione riesce. A partire da C# 7,0, è possibile usare l' operatore is per verificare se la conversione ha
esito positivo e, in caso di esito positivo, assegnare il risultato a una nuova variabile.

Espressione cast
Un'espressione cast nel formato (T)E esegue una conversione esplicita del risultato dell'espressione E nel
tipo T . Se non esiste alcuna conversione esplicita dal tipo E al tipo T , si verifica un errore in fase di
compilazione. In fase di esecuzione, è possibile che una conversione esplicita non riesca e che un'espressione
cast generi un'eccezione.
L'esempio seguente illustra conversioni esplicite numeriche e di riferimento:

double x = 1234.7;
int a = (int)x;
Console.WriteLine(a); // output: 1234

IEnumerable<int> numbers = new int[] { 10, 20, 30 };


IList<int> list = (IList<int>)numbers;
Console.WriteLine(list.Count); // output: 3
Console.WriteLine(list[1]); // output: 20

Per informazioni sulle conversioni esplicite supportate, vedere la sezione Conversioni esplicite della specifica del
linguaggio C#. Per informazioni su come definire una conversione personalizzata del tipo esplicito o implicito,
vedere Operatori di conversione definiti dall'utente.
Altri utilizzi di ()
È possibile usare le parentesi anche per chiamare un metodo oppure richiamare un delegato.
Le parentesi possono essere usate anche per specificare l'ordine in cui valutare le operazioni in un'espressione.
Per altre informazioni, vedere Operatori C#.

Operatore typeof
L'operatore typeof ottiene l'istanza System.Type per un tipo. L'argomento dell'operatore typeof deve essere il
nome o il parametro di un tipo, come illustrato nell'esempio seguente:
void PrintType<T>() => Console.WriteLine(typeof(T));

Console.WriteLine(typeof(List<string>));
PrintType<int>();
PrintType<System.Int32>();
PrintType<Dictionary<int, char>>();
// Output:
// System.Collections.Generic.List`1[System.String]
// System.Int32
// System.Int32
// System.Collections.Generic.Dictionary`2[System.Int32,System.Char]

È anche possibile usare l' typeof operatore con tipi generici non associati. Il nome di un tipo generico non
associato deve contenere il numero appropriato di virgole, ovvero una in meno rispetto al numero di parametri
del tipo. L'esempio seguente illustra l'uso dell'operatore typeof con un tipo generico non associato:

Console.WriteLine(typeof(Dictionary<,>));
// Output:
// System.Collections.Generic.Dictionary`2[TKey,TValue]

Un'espressione non può essere un argomento dell'operatore typeof . Per ottenere l' System.Type istanza per il
tipo di runtime di un risultato di un'espressione, usare il Object.GetType metodo.
Test del tipo con l'operatore typeof

Usare l'operatore typeof per controllare se il tipo di runtime del risultato dell'espressione corrisponde
esattamente a un determinato tipo. L'esempio seguente illustra la differenza tra il controllo del tipo eseguito con
l'operatore typeof e il controllo del tipo con l'operatore is:

public class Animal { }

public class Giraffe : Animal { }

public static class TypeOfExample


{
public static void Main()
{
object b = new Giraffe();
Console.WriteLine(b is Animal); // output: True
Console.WriteLine(b.GetType() == typeof(Animal)); // output: False

Console.WriteLine(b is Giraffe); // output: True


Console.WriteLine(b.GetType() == typeof(Giraffe)); // output: True
}
}

Overload degli operatori


is as typeof Non è possibile eseguire l'overload degli operatori, e.
Un tipo definito dall'utente non può eseguire l'overload dell'operatore () , ma può definire conversioni di tipi
personalizzate che possano essere eseguite da un'espressione cast. Per altre informazioni, vedere Operatori di
conversione definiti dall'utente.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Operatore is
Operatore as
Espressioni cast
Operatore typeof

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Come eseguire il cast in modo sicuro usando i criteri di ricerca e gli operatori is e As
Generics in .NET
Operatori di conversione definiti dall'utente
(riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Un tipo definito dall'utente può definire una conversione implicita o esplicita da o in un altro tipo.
Le conversioni implicite non richiedono una sintassi specifica per essere richiamate e possono essere usate in
diverse situazioni, ad esempio durante le chiamate di metodi e assegnazioni. Le conversioni implicite C#
predefinite hanno sempre esito positivo e non generano mai un'eccezione. Anche le conversioni implicite
definite dall'utente si devono comportare nello stesso modo. Se una conversione personalizzata può generare
un'eccezione o una perdita di informazioni, è necessario definirla come conversione esplicita.
Le conversioni definite dall'utente non vengono considerate dagli operatori is e as. Utilizzare un' espressione
cast per richiamare una conversione esplicita definita dall'utente.
Usare operator e implicit o le parole chiave explicit per definire rispettivamente una conversione implicita
o esplicita. Il tipo che definisce una conversione deve essere un tipo di origine o un tipo di destinazione della
conversione. È possibile definire una conversione tra due tipi definiti dall'utente in uno dei due tipi.
L'esempio seguente illustra come definire una conversione implicita ed esplicita:

using System;

public readonly struct Digit


{
private readonly byte digit;

public Digit(byte digit)


{
if (digit > 9)
{
throw new ArgumentOutOfRangeException(nameof(digit), "Digit cannot be greater than nine.");
}
this.digit = digit;
}

public static implicit operator byte(Digit d) => d.digit;


public static explicit operator Digit(byte b) => new Digit(b);

public override string ToString() => $"{digit}";


}

public static class UserDefinedConversions


{
public static void Main()
{
var d = new Digit(7);

byte number = d;
Console.WriteLine(number); // output: 7

Digit digit = (Digit)number;


Console.WriteLine(digit); // output: 7
}
}

È anche possibile usare la parola chiave operator per eseguire l'overload di un operatore C# predefinito. Per
altre informazioni, vedere Overload degli operatori.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Operatori di conversione
Conversioni definite dall'utente
Conversioni implicite
Conversioni esplicite

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Overload degli operatori
Operatori di cast e di test del tipo
Esecuzione del cast e conversioni di tipi
Linee guida di progettazione-operatori di conversione
Conversioni esplicite concatenate definite dall'utente in C#
Operatori correlati ai puntatori (Riferimento C#)
02/11/2020 • 12 minutes to read • Edit Online

È possibile usare gli operatori seguenti con i puntatori:


Operatore unario & (Address-of) : per ottenere l'indirizzo di una variabile
Operatore unario * (riferimento indiretto del puntatore) : per ottenere la variabile a cui punta un puntatore
Operatori -> (accesso ai membri) e [] (accesso agli elementi)
Operatori aritmetici + ,, - ++ e --
Operatori di confronto,,, == != < > , <= e >=
Per informazioni sui tipi di puntatori, vedere Tipi di puntatori.

NOTE
Qualsiasi operazione con i puntatori richiede un contesto unsafe. Il codice che contiene blocchi unsafe deve essere
compilato con l' -unsafe opzione del compilatore.

Address-of (operatore)&
L'operatore & unario restituisce l'indirizzo del relativo operando:

unsafe
{
int number = 27;
int* pointerToNumber = &number;

Console.WriteLine($"Value of the variable: {number}");


Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}");
}
// Output is similar to:
// Value of the variable: 27
// Address of the variable: 6C1457DBD4

L'operando dell'operatore & deve essere una variabile fissa. Le variabili fisse sono variabili che si trovano in
posizioni di archiviazione non interessate dall'operazione di Garbage Collector. Nell'esempio precedente la
variabile locale number è una variabile fissa perché si trova nello stack. Le variabili che si trovano in posizioni di
archiviazione che possono essere influenzate da Garbage Collector (ad esempio rilocate) sono chiamate variabili
mobili. I campi degli oggetti e gli elementi delle matrici sono esempi di variabili mobili. È possibile ottenere
l'indirizzo di una variabile mobile se si "corregge", o "pin", con un' fixed istruzione. L'indirizzo ottenuto è valido
solo all'interno del blocco di un' fixed istruzione. Nell'esempio seguente viene illustrato come utilizzare un'
fixed istruzione e l' & operatore:
unsafe
{
byte[] bytes = { 1, 2, 3 };
fixed (byte* pointerToFirst = &bytes[0])
{
// The address stored in pointerToFirst
// is valid only inside this fixed statement block.
}
}

Non è possibile ottenere l'indirizzo di una costante o di un valore.


Per altre informazioni sulle variabili fisse e mobili, vedere la sezione Variabili fisse e mobili dell'articolo relativo
alla specifica del linguaggio C#.
L'operatore & binario calcola l'AND logico dei relativi operandi booleani o l'AND logico bit per bit dei relativi
operandi integrali.

Operatore * (riferimento indiretto a puntatore)


L'operatore di riferimento indiretto a puntatore unario * ottiene la variabile a cui punta il relativo operando.
L'operatore è chiamato anche operatore di dereferenziazione. L'operando dell'operatore * deve essere un tipo
di puntatore.

unsafe
{
char letter = 'A';
char* pointerToLetter = &letter;
Console.WriteLine($"Value of the `letter` variable: {letter}");
Console.WriteLine($"Address of the `letter` variable: {(long)pointerToLetter:X}");

*pointerToLetter = 'Z';
Console.WriteLine($"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z

Non è possibile applicare l'operatore * a un'espressione di tipo void* .


L'operatore * binario calcola il prodotto dei relativi operandi numerici.

Operatore -> (accesso ai membri del puntatore)


L'operatore -> unisce il riferimento indiretto al puntatore e l'accesso ai membri. Ovvero, se x è un puntatore
di tipo T* e y è un membro accessibile di tipo T , un'espressione nel formato

x->y

equivale a

(*x).y

Nell'esempio seguente viene illustrato l'uso dell'operatore -> :


public struct Coords
{
public int X;
public int Y;
public override string ToString() => $"({X}, {Y})";
}

public class PointerMemberAccessExample


{
public static unsafe void Main()
{
Coords coords;
Coords* p = &coords;
p->X = 3;
p->Y = 4;
Console.WriteLine(p->ToString()); // output: (3, 4)
}
}

Non è possibile applicare l'operatore -> a un'espressione di tipo void* .

Operatore [] (accesso agli elementi del puntatore)


Per un'espressione p di un tipo di puntatore, l'accesso a un elemento del puntatore nella forma p[n] viene
calcolato come *(p + n) , dove n deve essere un tipo convertibile in modo implicito in int , uint , long o
ulong . Per informazioni sul comportamento dell'operatore + con i puntatori, vedere la sezione Addizione o
sottrazione di un valore integrale da un puntatore.
L'esempio seguente illustra come accedere agli elementi della matrice con un puntatore e l'operatore [] :

unsafe
{
char* pointerToChars = stackalloc char[123];

for (int i = 65; i < 123; i++)


{
pointerToChars[i] = (char)i;
}

Console.Write("Uppercase letters: ");


for (int i = 65; i < 91; i++)
{
Console.Write(pointerToChars[i]);
}
}
// Output:
// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ

Nell'esempio precedente un' stackalloc espressione alloca un blocco di memoria nello stack.

NOTE
L'operatore di accesso agli elementi del puntatore non ricerca gli errori relativi a valori non compresi nell'intervallo.

Non è possibile usare [] per l'accesso agli elementi del puntatore con un'espressione di tipo void* .
È anche possibile usare l' [] operatore per l' accesso dell'elemento della matrice o dell'indicizzatore.

Operatori aritmetici dei puntatori


È possibile eseguire le operazioni aritmetiche seguenti con i puntatori:
Aggiungere o sottrarre un valore integrale da un puntatore
Sottrarre due puntatori
Incrementare o decrementare un puntatore
Non è possibile eseguire queste operazioni con puntatori di tipo void* .
Per informazioni sulle operazioni aritmetiche supportate con i tipi numerici, vedere Operatori aritmetici.
Addizione o sottrazione di un valore integrale da un puntatore
Per un puntatore p di tipo T* e un'espressione n di tipo implicitamente convertibile in int , uint , long o
ulong , l'addizione e la sottrazione sono definite come segue:

Entrambe le espressioni p + n e n + p producono un puntatore di tipo T* risultante dalla somma di


n * sizeof(T) all'indirizzo specificato da p .
L'espressione p - n produce un puntatore di tipo T* risultante dalla sottrazione di n * sizeof(T)
dall'indirizzo specificato da p .

L' sizeof operatore ottiene le dimensioni in byte di un tipo.


L'esempio seguente illustra l'uso dell'operatore + con un puntatore:

unsafe
{
const int Count = 3;
int[] numbers = new int[Count] { 10, 20, 30 };
fixed (int* pointerToFirst = &numbers[0])
{
int* pointerToLast = pointerToFirst + (Count - 1);

Console.WriteLine($"Value {*pointerToFirst} at address {(long)pointerToFirst}");


Console.WriteLine($"Value {*pointerToLast} at address {(long)pointerToLast}");
}
}
// Output is similar to:
// Value 10 at address 1818345918136
// Value 30 at address 1818345918144

Sottrazione di puntatori
Per i due puntatori p1 e p2 di tipo T* , l'espressione p1 - p2 produce la differenza tra gli indirizzi specificati
da p1 e p2 divisi per sizeof(T) . Il tipo del risultato è long . Ovvero, p1 - p2 viene calcolato come
((long)(p1) - (long)(p2)) / sizeof(T) .

L'esempio seguente illustra la sottrazione del puntatore:

unsafe
{
int* numbers = stackalloc int[] { 0, 1, 2, 3, 4, 5 };
int* p1 = &numbers[1];
int* p2 = &numbers[5];
Console.WriteLine(p2 - p1); // output: 4
}

Incremento e decremento dei puntatori


L'operatore di incremento ++ somma 1 al relativo operando puntatore. L'operatore di decremento -- sottrae 1
dal relativo operando puntatore.
Entrambi gli operatori sono supportati in due forme: come suffisso ( p++ e p-- ) e come prefisso ( ++p e --p ).
Il risultato di p++ e p-- è il valore di p prima dell'operazione. Il risultato di ++p e --p è il valore di p dopo
l'operazione.
L'esempio seguente illustra il comportamento di entrambi gli operatori di incremento suffisso e prefisso:

unsafe
{
int* numbers = stackalloc int[] { 0, 1, 2 };
int* p1 = &numbers[0];
int* p2 = p1;
Console.WriteLine($"Before operation: p1 - {(long)p1}, p2 - {(long)p2}");
Console.WriteLine($"Postfix increment of p1: {(long)(p1++)}");
Console.WriteLine($"Prefix increment of p2: {(long)(++p2)}");
Console.WriteLine($"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516

Operatori di confronto dei puntatori


È possibile usare gli operatori == , != , < , > , <= e >= per confrontare gli operandi di qualsiasi tipo di
puntatore, incluso void* . Questi operatori confrontano gli indirizzi specificati dai due operandi come se fossero
interi senza segno.
Per informazioni sul comportamento di questi operatori per gli operandi di altri tipi, vedere gli articoli Operatori
di uguaglianza e Operatori di confronto.

Precedenza degli operatori


Nell'elenco seguente gli operatori correlati ai puntatori sono ordinati dalla precedenza più elevata a quella più
bassa:
Operatori di incremento x++ e decremento x-- suffisso e operatori -> e []
Operatori di incremento ++x e decremento --x prefisso e operatori & e *
Operatori di addizione + e -
Operatori di confronto < , > , <= e >=
Operatori di uguaglianza == e !=

Usare le parentesi, () , per cambiare l'ordine di valutazione imposto dalla precedenza tra gli operatori.
Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .

Overload degli operatori


Un tipo definito dall'utente non può eseguire l'overload degli operatori correlati ai puntatori & , * , -> e [] .

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Variabili fisse e mobili
Operatore address-of
Riferimento indiretto a puntatore
Accesso ai membri del puntatore
Accesso agli elementi del puntatore
Puntatore aritmetico
Incremento e decremento dei puntatori
Confronto tra puntatori

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Tipi puntatore
Parola chiave unsafe
parola chiave fixed
stackalloc
Operatore sizeof
Operatori di assegnazione (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L'operatore di assegnazione = assegna il valore dell'operando a destra a una variabile, una proprietà o un
elemento indicizzatore indicato dall'operando a sinistra. Il risultato di un'espressione di assegnazione è il valore
assegnato all'operando a sinistra. Il tipo dell'operando destro deve corrispondere al tipo dell'operando sinistro o
essere convertibile in modo implicito in esso.
L'operatore = di assegnazione è associato a destra, ovvero un'espressione nel formato

a = b = c

viene valutata come

a = (b = c)

L'esempio seguente illustra l'utilizzo dell'operatore di assegnazione con una variabile locale, una proprietà e un
elemento indicizzatore come operando sul lato sinistro:

var numbers = new List<double>() { 1.0, 2.0, 3.0 };

Console.WriteLine(numbers.Capacity);
numbers.Capacity = 100;
Console.WriteLine(numbers.Capacity);
// Output:
// 4
// 100

int newFirstElement;
double originalFirstElement = numbers[0];
newFirstElement = 5;
numbers[0] = newFirstElement;
Console.WriteLine(originalFirstElement);
Console.WriteLine(numbers[0]);
// Output:
// 1
// 5

Operatore di assegnazione ref


A partire da C# 7.3, è possibile usare l'operatore di assegnazione ref = ref per riassegnare una variabile locale
ref o locale ref readonly. L'esempio seguente illustra l'uso dell'operatore di assegnazione ref:
void Display(double[] s) => Console.WriteLine(string.Join(" ", s));

double[] arr = { 0.0, 0.0, 0.0 };


Display(arr);

ref double arrayElement = ref arr[0];


arrayElement = 3.0;
Display(arr);

arrayElement = ref arr[arr.Length - 1];


arrayElement = 5.0;
Display(arr);
// Output:
// 0 0 0
// 3 0 0
// 3 0 5

Nel caso dell'operatore di assegnazione Ref, entrambi gli operandi devono essere dello stesso tipo.

Assegnazione composta
Per un operatore binario op , un'espressione di assegnazione composta in formato

x op= y

equivale a

x = x op y

con la differenza che x viene valutato una sola volta.


L'assegnazione composta è supportata da operatori aritmetici, logici booleani e logici bit per bit e shift.

Assegnazione di Unione null


A partire da C# 8,0, è possibile usare l'operatore di assegnazione di Unione null ??= per assegnare il valore
dell'operando destro all'operando sinistro solo se l'operando sinistro restituisce null . Per ulteriori
informazioni, vedere ?? e?? = articolo Operators .

Overload degli operatori


Un tipo definito dall'utente non può eseguire l' Overload dell'operatore di assegnazione. Tuttavia, un tipo
definito dall'utente può definire una conversione implicita in un altro tipo. In questo modo il valore di un tipo
definito dall'utente può essere assegnato a una variabile, una proprietà o un elemento indicizzatore di un altro
tipo. Per altre informazioni, vedere Operatori di conversione definiti dall'utente.
Un tipo definito dall'utente non può eseguire in modo esplicito l'overload di un operatore di assegnazione
composta. Tuttavia, se un tipo definito dall'utente sovraccarica un operatore binario op , op= anche l'operatore,
se esistente, viene sottosovraccarico in modo implicito.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatori di assegnazione della specifica del linguaggio C#.
Per ulteriori informazioni sull'operatore di assegnazione Ref = ref , vedere la Nota relativa alla proposta di
funzionalità.
Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
ref (parola chiave)
Espressioni lambda (riferimenti per C#)
28/01/2021 • 20 minutes to read • Edit Online

Per creare una funzione anonima, è possibile usare un' espressione lambda . Usare l'operatore di dichiarazione
lambda => per separare l'elenco di parametri dell'espressione lambda dal corpo. Un'espressione lambda può
essere di uno dei due formati seguenti:
Espressione lambda con espressione, che contiene un'espressione come corpo:

(input-parameters) => expression

Espressione lambda con istruzioni, che contiene un blocco di istruzioni come corpo:

(input-parameters) => { <sequence-of-statements> }

Per creare un'espressione lambda, è necessario specificare gli eventuali parametri di input a sinistra
dell'operatore lambda e un'espressione o un blocco di istruzioni sull'altro lato.
Qualsiasi espressione lambda può essere convertita in tipo delegato. Il tipo delegato in cui è possibile convertire
un'espressione lambda è definito dai tipi dei relativi parametri e del valore restituito. Se un'espressione lambda
non restituisce alcun valore, può essere convertita in uno dei tipi delegati Action , altrimenti può essere
convertita in uno dei tipi delegati Func . Ad esempio, un'espressione lambda che include due parametri e non
restituisce alcun valore può essere convertita in delegato Action<T1,T2>. Un'espressione lambda che include un
parametro e restituisce un valore può essere convertita in delegato Func<T,TResult>. Nell'esempio seguente,
l'espressione lambda x => x * x , che specifica un parametro denominato x e restituisce il valore di x
Squared, viene assegnata a una variabile di un tipo delegato:

Func<int, int> square = x => x * x;


Console.WriteLine(square(5));
// Output:
// 25

Le espressioni lambda dell'espressione possono anche essere convertite nei tipi di albero delle espressioni ,
come illustrato nell'esempio seguente:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;


Console.WriteLine(e);
// Output:
// x => (x * x)

È possibile usare espressioni lambda in qualsiasi codice che richiede istanze di tipi delegati o alberi delle
espressioni, ad esempio come argomento per il metodo Task.Run(Action) per passare il codice da eseguire in
background. È anche possibile usare le espressioni lambda quando si scrive LINQ in C#, come illustrato
nell'esempio seguente:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
Quando si usa la sintassi basata su metodo per chiamare il metodo Enumerable.Select nella classe
System.Linq.Enumerable, ad esempio in LINQ to Objects e LINQ to XML, il parametro è un tipo delegato
System.Func<T,TResult>. Quando si chiama il Queryable.Select metodo nella System.Linq.Queryable classe, ad
esempio in LINQ to SQL, il tipo di parametro è un tipo di albero delle espressioni
Expression<Func<TSource,TResult>> . In entrambi i casi è possibile usare la stessa espressione lambda per
specificare il valore del parametro. Questo approccio fa sì che le due chiamate Select risultino simili anche se
in realtà il tipo degli oggetti creati dalle espressioni lambda è diverso.

Espressioni lambda
Un'espressione lambda con un'espressione a destra dell'operatore => è denominata espressione lambda.
Un'espressione lambda dell'espressione restituisce il risultato dell'espressione e ha il formato di base seguente:

(input-parameters) => expression

Il corpo di un'espressione lambda può essere costituito da una chiamata al metodo. Tuttavia, se si creano alberi
delle espressioni valutati al di fuori del contesto di .NET Common Language Runtime (CLR), ad esempio in SQL
Server, non è consigliabile utilizzare chiamate al metodo nelle espressioni lambda. I metodi non avranno alcun
significato all'esterno del contesto di .NET Common Language Runtime (CLR).

Espressioni lambda dell'istruzione


Un'espressione lambda dell'istruzione è simile a un'espressione lambda dell'espressione con la differenza che le
istruzioni sono racchiuse tra parentesi graffe:

(input-parameters) => { <sequence-of-statements> }

Il corpo di un'espressione lambda dell'istruzione può essere costituito da un numero qualsiasi di istruzioni,
sebbene in pratica generalmente non ce ne siano più di due o tre.

Action<string> greet = name =>


{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

Non è possibile usare le espressioni lambda dell'istruzione per creare alberi delle espressioni.

Parametri di input di un'espressione lambda


Racchiudere i parametri di input di un'espressione lambda tra parentesi. Specificare zero parametri di input con
parentesi vuote:

Action line = () => Console.WriteLine();

Se un'espressione lambda dispone di un solo parametro di input, le parentesi sono facoltative:

Func<double, double> cube = x => x * x * x;

Due o più parametri di input sono separati da virgole:


Func<int, int, bool> testForEquality = (x, y) => x == y;

In alcuni casi il compilatore non può dedurre i tipi di parametri di input. È possibile specificare i tipi in modo
esplicito come illustrato nell'esempio seguente:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

I parametri di input devono essere tutti di tipo esplicito o tutti di tipo implicito. In caso contrario, si verifica un
errore del compilatore CS0748.
A partire da C# 9,0, è possibile usare le variabili Discard per specificare due o più parametri di input di
un'espressione lambda che non vengono usati nell'espressione:

Func<int, int, int> constant = (_, _) => 42;

I parametri di eliminazione lambda possono essere utili quando si usa un'espressione lambda per fornire un
gestore eventi.

NOTE
Per la compatibilità con le versioni precedenti, se viene denominato un solo parametro _ di input, all'interno di
un'espressione lambda _ viene considerato come il nome del parametro.

Espressioni lambda asincrone


È facile creare istruzioni ed espressioni lambda che includono l'elaborazione asincrona utilizzando le parole
chiave async e await . Nell'esempio seguente di Windows Form è presente un gestore eventi che chiama e
attende un metodo asincrono, ExampleMethodAsync .

public partial class Form1 : Form


{
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
}

private async void button1_Click(object sender, EventArgs e)


{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
}

private async Task ExampleMethodAsync()


{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}

È possibile aggiungere lo stesso gestore eventi utilizzando un'espressione lambda asincrona. Per aggiungere il
gestore, aggiungere un modificatore async prima dell'elenco di parametri lambda, come illustrato di seguito:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
};
}

private async Task ExampleMethodAsync()


{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}

Per altre informazioni su come creare e usare i metodi asincroni, vedere programmazione asincrona con Async e
await.

Espressioni lambda e tuple


A partire da C# 7,0, il linguaggio C# fornisce supporto incorporato per le Tuple. È possibile specificare una tupla
come argomento di un'espressione lambda e l'espressione lambda può restituire una tupla. In alcuni casi, il
compilatore C# usa l'inferenza del tipo per determinare i tipi di componenti della tupla.
Per definire una tupla, è necessario racchiudere tra parentesi un elenco di componenti delimitato da virgole.
L'esempio riportato sotto usa una tupla con tre componenti per passare una sequenza di numeri a
un'espressione lambda, la quale raddoppia ogni valore e restituisce una tupla con tre componenti che contiene il
risultato delle moltiplicazioni.

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

In genere, i campi di una tupla sono denominati Item1 , Item2 e così via. È tuttavia possibile definire una tupla
con i componenti denominati, come nell'esempio seguente.

Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");

Per altre informazioni sulle tuple C#, vedere tipi di tupla.

Espressioni lambda con operatori query standard


LINQ to Objects, tra altre implementazioni, ha un parametro di input il cui tipo appartiene alla famiglia
Func<TResult> di delegati generici. Questi delegati usano parametri di tipo per definire il numero e il tipo di
parametri di input e il tipo restituito del delegato. I delegati Func sono molto utili per incapsulare le espressioni
definite dall'utente applicate a ogni elemento in un set di dati di origine. Considerare ad esempio il tipo delegato
Func<T,TResult>:
public delegate TResult Func<in T, out TResult>(T arg)

È possibile creare un'istanza Func<int, bool> del delegato, dove int è un parametro di input e bool è il
valore restituito. Il valore restituito è sempre specificato nell'ultimo parametro di tipo. Func<int, string, bool> ,
ad esempio, definisce un delegato con due parametri di input, int e string , e un tipo restituito bool . Quando
viene richiamato, il delegato Func seguente restituisce il valore booleano indicante se il parametro di input è
uguale a cinque:

Func<int, bool> equalsFive = x => x == 5;


bool result = equalsFive(4);
Console.WriteLine(result); // False

È anche possibile specificare un'espressione lambda quando il tipo di argomento è Expression<TDelegate>, ad


esempio negli operatori query standard definiti nel tipo Queryable. Quando si specifica un argomento
Expression<TDelegate>, l'espressione lambda viene compilata per un albero delle espressioni.
L'esempio seguente usa l'operatore query standard Count:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

Il compilatore è in grado di dedurre il tipo del parametro di input oppure è possibile specificarlo in modo
esplicito. Questa espressione lambda particolare conta i numeri interi ( n ) che divisi per due danno il resto di 1.
L'esempio seguente crea una sequenza contenente tutti gli elementi presenti nella matrice numbers che si
trovano a sinistra di 9, vale a dire il primo numero della sequenza che non soddisfa la condizione:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

In questo esempio viene illustrato come specificare più parametri di input racchiudendoli tra parentesi. Il
metodo restituisce tutti gli elementi presenti nella matrice numbers finché non viene rilevato un numero il cui
valore è inferiore alla relativa posizione all'interno della matrice:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4

Inferenza del tipo nelle espressioni lambda


Quando si scrivono espressioni lambda, spesso non occorre specificare un tipo per i parametri di input, perché il
compilatore può dedurlo in base al corpo dell'espressione lambda, ai tipi di parametro e ad altri fattori, come
descritto nelle specifiche del linguaggio C#. Per la maggior parte degli operatori di query standard, il primo
input è il tipo degli elementi nella sequenza di origine. Pertanto, se si esegue una query su un oggetto
IEnumerable<Customer> , si deduce che la variabile di input sia un oggetto Customer , ovvero che si dispone
dell'accesso ai relativi metodi e proprietà:
customers.Where(c => c.City == "London");

Di seguito sono riportate le regole generali per l'inferenza del tipo nelle espressioni lambda:
L'espressione lambda deve contenere lo stesso numero di parametri del tipo delegato.
Ogni parametro di input nell'espressione lambda deve essere convertibile in modo implicito nel
parametro del delegato corrispondente.
Il valore restituito dell'espressione lambda, se presente, deve essere convertibile in modo implicito nel
tipo restituito del delegato.
Si noti che le espressioni lambda non hanno un tipo perché Common Type System non ha alcun concetto
intrinseco di "espressione lambda". In alcuni casi, tuttavia, può essere utile fare riferimento in modo informale al
"tipo" di un'espressione lambda. In questi casi, per tipo si intende il tipo delegato o il tipo Expression in cui viene
convertita l'espressione lambda.

Acquisire variabili esterne e ambito delle variabili nelle espressioni


lambda
Le espressioni lambda possono fare riferimento a variabili esterne. Si tratta delle variabili incluse nell'ambito del
metodo che definisce l'espressione lambda oppure nell'ambito del tipo che contiene l'espressione lambda. Le
variabili acquisite in questo modo vengono archiviate per poter essere utilizzate nell'espressione lambda anche
se le variabili diventano esterne all'ambito e vengono sottoposte a Garbage Collection. Una variabile esterna
deve essere assegnata prima di poter essere utilizzata in un'espressione lambda. Nell'esempio seguente
vengono illustrate queste regole:
public static class VariableScopeWithLambdas
{
public class VariableCaptureGame
{
internal Action<int> updateCapturedLocalVariable;
internal Func<int, bool> isEqualToCapturedLocalVariable;

public void Run(int input)


{
int j = 0;

updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};

isEqualToCapturedLocalVariable = x => x == j;

Console.WriteLine($"Local variable before lambda invocation: {j}");


updateCapturedLocalVariable(10);
Console.WriteLine($"Local variable after lambda invocation: {j}");
}
}

public static void Main()


{
var game = new VariableCaptureGame();

int gameInput = 5;
game.Run(gameInput);

int jTry = 10;


bool result = game.isEqualToCapturedLocalVariable(jTry);
Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

int anotherJ = 3;
game.updateCapturedLocalVariable(anotherJ);

bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);


Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
}
// Output:
// Local variable before lambda invocation: 0
// 10 is greater than 5: True
// Local variable after lambda invocation: 10
// Captured local variable is equal to 10: True
// 3 is greater than 5: False
// Another lambda observes a new value of captured variable: True
}

Le regole seguenti si applicano all'ambito delle variabili nelle espressioni lambda:


Una variabile acquisita non sarà sottoposta a Garbage Collection finché il delegato a cui fa riferimento
non diventa idoneo per il Garbage Collection.
Le variabili introdotte in un'espressione lambda non sono visibili nel metodo contenitore.
Un'espressione lambda non può acquisire direttamente un parametro in, ref o out dal metodo
contenitore.
Un'istruzione return in un'espressione lambda non causa la restituzione del metodo contenitore.
Un'espressione lambda non può contenere un'istruzione goto, break o continue se la destinazione di tale
istruzione di salto è esterna al blocco dell'espressione lambda. È anche errato inserire all'esterno del
blocco dell'espressione lambda un'istruzione di salto se la destinazione è interna al blocco.
A partire da C# 9,0, è possibile applicare il static modificatore a un'espressione lambda per impedire
l'acquisizione accidentale di variabili locali o lo stato dell'istanza mediante l'espressione lambda:

Func<double, double> square = static x => x * x;

Un'espressione lambda statica non può acquisire le variabili locali o lo stato dell'istanza dagli ambiti di
inclusione, ma può fare riferimento a membri statici e definizioni di costanti.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni di funzioni anonime della specifica del linguaggio C#.
Per ulteriori informazioni sulle funzionalità aggiunte in C# 9,0, vedere le note sulla proposta di funzionalità
seguenti:
Parametri di rimozione lambda
Funzioni anonime statiche

Vedi anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
LINQ (Language-Integrated Query)
Alberi delle espressioni
Funzioni locali ed espressioni lambda
Esempi C# di Visual Studio 2008 (vedere i file di query di esempio LINQ e il programma XQuery)
Operatori + e += (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Gli + += operatori e sono supportati dai tipi numerici integrali e a virgola mobile incorporati, dal tipo di
stringa e dai tipi delegati .
Per informazioni sull'operatore aritmetico + , vedere le sezioni Operatori più e meno unari e Operatore di
addizione + dell'articolo Operatori aritmetici.

Concatenazione di stringhe
Quando uno o entrambi gli operandi sono di tipo stringa, l' + operatore concatena le rappresentazioni di
stringa dei relativi operandi (la rappresentazione di stringa di null è una stringa vuota):

Console.WriteLine("Forgot" + "white space");


Console.WriteLine("Probably the oldest constant: " + Math.PI);
Console.WriteLine(null + "Nothing to add.");
// Output:
// Forgotwhite space
// Probably the oldest constant: 3.14159265358979
// Nothing to add.

A partire da C# 6, l' interpolazione di stringhe rappresenta un modo più pratico per formattare le stringhe:

Console.WriteLine($"Probably the oldest constant: {Math.PI:F2}");


// Output:
// Probably the oldest constant: 3.14

Combinazione di delegati
Per gli operandi con lo stesso tipo delegato, l'operatore + restituisce una nuova istanza di delegato che, quando
viene chiamata, richiama l'operando di sinistra e quindi quello di destra. Se uno degli operandi è null ,
l'operatore + restituisce il valore di un altro operando, che può essere anch'esso null . L'esempio seguente
mostra in che modo è possibile combinare delegati con l'operatore + :

Action a = () => Console.Write("a");


Action b = () => Console.Write("b");
Action ab = a + b;
ab(); // output: ab

Per eseguire la rimozione dei delegati, usare l' - operatore.


Per altre informazioni sui tipi delegate, vedere Delegati.

Operatore di assegnazione di addizione +=


Un'espressione che usa l'operatore += , ad esempio

x += y
equivale a

x = x + y

con la differenza che x viene valutato una sola volta.


Nell'esempio seguente viene illustrato l'uso dell'operatore += :

int i = 5;
i += 9;
Console.WriteLine(i);
// Output: 14

string story = "Start. ";


story += "End.";
Console.WriteLine(story);
// Output: Start. End.

Action printer = () => Console.Write("a");


printer(); // output: a

Console.WriteLine();
printer += () => Console.Write("b");
printer(); // output: ab

È anche possibile usare l'operatore += per specificare un metodo del gestore eventi quando si sottoscrive un
evento. Per altre informazioni, vedere Procedura: Sottoscrivere e annullare la sottoscrizione di eventi.

Overload degli operatori


Un tipo definito dall'utente può eseguire l'overload dell'operatore + . Quando viene eseguito l'overload di un
operatore + binario, viene eseguito in modo implicito anche l'overload dell'operatore += . Un tipo definito
dall'utente non può eseguire l'overload dell'operatore += in modo esplicito.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni Operatore + unario e Operatore addizione della specifica del linguaggio
C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Come concatenare più stringhe
Eventi
Operatori aritmetici
operatori-and-=
Operatori - e -= (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Gli - -= operatori e sono supportati dai tipi numerici integrali e a virgola mobile incorporati e dai tipi delegati
.
Per informazioni sull'operatore aritmetico - , vedere le sezioni Operatori più e meno unari e Operatore di
sottrazione - dell'articolo Operatori aritmetici.

Rimozione di delegati
Per gli operandi dello stesso tipo delegato, l'operatore - restituisce un'istanza di delegato che viene calcolata
come segue:
Se entrambi gli operandi sono diversi da Null e l'elenco chiamate dell'operando di destra è un
sottoelenco contiguo dell'elenco chiamate dell'operando di sinistra, il risultato dell'operazione è un
nuovo elenco chiamate ottenuto rimuovendo le voci dell'operando di destra dall'elenco di chiamate
dell'operando di sinistra. Se l'elenco dell'operando di destra corrisponde a più sottoelenchi contigui
nell'elenco dell'operando di sinistra, viene rimosso solo il sottoelenco corrispondente più a destra. Se la
rimozione restituisce un elenco vuoto, il risultato è null .

Action a = () => Console.Write("a");


Action b = () => Console.Write("b");

var abbaab = a + b + b + a + a + b;
abbaab(); // output: abbaab
Console.WriteLine();

var ab = a + b;
var abba = abbaab - ab;
abba(); // output: abba
Console.WriteLine();

var nihil = abbaab - abbaab;


Console.WriteLine(nihil is null); // output: True

Se l'elenco chiamate dell'operando di destra non è un sottoelenco contiguo dell'elenco chiamate


dell'operando di sinistra, il risultato dell'operazione è l'operando di sinistra. La rimozione di un delegato
che non fa parte del delegato multicast, ad esempio, non produce alcun risultato e il delegato multicast
rimane invariato.
Action a = () => Console.Write("a");
Action b = () => Console.Write("b");

var abbaab = a + b + b + a + a + b;
var aba = a + b + a;

var first = abbaab - aba;


first(); // output: abbaab
Console.WriteLine();
Console.WriteLine(object.ReferenceEquals(abbaab, first)); // output: True

Action a2 = () => Console.Write("a");


var changed = aba - a;
changed(); // output: ab
Console.WriteLine();
var unchanged = aba - a2;
unchanged(); // output: aba
Console.WriteLine();
Console.WriteLine(object.ReferenceEquals(aba, unchanged)); // output: True

L'esempio precedente dimostra anche che, durante la rimozione del delegato, vengono confrontate le
istanze del delegato. I delegati prodotti dalla valutazione di espressioni lambda identiche, ad esempio,
non sono uguali. Per altre informazioni sull'uguaglianza dei delegati, vedere la sezione Delegare gli
operatori di uguaglianza dell'articolo Specifiche del linguaggio C#.
Se l'operando di sinistra è null , il risultato dell'operazione è null . Se l'operando di destra è null , il
risultato dell'operazione è l'operando di sinistra.

Action a = () => Console.Write("a");

var nothing = null - a;


Console.WriteLine(nothing is null); // output: True

var first = a - null;


a(); // output: a
Console.WriteLine();
Console.WriteLine(object.ReferenceEquals(first, a)); // output: True

Per combinare i delegati, usare l' + operatore.


Per altre informazioni sui tipi delegate, vedere Delegati.

Operatore di assegnazione di sottrazione -=


Un'espressione che usa l'operatore -= , ad esempio

x -= y

equivale a

x = x - y

con la differenza che x viene valutato una sola volta.


Nell'esempio seguente viene illustrato l'uso dell'operatore -= :
int i = 5;
i -= 9;
Console.WriteLine(i);
// Output: -4

Action a = () => Console.Write("a");


Action b = () => Console.Write("b");
var printer = a + b + a;
printer(); // output: aba

Console.WriteLine();
printer -= a;
printer(); // output: ab

È anche possibile usare l'operatore -= per specificare un metodo del gestore eventi quando si elimina la
sottoscrizione a un evento. Per ulteriori informazioni, vedere come sottoscrivere e annullare la sottoscrizione di
eventi.

Overload degli operatori


Un tipo definito dall'utente può eseguire l'overload dell'operatore - . Quando viene eseguito l'overload di un
operatore - binario, viene eseguito in modo implicito anche l'overload dell'operatore -= . Un tipo definito
dall'utente non può eseguire l'overload dell'operatore -= in modo esplicito.

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni Operatore meno unario e Operatore di sottrazione di Specifiche del
linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Eventi
Operatori aritmetici
Operatori + e +=
Operatore ?: (Riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

L'operatore condizionale ?: , noto anche come operatore condizionale ternario, valuta un'espressione
booleana e restituisce il risultato di una delle due espressioni, a seconda che l'espressione booleana restituisca
true o false .

La sintassi dell'operatore condizionale è la seguente:

condition ? consequent : alternative

L'espressione condition deve restituire true o false . Se condition restituisce true , viene valutata
l'espressione consequent e il suo risultato diventa il risultato dell'operazione. Se condition restituisce false ,
viene valutata l'espressione alternative e il suo risultato diventa il risultato dell'operazione. Viene valutata solo
consequent o solo alternative .

A partire da C# 9,0, le espressioni condizionali sono tipizzate come destinazione. Ovvero, se un tipo di
destinazione di un'espressione condizionale è noto, i tipi di consequent e alternative devono essere
convertibili in modo implicito nel tipo di destinazione, come illustrato nell'esempio seguente:

var rand = new Random();


var condition = rand.NextDouble() > 0.5;

int? x = condition ? 12 : null;

IEnumerable<int> xs = x is null ? new List<int>() { 0, 1 } : new int[] { 2, 3 };

Se un tipo di destinazione di un'espressione condizionale è sconosciuto (ad esempio, quando si usa la var
parola chiave) o in C# 8,0 e versioni precedenti, il tipo di consequent e alternative deve essere uguale oppure
deve essere presente una conversione implicita da un tipo all'altro:

var rand = new Random();


var condition = rand.NextDouble() > 0.5;

var x = condition ? 12 : (int?)null;

L'operatore condizionale si associa all'operando a destra, che significa che un'espressione nel formato

a ? b : c ? d : e

viene valutata come

a ? b : (c ? d : e)
TIP
Un modo per ricordarsi che cosa restituisce questo operatore è il seguente:

is this condition true ? yes : no

L'esempio seguente illustra l'uso dell'operatore condizionale:

double sinc(double x) => x != 0.0 ? Math.Sin(x) / x : 1;

Console.WriteLine(sinc(0.1));
Console.WriteLine(sinc(0.0));
// Output:
// 0.998334166468282
// 1

Espressione condizionale ref


A partire da C# 7,2, una variabile locale ref local o ref ReadOnly può essere assegnata in modo condizionale con
un'espressione di riferimento condizionale. È anche possibile usare un'espressione di riferimento condizionale
come valore restituito di riferimento o come ref argomento del metodo.
La sintassi per un'espressione di riferimento condizionale è la seguente:

condition ? ref consequent : ref alternative

Analogamente all'operatore condizionale originale, un'espressione di riferimento condizionale valuta solo una
delle due espressioni: consequent o alternative .
Nel caso di un'espressione di riferimento condizionale, il tipo di consequent e alternative deve essere lo
stesso. Le espressioni di riferimento condizionale non sono tipizzate come destinazione.
Nell'esempio seguente viene illustrato l'utilizzo di un'espressione di riferimento condizionale:

var smallArray = new int[] { 1, 2, 3, 4, 5 };


var largeArray = new int[] { 10, 20, 30, 40, 50 };

int index = 7;
ref int refValue = ref ((index < 5) ? ref smallArray[index] : ref largeArray[index - 5]);
refValue = 0;

index = 2;
((index < 5) ? ref smallArray[index] : ref largeArray[index - 5]) = 100;

Console.WriteLine(string.Join(" ", smallArray));


Console.WriteLine(string.Join(" ", largeArray));
// Output:
// 1 2 100 4 5
// 10 20 0 40 50

Operatore condizionale e istruzione if..else


L'uso dell'operatore condizionale anziché di un'istruzione if-else potrebbe produrre codice più conciso nei casi in
cui è necessario calcolare un valore in modo condizionale. L'esempio seguente illustra due modi di classificare
un intero come negativo o non negativo:
int input = new Random().Next(-5, 5);

string classify;
if (input >= 0)
{
classify = "nonnegative";
}
else
{
classify = "negative";
}

classify = (input >= 0) ? "nonnegative" : "negative";

Overload degli operatori


Un tipo definito dall'utente non può eseguire l'overload dell'operatore condizionale.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatore condizionale della specifica del linguaggio C#.
Per ulteriori informazioni sulle funzionalità aggiunte in C# 7,2 e versioni successive, vedere le note della
proposta di funzionalità seguenti:
Espressioni di riferimento condizionale (C# 7,2)
Espressione condizionale tipizzata di destinazione (C# 9,0)

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Istruzione if-else
?. e? operatori []
?? e? = operatori
ref (parola chiave)
! operatore (null-perdonatore) (riferimenti per C#)
28/01/2021 • 4 minutes to read • Edit Online

Disponibile in C# 8,0 e versioni successive, l'operatore unario di suffisso ! è l'operatore null-perdonatore o con
eliminazione null. In un contesto di annotazione Nullableabilitato, si usa l'operatore che perdona i valori null per
dichiarare che l'espressione x di un tipo di riferimento non è null : x! . L'operatore di prefisso unario ! è l'
operatore logico di negazione.
L'operatore che perdona i valori null non ha alcun effetto in fase di esecuzione. Influisce solo sull'analisi del
flusso statico del compilatore modificando lo stato null dell'espressione. In fase di esecuzione, l'espressione x!
restituisce il risultato dell'espressione sottostante x .
Per ulteriori informazioni sulla funzionalità dei tipi di riferimento Nullable, vedere tipi di riferimento Nullable.

Esempio
Uno dei casi d'uso dell'operatore che perdona i valori null è il test della logica di convalida degli argomenti. Si
consideri ad esempio la classe seguente:

#nullable enable
public class Person
{
public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

public string Name { get; }


}

Usando il Framework di test di MSTest, è possibile creare il test seguente per la logica di convalida nel
costruttore:

[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void NullNameShouldThrowTest()
{
var person = new Person(null!);
}

Senza l'operatore di indulgenza null, il compilatore genera l'avviso seguente per il codice precedente:
Warning CS8625: Cannot convert null literal to non-nullable reference type . Utilizzando l'operatore di
indulgenza null, si informa il compilatore che il passaggio null è previsto e non deve essere avvisato.
È anche possibile usare l'operatore di indulgenza null quando si è certi che un'espressione non può essere null
, ma il compilatore non riesce a riconoscerlo. Nell'esempio seguente, se il IsValid metodo restituisce true , il
relativo argomento non è null ed è possibile dereferenziarlo in modo sicuro:
public static void Main()
{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p!.Name}");
}
}

public static bool IsValid(Person? person)


{
return person != null && !string.IsNullOrEmpty(person.Name);
}

Senza l'operatore di indulgenza null, il compilatore genera l'avviso seguente per il p.Name codice:
Warning CS8602: Dereference of a possibly null reference .

Se è possibile modificare il IsValid metodo, è possibile usare l'attributo NotNullWhen per informare il
compilatore che un argomento del IsValid metodo non può essere null quando il metodo restituisce true :

public static void Main()


{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p.Name}");
}
}

public static bool IsValid([NotNullWhen(true)] Person? person)


{
return person != null && !string.IsNullOrEmpty(person.Name);
}

Nell'esempio precedente non è necessario usare l'operatore che perdona i valori null perché il compilatore
dispone di informazioni sufficienti per individuare che p non possono essere incluse null nell' if istruzione.
Per ulteriori informazioni sugli attributi che consentono di fornire informazioni aggiuntive sullo stato null di una
variabile, vedere aggiornare le API con attributi per definire le aspettative null.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione operatore con indulgenza null della bozza della specifica dei tipi di
riferimento Nullable.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Esercitazione: progettare con tipi di riferimento Nullable
?? e? = Operators (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L'operatore null-coalescing ?? restituisce il valore dell'operando a sinistra se è null ; in caso contrario, valuta
l'operando a destra e ne restituisce il risultato. L'operatore ?? non valuta l'operando di destra se l'operando di
sinistra restituisce un valore non Null.
Disponibile in C# 8,0 e versioni successive, l'operatore di assegnazione di Unione null ??= assegna il valore
dell'operando destro all'operando sinistro solo se l'operando sinistro restituisce null . L'operatore ??= non
valuta l'operando di destra se l'operando di sinistra restituisce un valore non Null.

List<int> numbers = null;


int? a = null;

(numbers ??= new List<int>()).Add(5);


Console.WriteLine(string.Join(" ", numbers)); // output: 5

numbers.Add(a ??= 0);


Console.WriteLine(string.Join(" ", numbers)); // output: 5 0
Console.WriteLine(a); // output: 0

L'operando sinistro dell' ??= operatore deve essere una variabile, una Proprietào un elemento indicizzatore .
In C# 7,3 e versioni precedenti, il tipo dell'operando sinistro dell' ?? operatore deve essere un tipo di
riferimento o un tipo di valore Nullable. A partire da C# 8,0, il requisito viene sostituito con quanto segue: il tipo
dell'operando sinistro degli ?? ??= operatori e non può essere un tipo di valore non nullable. In particolare, a
partire da C# 8,0, è possibile usare gli operatori di Unione null con i parametri di tipo non vincolati:

private static void Display<T>(T a, T backup)


{
Console.WriteLine(a ?? backup);
}

Gli operatori che uniscono i valori null sono associativi a destra. Ovvero espressioni nel formato

a ?? b ?? c
d ??= e ??= f

vengono valutati come

a ?? (b ?? c)
d ??= (e ??= f)

Esempi
Gli ?? ??= operatori e possono essere utili negli scenari seguenti:
Nelle espressioni con gli operatori condizionali null?. e? [], è possibile usare l' ?? operatore per fornire
un'espressione alternativa da valutare se il risultato dell'espressione con operazioni condizionali null è
null :
double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
{
return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}

var sum = SumNumbers(null, 0);


Console.WriteLine(sum); // output: NaN

Quando si utilizzano tipi di valore Nullable ed è necessario fornire un valore di un tipo di valore
sottostante, utilizzare l' ?? operatore per specificare il valore da fornire nel caso in cui un valore di tipo
Nullable sia null :

int? a = null;
int b = a ?? -1;
Console.WriteLine(b); // output: -1

Usare il metodo Nullable<T>.GetValueOrDefault() se il valore da usare quando un valore di tipo nullable


è null deve essere il valore predefinito del tipo valore sottostante.
A partire da C# 7,0, è possibile usare un' throw espressione come operando destro dell' ?? operatore
per rendere più conciso il codice per il controllo degli argomenti:

public string Name


{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value), "Name cannot be null");
}

L'esempio precedente dimostra anche come usare membri con corpo di espressione per definire una
proprietà.
A partire da C# 8,0, è possibile usare l' ??= operatore per sostituire il codice del modulo

if (variable is null)
{
variable = expression;
}

con il codice seguente:

variable ??= expression;

Overload degli operatori


?? ??= Non è possibile eseguire l'overload degli operatori e.

Specifiche del linguaggio C#


Per ulteriori informazioni sull' ?? operatore, vedere la sezione relativa all'operatore di Unione null della
specifica del linguaggio C#.
Per ulteriori informazioni sull' ??= operatore, vedere la Nota relativa alla proposta di funzionalità.
Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
?. e? operatori []
Operatore?:
Operatore => (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

Il => token è supportato in due formati: come operatore lambda e come separatore di un nome di membro e
dell'implementazione del membro in una definizione del corpo dell'espressione.

Operatore lambda
Nelle espressioni lambdal'operatore lambda => separa i parametri di input sul lato sinistro dal corpo
dell'espressione lambda sul lato destro.
L'esempio seguente illustra l'utilizzo delle espressioni lambda tramite la funzionalità LINQ con la sintassi dei
metodi:

string[] words = { "bot", "apple", "apricot" };


int minimalLength = words
.Where(w => w.StartsWith("a"))
.Min(w => w.Length);
Console.WriteLine(minimalLength); // output: 5

int[] numbers = { 4, 7, 10 };
int product = numbers.Aggregate(1, (interim, next) => interim * next);
Console.WriteLine(product); // output: 280

I parametri di input di un'espressione lambda sono fortemente tipizzati in fase di compilazione. Quando il
compilatore può dedurre i tipi di parametri di input, come nell'esempio precedente, è possibile omettere le
dichiarazioni di tipo. Se è necessario specificare il tipo di parametri di input, è necessario eseguire questa
operazione per ogni parametro, come illustrato nell'esempio seguente:

int[] numbers = { 4, 7, 10 };
int product = numbers.Aggregate(1, (int interim, int next) => interim * next);
Console.WriteLine(product); // output: 280

Nell'esempio seguente viene illustrato come definire un'espressione lambda senza parametri di input:

Func<string> greet = () => "Hello, World!";


Console.WriteLine(greet());

Per altre informazioni, vedere Espressioni lambda.

Definizione del corpo dell'espressione


Una definizione di corpo di espressione presenta la seguente sintassi generale:

member => expression;

dove expression è un'espressione valida. Il tipo restituito di expression deve essere convertibile in modo
implicito nel tipo restituito del membro. Se il tipo restituito del membro è void o se il membro è un costruttore,
un finalizzatore o una proprietà o una funzione di accesso dell'indicizzatore set , expression deve essere un'
espressione di istruzione. Poiché il risultato dell'espressione viene ignorato, il tipo restituito dell'espressione può
essere di qualsiasi tipo.
L'esempio seguente illustra una definizione del corpo dell'espressione per un metodo Person.ToString :

public override string ToString() => $"{fname} {lname}".Trim();

Si tratta di una versione abbreviata della definizione di metodo seguente:

public override string ToString()


{
return $"{fname} {lname}".Trim();
}

Le definizioni del corpo dell'espressione per metodi, operatori e proprietà di sola lettura sono supportate a
partire da C# 6. Le definizioni del corpo dell'espressione per costruttori, finalizzatori e funzioni di accesso a
proprietà e indicizzatori sono supportate a partire da C# 7,0.
Per altre informazioni, vedere Membri con corpo di espressione.

Overload degli operatori


Non è possibile eseguire l'overload dell'operatore => .

Specifiche del linguaggio C#


Per ulteriori informazioni sull'operatore lambda, vedere la sezione espressioni di funzione anonime della
specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatore :: (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Usare il qualificatore alias dello spazio dei nomi :: per accedere a un membro di uno spazio dei nomi con
alias. È possibile utilizzare il :: qualificatore solo tra due identificatori. L'identificatore a sinistra può essere uno
qualsiasi degli alias seguenti:
Alias dello spazio dei nomi creato con una direttiva alias using:

using forwinforms = System.Drawing;


using forwpf = System.Windows;

public class Converters


{
public static forwpf::Point Convert(forwinforms::Point point) => new forwpf::Point(point.X,
point.Y);
}

Un alias extern.
L'alias global , ovvero l'alias dello spazio dei nomi globale. Lo spazio dei nomi globale è lo spazio dei
nomi che contiene gli spazi dei nomi e i tipi non dichiarati all'interno di uno spazio dei nomi denominato.
Quando viene usato con il qualificatore :: , l'alias global fa sempre riferimento allo spazio dei nomi
globale, anche se è presente l'alias dello spazio dei nomi global definito dall'utente.
Nell'esempio seguente viene usato l'alias global per accedere allo spazio dei nomi .NET System, che è
un membro dello spazio dei nomi globale. Senza l'alias global , verrà eseguito l'accesso allo spazio dei
nomi System definito dall'utente, che è un membro dello spazio dei nomi MyCompany.MyProduct :

namespace MyCompany.MyProduct.System
{
class Program
{
static void Main() => global::System.Console.WriteLine("Using global alias");
}

class Console
{
string Suggestion => "Consider renaming this class";
}
}

NOTE
La parola chiave global è l'alias dello spazio dei nomi globale solo quando è l'identificatore di sinistra del
qualificatore :: .

È anche possibile usare il . token per accedere a un membro di uno spazio dei nomi con alias. Tuttavia, il .
token viene utilizzato anche per accedere a un membro del tipo. Il qualificatore :: garantisce che il relativo
identificatore di sinistra faccia sempre riferimento a un alias dello spazio dei nomi, anche se esiste un tipo o uno
spazio dei nomi con lo stesso nome.
Specifiche del linguaggio C#
Per altre informazioni, vedere la sezione Qualificatori di alias dello spazio dei nomi della specifica del linguaggio
C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Using Namespaces
Operatore await (Riferimenti per C#)
02/11/2020 • 6 minutes to read • Edit Online

L'operatore await sospende la valutazione del metodo async contenitore fino al completamento
dell'operazione asincrona rappresentata dal rispettivo operando. Quando l'operazione asincrona viene
completata, l'operatore await restituisce il risultato dell'operazione, se disponibile. Quando l' await operatore
viene applicato all'operando che rappresenta un'operazione già completata, restituisce immediatamente il
risultato dell'operazione senza sospensione del metodo contenitore. L'operatore await non blocca il thread che
valuta il metodo async. Quando l' await operatore sospende il metodo asincrono di inclusione, il controllo
torna al chiamante del metodo.
Nell'esempio seguente il metodo HttpClient.GetByteArrayAsync restituisce l'istanza Task<byte[]> , che
rappresenta un'operazione asincrona che produce una matrice di byte quando viene completata. Fino al
completamento dell'operazione, l'operatore await sospende il metodo DownloadDocsMainPageAsync . Quando
DownloadDocsMainPageAsync viene sospeso, il controllo viene restituito al metodo Main , che è il chiamante di
DownloadDocsMainPageAsync . Il metodo Main viene eseguito fino a quando non è necessario il risultato
dell'operazione asincrona eseguita dal metodo DownloadDocsMainPageAsync . Quando GetByteArrayAsync ottiene
tutti i byte, viene valutato il resto del metodo DownloadDocsMainPageAsync . Successivamente, viene valutato il
resto del metodo Main .

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class AwaitOperator


{
public static async Task Main()
{
Task<int> downloading = DownloadDocsMainPageAsync();
Console.WriteLine($"{nameof(Main)}: Launched downloading.");

int bytesLoaded = await downloading;


Console.WriteLine($"{nameof(Main)}: Downloaded {bytesLoaded} bytes.");
}

private static async Task<int> DownloadDocsMainPageAsync()


{
Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: About to start downloading.");

var client = new HttpClient();


byte[] content = await client.GetByteArrayAsync("https://docs.microsoft.com/en-us/");

Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: Finished downloading.");


return content.Length;
}
}
// Output similar to:
// DownloadDocsMainPageAsync: About to start downloading.
// Main: Launched downloading.
// DownloadDocsMainPageAsync: Finished downloading.
// Main: Downloaded 27700 bytes.

Nell'esempio precedente viene usato il Main metodo asincrono, che è possibile a partire da C# 7,1. Per altre
informazioni, vedere la sezione Operatore await nel metodo Main.
NOTE
Per un'introduzione alla programmazione asincrona, vedere Programmazione asincrona con async e await. La
programmazione asincrona con async e await segue il modello asincrono basato su attività.

È possibile usare l'operatore await solo in un metodo, in un'espressione lambda o in un metodo anonimo
modificato dalla parola chiave async. All'interno di un metodo asincrono, non è possibile usare l' await
operatore nel corpo di una funzione sincrona, all'interno del blocco di un' istruzione locke in un contesto unsafe .
L'operando dell'operatore await è in genere di uno dei tipi .NET seguenti: Task, Task<TResult>, ValueTask o
ValueTask<TResult>. Qualsiasi espressione awaitable, tuttavia, può essere l'operando dell'operatore await . Per
altre informazioni, vedere la sezione Espressioni awaitable in Specifica del linguaggio C#.
Il tipo di espressione await t è TResult se il tipo di espressione t è Task<TResult> o ValueTask<TResult>. Se
il tipo di t è Task o ValueTask, il tipo di await t è void . In entrambi i casi, se t genera un'eccezione, await t
genera nuovamente l'eccezione. Per altre informazioni sulla gestione delle eccezioni, vedere la sezione Eccezioni
nei metodi asincroni dell'articolo Istruzione try-catch.
Le async await parole chiave e sono disponibili in C# 5 e versioni successive.

Flussi asincroni e disposable


A partire da C# 8,0, è possibile usare flussi asincroni e disposable.
L'istruzione viene utilizzata await foreach per utilizzare un flusso di dati asincrono. Per ulteriori informazioni,
vedere l'articolo dell' foreach istruzione e la sezione relativa ai flussi asincroni dell'articolo novità in C# 8,0 .
Usare l' await using istruzione per lavorare con un oggetto eliminabile in modo asincrono, ovvero un oggetto
di un tipo che implementa un' IAsyncDisposable interfaccia. Per ulteriori informazioni, vedere la sezione using
Async Disposable dell'articolo implementare un metodo DisposeAsync .

Operatore await nel metodo Main


A partire da C# 7,1, il Main Metodo, che è il punto di ingresso dell'applicazione, può restituire Task o
Task<int> , consentendo che sia asincrono, in modo da poter usare l' await operatore nel corpo. Nelle versioni
di C# precedenti, per garantire che il metodo Main attenda il completamento di un'operazione asincrona, è
possibile recuperare il valore della proprietà Task<TResult>.Result dell'istanza di Task<TResult> restituita dal
metodo asincrono corrispondente. Per le operazioni asincrone che non producono un valore, è possibile
chiamare il metodo Task.Wait. Per informazioni su come selezionare la versione della lingua, vedere controllo
delle versioni del linguaggio C#.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni await in Specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
async
Modello di programmazione asincrona attività
Programmazione asincrona
La programmazione asincrona in dettaglio
Procedura dettagliata: accesso al Web tramite Async e await
Esercitazione: generare e utilizzare flussi asincroni con C# 8,0 e .NET Core 3,0
espressioni con valore predefinito (riferimenti per
C#)
02/11/2020 • 2 minutes to read • Edit Online

Un'espressione con valore predefinito produce il valore predefinito di un tipo. Esistono due tipi di espressioni
con valore predefinito: la chiamata dell' operatore predefinita e un valore letterale predefinito.
È inoltre possibile utilizzare la default parola chiave come etichetta case predefinita all'interno di un' switch
istruzione.

operatore default
L'argomento dell'operatore default deve essere il nome o il parametro di un tipo, come illustrato nell'esempio
seguente:

Console.WriteLine(default(int)); // output: 0
Console.WriteLine(default(object) is null); // output: True

void DisplayDefaultOf<T>()
{
var val = default(T);
Console.WriteLine($"Default value of {typeof(T)} is {(val == null ? "null" : val.ToString())}.");
}

DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
// Default value of System.Nullable`1[System.Int32] is null.
// Default value of System.Numerics.Complex is (0, 0).
// Default value of System.Collections.Generic.List`1[System.Int32] is null.

valore letterale predefinito


A partire da C# 7.1, è possibile usare il valore letterale default per produrre il valore predefinito di un tipo
quando il compilatore è in grado di dedurre il tipo di espressione. L'espressione letterale default produce lo
stesso valore dell'espressione default(T) in cui T è il tipo derivato. È possibile usare il valore letterale
default in uno dei seguenti casi:

Nell'assegnazione o nell'inizializzazione di una variabile.


Nella dichiarazione del valore predefinito per un parametro del metodo facoltativo.
In una chiamata al metodo per fornire un valore di argomento.
In un' return istruzione o come espressione in un membro con corpo di espressione.
L'esempio seguente illustra l'utilizzo del valore letterale default :
T[] InitializeArray<T>(int length, T initialValue = default)
{
if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length), "Array length must be nonnegative.");
}

var array = new T[length];


for (var i = 0; i < length; i++)
{
array[i] = initialValue;
}
return array;
}

void Display<T>(T[] values) => Console.WriteLine($"[ {string.Join(", ", values)} ]");

Display(InitializeArray<int>(3)); // output: [ 0, 0, 0 ]
Display(InitializeArray<bool>(4, default)); // output: [ False, False, False, False ]

System.Numerics.Complex fillValue = default;


Display(InitializeArray(3, fillValue)); // output: [ (0, 0), (0, 0), (0, 0) ]

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni con valore predefinito della specifica del linguaggio C#.
Per altre informazioni sul valore letterale default , vedere la nota relativa alla proposta di funzionalità.

Vedi anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Valori predefiniti dei tipi C#
Generics in .NET
Operatore delegate (Riferimenti per C#)
02/11/2020 • 3 minutes to read • Edit Online

L'operatore delegate crea un metodo anonimo che può essere convertito in un tipo delegato:

Func<int, int, int> sum = delegate (int a, int b) { return a + b; };


Console.WriteLine(sum(3, 4)); // output: 7

NOTE
A partire da C# 3, le espressioni lambda forniscono un modo più conciso ed espressivo per creare una funzione anonima.
Usare l'operatore => per costruire un'espressione lambda:

Func<int, int, int> sum = (a, b) => a + b;


Console.WriteLine(sum(3, 4)); // output: 7

Per altre informazioni sulle funzionalità delle espressioni lambda, ad esempio l'acquisizione di variabili esterne, vedere
Espressioni lambda.

Quando si usa l'operatore delegate , è possibile omettere l'elenco di parametri. In tal caso, il metodo anonimo
creato può essere convertito in un tipo delegato con qualsiasi elenco di parametri, come illustrato nell'esempio
seguente:

Action greet = delegate { Console.WriteLine("Hello!"); };


greet();

Action<int, double> introduce = delegate { Console.WriteLine("This is world!"); };


introduce(42, 2.7);

// Output:
// Hello!
// This is world!

Questa è l'unica funzionalità di metodi anonimi non supportata dalle espressioni lambda. In tutti gli altri casi,
un'espressione lambda è la modalità preferita per scrivere codice inline.
A partire da C# 9,0, è possibile usare le variabili Discard per specificare due o più parametri di input di un
metodo anonimo che non vengono usati dal metodo:

Func<int, int, int> constant = delegate (int _, int _) { return 42; };


Console.WriteLine(constant(3, 4)); // output: 42

Per la compatibilità con le versioni precedenti, se un solo parametro è denominato _ , _ viene considerato
come nome del parametro all'interno di un metodo anonimo.
A partire da C# 9,0, è anche possibile usare il static modificatore nella dichiarazione di un metodo anonimo:

Func<int, int, int> sum = static delegate (int a, int b) { return a + b; };


Console.WriteLine(sum(10, 4)); // output: 14
Un metodo anonimo statico non può acquisire le variabili locali o lo stato dell'istanza dagli ambiti di inclusione.
È anche possibile usare la parola chiave delegate per dichiarare un tipo delegato.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni di funzioni anonime della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
= Operatore>
espressione NameOf (riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Un' nameof espressione produce il nome di una variabile, di un tipo o di un membro come costante stringa:

Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic


Console.WriteLine(nameof(List<int>)); // output: List
Console.WriteLine(nameof(List<int>.Count)); // output: Count
Console.WriteLine(nameof(List<int>.Add)); // output: Add

var numbers = new List<int> { 1, 2, 3 };


Console.WriteLine(nameof(numbers)); // output: numbers
Console.WriteLine(nameof(numbers.Count)); // output: Count
Console.WriteLine(nameof(numbers.Add)); // output: Add

Come illustrato nell'esempio precedente, nel caso di un tipo e di uno spazio dei nomi, il nome prodotto in
genere non è completo.
Nel caso degli identificatori Verbatim, il @ carattere non è la parte di un nome, come illustrato nell'esempio
seguente:

var @new = 5;
Console.WriteLine(nameof(@new)); // output: new

Un' nameof espressione viene valutata in fase di compilazione e non ha alcun effetto in fase di esecuzione.
È possibile usare un' nameof espressione per rendere più gestibile il codice per il controllo degli argomenti:

public string Name


{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null");
}

Un' nameof espressione è disponibile in C# 6 e versioni successive.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Espressioni Nameof in Specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatore new (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L'operatore new consente di creare una nuova istanza di un tipo.


È anche possibile usare la parola chiave new come modificatore di dichiarazione di membro o come vincolo di
tipo generico.

Chiamata di un costruttore
Per creare una nuova istanza di un tipo, in genere si chiama uno dei costruttori del tipo in questione tramite
l'operatore new :

var dict = new Dictionary<string, int>();


dict["first"] = 10;
dict["second"] = 20;
dict["third"] = 30;

Console.WriteLine(string.Join("; ", dict.Select(entry => $"{entry.Key}: {entry.Value}")));


// Output:
// first: 10; second: 20; third: 30

È possibile usare un inizializzatore di oggetto o insieme con l'operatore new per creare un'istanza di un oggetto
e inizializzare l'oggetto in un'unica istruzione, come illustrato nell'esempio seguente:

var dict = new Dictionary<string, int>


{
["first"] = 10,
["second"] = 20,
["third"] = 30
};

Console.WriteLine(string.Join("; ", dict.Select(entry => $"{entry.Key}: {entry.Value}")));


// Output:
// first: 10; second: 20; third: 30

A partire da C# 9,0, le espressioni di chiamata del costruttore sono tipizzate come destinazione. Ovvero, se un
tipo di destinazione di un'espressione è noto, è possibile omettere un nome di tipo, come illustrato nell'esempio
seguente:

List<int> xs = new();
List<int> ys = new(capacity: 10_000);
List<int> zs = new() { Capacity = 20_000 };

Dictionary<int, List<int>> lookup = new()


{
[1] = new() { 1, 2, 3 },
[2] = new() { 5, 8, 3 },
[5] = new() { 1, 0, 4 }
};

Come illustrato nell'esempio precedente, si usano sempre le parentesi in un'espressione tipizzata di


destinazione new .
Se un tipo di destinazione di un' new espressione è sconosciuto (ad esempio, quando si usa la var parola
chiave), è necessario specificare un nome di tipo.

creazione di matrici
È possibile usare l'operatore new per creare un'istanza di matrice, come illustrato nell'esempio seguente:

var numbers = new int[3];


numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

Console.WriteLine(string.Join(", ", numbers));


// Output:
// 10, 20, 30

Usare la sintassi di inizializzazione di una matrice per creare un'istanza di una matrice e popolarla con elementi
in un'unica istruzione. L'esempio seguente illustra diversi modi per eseguire questa operazione:

var a = new int[3] { 10, 20, 30 };


var b = new int[] { 10, 20, 30 };
var c = new[] { 10, 20, 30 };
Console.WriteLine(c.GetType()); // output: System.Int32[]

Per altre informazioni sulle matrici, vedere Matrici.

Creazione di istanze di tipi anonimi


Per creare un'istanza di un tipo anonimo, usare l'operatore new e la sintassi dell'inizializzatore di oggetto:

var example = new { Greeting = "Hello", Name = "World" };


Console.WriteLine($"{example.Greeting}, {example.Name}!");
// Output:
// Hello, World!

Eliminazione di istanze di tipi


Non è necessario eliminare definitivamente istanze di tipi create in precedenza. Le istanze dei tipi riferimento e
valore vengono eliminate definitivamente in modo automatico. Le istanze dei tipi valore vengono eliminate
definitivamente non appena viene eliminato definitivamente il contesto che le contiene. Le istanze dei tipi di
riferimento vengono distrutte dal Garbage Collector in un momento non specificato dopo che è stato rimosso
l'ultimo riferimento.
Per le istanze di tipo che contengono risorse non gestite, ad esempio un handle di file, è consigliabile usare la
pulizia deterministica per assicurarsi che le risorse che contengono siano rilasciate il prima possibile. Per altre
informazioni, vedere le informazioni di riferimento dell'API System.IDisposable e l'articolo sull' istruzione using .

Overload degli operatori


Un tipo definito dall'utente non può eseguire l'overload dell'operatore new .

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatore new della specifica del linguaggio C#.
Per ulteriori informazioni su un'espressione tipizzata di destinazione new , vedere la Nota relativa alla proposta
di funzionalità.
Vedi anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Inizializzatori di oggetto e di raccolta
Operatore sizeof (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

L'operatore sizeof restituisce il numero di byte occupati da una variabile di un tipo specificato. L'argomento
dell'operatore sizeof deve essere il nome di un tipo non gestito o un parametro di tipo vincolato a un tipo non
gestito.
L'operatore sizeof richiede un contesto unsafe. Tuttavia, per le espressioni presentate nella tabella seguente
vengono restituiti i valori costanti corrispondenti in fase di compilazione e non è richiesto un contesto unsafe:

EXP RESSIO N VA LO RE C O STA N T E

sizeof(sbyte) 1

sizeof(byte) 1

sizeof(short) 2

sizeof(ushort) 2

sizeof(int) 4

sizeof(uint) 4

sizeof(long) 8

sizeof(ulong) 8

sizeof(char) 2

sizeof(float) 4

sizeof(double) 8

sizeof(decimal) 16

sizeof(bool) 1

Non è inoltre necessario usare un contesto unsafe quando l'operando dell'operatore sizeof è il nome di un
tipo enum.
Nell'esempio seguente viene illustrato l'uso dell'operatore sizeof :
using System;

public struct Point


{
public Point(byte tag, double x, double y) => (Tag, X, Y) = (tag, x, y);

public byte Tag { get; }


public double X { get; }
public double Y { get; }
}

public class SizeOfOperator


{
public static void Main()
{
Console.WriteLine(sizeof(byte)); // output: 1
Console.WriteLine(sizeof(double)); // output: 8

DisplaySizeOf<Point>(); // output: Size of Point is 24


DisplaySizeOf<decimal>(); // output: Size of System.Decimal is 16

unsafe
{
Console.WriteLine(sizeof(Point*)); // output: 8
}
}

static unsafe void DisplaySizeOf<T>() where T : unmanaged


{
Console.WriteLine($"Size of {typeof(T)} is {sizeof(T)}");
}
}

L'operatore sizeof restituisce un numero di byte che verrebbero allocati dal Common Language Runtime in
managed memory. Per i tipi struct questo valore include l'eventuale riempimento, come illustrato nell'esempio
precedente. Il risultato dell'operatore sizeof può essere diverso da quello del metodo Marshal.SizeOf, che
restituisce le dimensioni di un tipo nella memoria non gestita.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Operatore sizeof della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatori relativi al puntatore
Tipi puntatore
Tipi correlati alla memoria e agli intervalli
Generics in .NET
espressione stackalloc (riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Un' stackalloc espressione alloca un blocco di memoria nello stack. Un blocco di memoria allocato nello stack,
creato durante l'esecuzione del metodo, viene automaticamente eliminato alla restituzione del metodo. Non è
possibile liberare in modo esplicito la memoria allocata con stackalloc . Un blocco di memoria allocata nello
stack non è soggetto a Garbage Collection e non deve essere aggiunto con un' fixed istruzione.
È possibile assegnare il risultato di un' stackalloc espressione a una variabile di uno dei tipi seguenti:
A partire da C# 7,2 System.Span<T> o System.ReadOnlySpan<T> , come illustrato nell'esempio
seguente:

int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}

Non è necessario usare un contesto unsafe quando si assegna un blocco di memoria allocato nello stack
a una variabile Span<T> o ReadOnlySpan<T>.
Se si usano questi tipi, è possibile applicare un'espressione stackalloc in espressioni condizionali o di
assegnazione, come illustrato nell'esempio seguente.

int length = 1000;


Span<byte> buffer = length <= 1024 ? stackalloc byte[length] : new byte[length];

A partire da C# 8,0, è possibile usare un' stackalloc espressione all'interno di altre espressioni ogni
volta che Span<T> ReadOnlySpan<T> è consentita una variabile o, come illustrato nell'esempio
seguente:

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };


var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 });
Console.WriteLine(ind); // output: 1

NOTE
In presenza di memoria allocata nello stack, è consigliabile usare il tipo Span<T> o ReadOnlySpan<T> ogni
qualvolta sia possibile.

Un tipo di puntatore, come nell'esempio seguente.


unsafe
{
int length = 3;
int* numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
}

Come illustrato nell'esempio precedente, quando si usa un tipo di puntatore è necessario adottare un
contesto unsafe .
Nel caso dei tipi di puntatore, è possibile usare un' stackalloc espressione solo in una dichiarazione di
variabile locale per inizializzare la variabile.
La quantità di memoria disponibile nello stack è limitata. Se si alloca una quantità eccessiva di memoria nello
stack, StackOverflowException viene generata un'eccezione. Per evitare questo, attenersi alle regole seguenti:
Limitare la quantità di memoria allocata con stackalloc :

const int MaxStackLimit = 1024;


Span<byte> buffer = inputLength <= MaxStackLimit ? stackalloc byte[inputLength] : new
byte[inputLength];

Poiché la quantità di memoria disponibile nello stack dipende dall'ambiente in cui viene eseguito il
codice, è conservabile quando si definisce il valore del limite effettivo.
Evitare l'uso di stackalloc cicli interni. Allocare il blocco di memoria all'esterno di un ciclo e riutilizzarlo
all'interno del ciclo.
Il contenuto della memoria appena allocata non è definito. È necessario inizializzarlo prima di usarlo. Ad
esempio, è possibile usare il Span<T>.Clear metodo che imposta tutti gli elementi sul valore predefinito di tipo
T .

A partire da C# 7,3, è possibile usare la sintassi dell'inizializzatore di matrici per definire il contenuto della
memoria appena allocata. Nell'esempio seguente vengono illustrati vari modi per eseguire questa operazione.

Span<int> first = stackalloc int[3] { 1, 2, 3 };


Span<int> second = stackalloc int[] { 1, 2, 3 };
ReadOnlySpan<int> third = stackalloc[] { 1, 2, 3 };

In Expression stackalloc T[E] T deve essere un tipo non gestito e E deve restituire un valore int non
negativo.

Sicurezza
L'uso di stackalloc attiva automaticamente le funzionalità di rilevazione del sovraccarico del buffer in
Common Language Runtime (CLR). Se viene rilevato un sovraccarico del buffer, il processo viene terminato il
più rapidamente possibile per ridurre al minimo la possibilità che venga eseguito codice dannoso.

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere la sezione relativa all' allocazione dello stack della specifica del linguaggio C# e
la nota relativa alla funzionalità Consenti stackalloc in contesti annidati .
Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatori relativi al puntatore
Tipi puntatore
Tipi correlati alla memoria e agli intervalli
DOS e non stackalloc
espressione switch (riferimenti per C#)
28/01/2021 • 6 minutes to read • Edit Online

Questo articolo illustra l' switch espressione introdotta in C# 8,0. Per informazioni sull' switch istruzione,
vedere l'articolo sull' switch istruzione nella sezione statements .

Esempio di base
L' switch espressione fornisce la switch semantica like in un contesto di espressione. Fornisce una sintassi
concisa quando le braccia del commutire producono un valore. Nell'esempio seguente viene illustrata la
struttura di un'espressione switch. Converte i valori da un oggetto enum che rappresenta le direzioni visive in
un mapping online alla direzione Cardinal corrispondente:

public static class SwitchExample


{
public enum Directions
{
Up,
Down,
Right,
Left
}

public enum Orientation


{
North,
South,
East,
West
}

public static void Main()


{
var direction = Directions.Right;
Console.WriteLine($"Map view direction is {direction}");

var orientation = direction switch


{
Directions.Up => Orientation.North,
Directions.Right => Orientation.East,
Directions.Down => Orientation.South,
Directions.Left => Orientation.West,
};
Console.WriteLine($"Cardinal orientation is {orientation}");
}
}

L'esempio precedente Mostra gli elementi di base di un'espressione switch:


Espressione di intervallo: nell'esempio precedente viene utilizzata la variabile direction come espressione
di intervallo.
L' espressione switch Arms: ogni espressione switch ARM contiene un pattern, un case Guard facoltativo, il
=> token e un' espressione.

Il risultato dell' espressione switch è il valore dell'espressione del primo ARM dell'espressione switch il cui
modello corrisponde all' espressione di intervallo e il cui case Guard, se presente, restituisce true . L'
espressione a destra del => token non può essere un'istruzione di espressione.
Le armi dell'espressione switch vengono valutate in ordine di testo. Il compilatore genera un errore quando non
è possibile scegliere un ARM con un' espressione di cambio inferiore perché un ARM dell' espressione del
comcambio superiore corrisponde a tutti i valori.

Modelli e protezioni tra maiuscole e minuscole


Molti modelli sono supportati nelle braccia dell'espressione switch. Nell'esempio precedente viene usato un
criterio costante. Un criterio costante confronta l'espressione di intervallo con un valore. Tale valore deve essere
una costante in fase di compilazione. Il modello di tipo confronta l'espressione di intervallo con un tipo
conosciuto. Nell'esempio seguente viene recuperato il terzo elemento da una sequenza. Usa metodi diversi in
base al tipo della sequenza:

public static T TypeExample<T>(IEnumerable<T> sequence) =>


sequence switch
{
System.Array array => (T)array.GetValue(2),
IList<T> list => list[2],
IEnumerable<T> seq => seq.Skip(2).First(),
};

I modelli possono essere ricorsivi, in cui un modello verifica un tipo e, se tale tipo corrisponde, il modello
corrisponde a uno o più valori di proprietà nell'espressione di intervallo. Per estendere l'esempio precedente, è
possibile usare modelli ricorsivi. Si aggiungono le braccia dell'espressione switch per le matrici che contengono
meno di 3 elementi. I modelli ricorsivi sono illustrati nell'esempio seguente:

public static T RecursiveExample<T>(IEnumerable<T> sequence) =>


sequence switch
{
System.Array { Length : 0} => default(T),
System.Array { Length : 1} array => (T)array.GetValue(0),
System.Array { Length : 2} array => (T)array.GetValue(1),
System.Array array => (T)array.GetValue(2),
IList<T> list => list[2],
IEnumerable<T> seq => seq.Skip(2).First(),
};

I modelli ricorsivi possono esaminare le proprietà dell'espressione di intervallo, ma non possono eseguire
codice arbitrario. È possibile usare una protezione del case, specificata in una when clausola, per fornire controlli
simili per altri tipi di sequenza:

public static T CaseGuardExample<T>(IEnumerable<T> sequence) =>


sequence switch
{
System.Array { Length : 0} => default(T),
System.Array { Length : 1} array => (T)array.GetValue(0),
System.Array { Length : 2} array => (T)array.GetValue(1),
System.Array array => (T)array.GetValue(2),
IEnumerable<T> list when !list.Any() => default(T),
IEnumerable<T> list when list.Count() < 3 => list.Last(),
IList<T> list => list[2],
IEnumerable<T> seq => seq.Skip(2).First(),
};

Infine, è possibile aggiungere il _ modello e il null criterio per intercettare gli argomenti che non vengono
elaborati da qualsiasi altra espressione switch ARM. In questo modo l'espressione switch è esaustiva, ovvero
viene gestito qualsiasi valore possibile dell'espressione di intervallo. Nell'esempio seguente vengono aggiunte
le espressioni Arms:

public static T ExhaustiveExample<T>(IEnumerable<T> sequence) =>


sequence switch
{
System.Array { Length : 0} => default(T),
System.Array { Length : 1} array => (T)array.GetValue(0),
System.Array { Length : 2} array => (T)array.GetValue(1),
System.Array array => (T)array.GetValue(2),
IEnumerable<T> list
when !list.Any() => default(T),
IEnumerable<T> list
when list.Count() < 3 => list.Last(),
IList<T> list => list[2],
null => throw new ArgumentNullException(nameof(sequence)),
_ => sequence.Skip(2).First(),
};

Nell'esempio precedente viene aggiunto un null modello e il IEnumerable<T> modello di tipo viene modificato
in un _ modello. Il null modello fornisce un controllo null come ARM dell'espressione switch. L'espressione
per tale ARM genera un'eccezione ArgumentNullException . Il _ modello corrisponde a tutti gli input per i quali
non sono state trovate corrispondenze con le armi precedenti. Il controllo deve essere eseguito dopo il null
controllo oppure corrisponde a null input.

Espressioni switch non esaustive


Se nessuno dei modelli di un'espressione switch rileva un argomento, il runtime genera un'eccezione. In .NET
Core 3,0 e versioni successive, l'eccezione è System.Runtime.CompilerServices.SwitchExpressionException . In
.NET Framework, l'eccezione è InvalidOperationException .

Vedere anche
Proposte specifiche del linguaggio C# per modelli ricorsivi
Informazioni di riferimento su C#
Operatori ed espressioni C#
Criteri di ricerca
Operatori true e false (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

L' true operatore restituisce il bool valore bool true per indicare che l'operando è sicuramente true. L' false
operatore restituisce il bool valore true per indicare che l'operando è decisamente false. Gli operatori true e
false non sono necessariamente complementari tra loro. Questo significa che entrambi gli operatori true e
false possono restituire il valore bool``false per lo stesso operando. Se un tipo definisce uno dei due
operatori, deve definire anche l'altro operatore.

TIP
Usare il bool? tipo, se è necessario supportare la logica a tre valori, ad esempio quando si lavora con database che
supportano un tipo booleano a tre valori. In C# sono disponibili gli operatori & e | che supportano la logica a tre
valori con gli operandi bool? . Per altre informazioni, vedere la sezione Operatori logici booleani nullable dell'articolo
Operatori logici booleani.

Espressioni booleane
Un tipo con l'operatore true definito può essere il tipo di un risultato di un'espressione condizionale di
controllo nelle istruzioni if, do, while e for e nell'operatore condizionale ?: . Per altre informazioni, vedere la
sezione Espressioni booleane della specifica del linguaggio C#.

Operatori logici condizionali definiti dall'utente


Se un tipo con gli operatori true e false definiti esegue l'overload dell'operatore logico OR | o
dell'operatore AND logico & in un certo modo, l'operatore OR logico condizionale || oppure l'operatore AND
logico condizionale && , rispettivamente, può essere valutato per gli operandi di quel tipo. Per altre informazioni,
vedere la sezione Operatori logici condizionali definiti dall'utente di Specifica del linguaggio C#.

Esempio
L'esempio seguente presenta il tipo che definisce entrambi gli operatori true e false . Il tipo, inoltre, consente
di eseguire l'overload dell'operatore logico AND & in modo che l' && operatore possa essere valutato anche
per gli operandi di quel tipo.
using System;

public struct LaunchStatus


{
public static readonly LaunchStatus Green = new LaunchStatus(0);
public static readonly LaunchStatus Yellow = new LaunchStatus(1);
public static readonly LaunchStatus Red = new LaunchStatus(2);

private int status;

private LaunchStatus(int status)


{
this.status = status;
}

public static bool operator true(LaunchStatus x) => x == Green || x == Yellow;


public static bool operator false(LaunchStatus x) => x == Red;

public static LaunchStatus operator &(LaunchStatus x, LaunchStatus y)


{
if (x == Red || y == Red || (x == Yellow && y == Yellow))
{
return Red;
}

if (x == Yellow || y == Yellow)
{
return Yellow;
}

return Green;
}

public static bool operator ==(LaunchStatus x, LaunchStatus y) => x.status == y.status;


public static bool operator !=(LaunchStatus x, LaunchStatus y) => !(x == y);

public override bool Equals(object obj) => obj is LaunchStatus other && this == other;
public override int GetHashCode() => status;
}

public class LaunchStatusTest


{
public static void Main()
{
LaunchStatus okToLaunch = GetFuelLaunchStatus() && GetNavigationLaunchStatus();
Console.WriteLine(okToLaunch ? "Ready to go!" : "Wait!");
}

static LaunchStatus GetFuelLaunchStatus()


{
Console.WriteLine("Getting fuel launch status...");
return LaunchStatus.Red;
}

static LaunchStatus GetNavigationLaunchStatus()


{
Console.WriteLine("Getting navigation launch status...");
return LaunchStatus.Yellow;
}
}

Si noti il comportamento che causa il corto circuito dell'operatore && . Quando il metodo GetFuelLaunchStatus
restituisce LaunchStatus.Red , l'operando di destra dell'operatore && non viene valutato. Il motivo è che
LaunchStatus.Red è indubbiamente false. Pertanto il risultato dell'operatore AND logico non dipende dal valore
dell'operando di destra. L'output dell'esempio è il seguente:
Getting fuel launch status...
Wait!

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
espressione with (riferimenti per C#)
28/01/2021 • 4 minutes to read • Edit Online

Disponibile in C# 9,0 e versioni successive, un' with espressione produce una copia dell'operando del record
con le proprietà e i campi specificati modificati:

using System;

public class WithExpressionBasicExample


{
public record NamedPoint(string Name, int X, int Y);

public static void Main()


{
var p1 = new NamedPoint("A", 0, 0);
Console.WriteLine($"{nameof(p1)}: {p1}"); // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }

var p2 = p1 with { Name = "B", X = 5 };


Console.WriteLine($"{nameof(p2)}: {p2}"); // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }

var p3 = p1 with
{
Name = "C",
Y = 4
};
Console.WriteLine($"{nameof(p3)}: {p3}"); // output: p3: NamedPoint { Name = C, X = 0, Y = 4 }

Console.WriteLine($"{nameof(p1)}: {p1}"); // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }


}
}

Come illustrato nell'esempio precedente, si usa la sintassi dell' inizializzatore di oggetto per specificare quali
membri modificare e i relativi nuovi valori. In un' with espressione, un operando di sinistra deve essere di un
tipo di record.
Il risultato di un' with espressione ha lo stesso tipo di runtime dell'operando dell'espressione, come illustrato
nell'esempio seguente:

using System;

public class InheritanceExample


{
public record Point(int X, int Y);
public record NamedPoint(string Name, int X, int Y) : Point(X, Y);

public static void Main()


{
Point p1 = new NamedPoint("A", 0, 0);
Point p2 = p1 with { X = 5, Y = 3 };
Console.WriteLine(p2 is NamedPoint); // output: True
Console.WriteLine(p2); // output: NamedPoint { X = 5, Y = 3, Name = A }

}
}

Nel caso di un membro di tipo riferimento, quando viene copiato un record viene copiato solo il riferimento a
un'istanza. Sia la copia che il record originale hanno accesso alla stessa istanza di tipo riferimento. L'esempio
seguente illustra questo comportamento:

using System;
using System.Collections.Generic;

public class ExampleWithReferenceType


{
public record TaggedNumber(int Number, List<string> Tags)
{
public string PrintTags() => string.Join(", ", Tags);
}

public static void Main()


{
var original = new TaggedNumber(1, new List<string> { "A", "B" });

var copy = original with { Number = 2 };


Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
// output: Tags of copy: A, B

original.Tags.Add("C");
Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
// output: Tags of copy: A, B, C
}
}

Qualsiasi tipo di record ha il costruttore di copia. Si tratta di un costruttore con un solo parametro del tipo di
record che lo contiene. Copia lo stato del relativo argomento in una nuova istanza di record. Alla valutazione di
un' with espressione, viene chiamato il costruttore di copia per creare un'istanza di una nuova istanza di record
in base a un record originale. Successivamente, la nuova istanza viene aggiornata in base alle modifiche
specificate. Per impostazione predefinita, il costruttore di copia è implicito, ovvero generato dal compilatore. Se è
necessario personalizzare la semantica di copia dei record, dichiarare in modo esplicito un costruttore di copia
con il comportamento desiderato. Nell'esempio seguente viene aggiornato l'esempio precedente con un
costruttore di copia esplicito. Il nuovo comportamento di copia consiste nel copiare gli elementi dell'elenco
invece di un riferimento a un elenco quando viene copiato un record:
using System;
using System.Collections.Generic;

public class UserDefinedCopyConstructorExample


{
public record TaggedNumber(int Number, List<string> Tags)
{
protected TaggedNumber(TaggedNumber original)
{
Number = original.Number;
Tags = new List<string>(original.Tags);
}

public string PrintTags() => string.Join(", ", Tags);


}

public static void Main()


{
var original = new TaggedNumber(1, new List<string> { "A", "B" });

var copy = original with { Number = 2 };


Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
// output: Tags of copy: A, B

original.Tags.Add("C");
Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
// output: Tags of copy: A, B
}
}

Specifiche del linguaggio C#


Per ulteriori informazioni, vedere le sezioni riportate di seguito della Nota sulla funzionalità relativa ai record:
with espressione
Copiare e clonare i membri

Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Overload degli operatori (Riferimento C#)
28/01/2021 • 7 minutes to read • Edit Online

Un tipo definito dall'utente può eseguire l'overload di un operatore C# definito in precedenza. Ovvero, un tipo
può fornire l'implementazione personalizzata di un'operazione nel caso in cui uno o entrambi gli operandi siano
di quel tipo. La sezione Operatori che supportano l'overload illustra gli operatori C# che possono essere
sottoposti a overload.
Per dichiarare un operatore, usare la parola chiave operator . Una dichiarazione di operatore deve soddisfare le
regole seguenti:
Include sia un modificatore public che un modificatore static .
Un operatore unario ha un parametro di input. Un operatore binario ha due parametri di input. In entrambi i
casi almeno un parametro deve essere di tipo T o T? , dove T è il tipo che contiene la dichiarazione
dell'operatore.
L'esempio seguente definisce una struttura semplificata per la rappresentazione di un numero razionale. La
struttura esegue l'overload di alcuni operatori aritmetici:
using System;

public readonly struct Fraction


{
private readonly int num;
private readonly int den;

public Fraction(int numerator, int denominator)


{
if (denominator == 0)
{
throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
}
num = numerator;
den = denominator;
}

public static Fraction operator +(Fraction a) => a;


public static Fraction operator -(Fraction a) => new Fraction(-a.num, a.den);

public static Fraction operator +(Fraction a, Fraction b)


=> new Fraction(a.num * b.den + b.num * a.den, a.den * b.den);

public static Fraction operator -(Fraction a, Fraction b)


=> a + (-b);

public static Fraction operator *(Fraction a, Fraction b)


=> new Fraction(a.num * b.num, a.den * b.den);

public static Fraction operator /(Fraction a, Fraction b)


{
if (b.num == 0)
{
throw new DivideByZeroException();
}
return new Fraction(a.num * b.den, a.den * b.num);
}

public override string ToString() => $"{num} / {den}";


}

public static class OperatorOverloading


{
public static void Main()
{
var a = new Fraction(5, 4);
var b = new Fraction(1, 2);
Console.WriteLine(-a); // output: -5 / 4
Console.WriteLine(a + b); // output: 14 / 8
Console.WriteLine(a - b); // output: 6 / 8
Console.WriteLine(a * b); // output: 5 / 8
Console.WriteLine(a / b); // output: 10 / 4
}
}

È possibile estendere l'esempio precedente definendo una conversione implicita da int a Fraction . Gli
operatori di overload supporteranno quindi gli argomenti di questi due tipi. Diventerà quindi possibile
aggiungere un numero intero a una frazione e ottenere di conseguenza una frazione.
È anche possibile usare la parola chiave operator per definire una conversione del tipo personalizzata. Per altre
informazioni, vedere Operatori di conversione definiti dall'utente.

Operatori che supportano l'overload


La tabella seguente fornisce informazioni sugli operatori C# che è possibile sottoporre a overload:

O P ERATO RI P O SSIB IL ITÀ DI O VERLO A D

+x, -x, !x, ~x, ++, --, true, false Questi operatori unari possono essere sottoposti a overload.

x + y, x-y, x * y, x/y, x% y, x & y, x | y, x ^ y, x << y, x >> y, x Questi operatori binari possono essere sottoposti a
= = y, x! = y, x < y, x > y, x <= y, x > = y overload. Alcuni operatori devono essere sottoposti a
overload in coppia: per altre informazioni, vedere la nota
dopo questa tabella.

x && y, x || y Non è possibile eseguire l'overload degli operatori logici


condizionali. Tuttavia, se un tipo con gli operatori di overload
true e false , inoltre, può eseguire l'overload dell' &
| operatore o in un certo modo, l' && || operatore o,
rispettivamente, può essere valutato per gli operandi di quel
tipo. Per altre informazioni, vedere la sezione Operatori logici
condizionali definiti dall'utente di Specifica del linguaggio C#.

[], a?[i] L'accesso all'elemento non viene considerato come


operatore con supporto dell'overload, ma è possibile definire
un indicizzatore.

(T) x Non è possibile eseguire l'overload dell'operatore cast, ma è


possibile definire conversioni di tipi personalizzate che
possono essere eseguite da un'espressione cast. Per altre
informazioni, vedere Operatori di conversione definiti
dall'utente.

+=, -= , *= , /= , %= , &= , |=, ^= , <<=, >>= Gli operatori di assegnazione composta non possono essere
sottoposti a overload in modo esplicito. Quando viene
eseguito l'overload di un operatore binario, viene tuttavia
eseguito in modo implicito anche l'overload dell'operatore di
assegnazione composta corrispondente, se presente. Ad
esempio, += viene valutato usando + , che può essere
sottoposto a overload.

^ x, x = y, x. y, x?.y , c? t: f, x?? y, x?? = y, x.. y, x->y, => , f Questi operatori non possono essere sottoposti a overload.
(x), As, await, checked, unchecked, default, delegate, is,
NameOf, New, sizeof, stackalloc, Switch, typeof, with

NOTE
Gli operatori di confronto devono essere sottoposti a overload in coppie. Se uno dei due operatori di una coppia viene
sottoposto a overload, anche l'altro operatore della coppia deve essere sottoposto a overload. Le associazioni sono le
seguenti:
Operatori == e !=
Operatori < e >
Operatori <= e >=

Specifiche del linguaggio C#


Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:
Overload degli operatori
Operatori
Vedere anche
Informazioni di riferimento su C#
Operatori ed espressioni C#
Operatori di conversione definiti dall'utente
Linee guida di progettazione-overload degli operatori
Linee guida di progettazione-operatori di uguaglianza
Why are overloaded operators always static in C#? (Perché gli operatori sottoposti a overload sono sempre
statici in C#?)
Caratteri speciali di C#
02/11/2020 • 2 minutes to read • Edit Online

I caratteri speciali sono caratteri contestuali predefiniti che modificano l'elemento del programma (stringa
letterale, identificatore o nome di attributo) a cui vengono anteposti. C# supporta i caratteri speciali seguenti:
@, il carattere dell'identificatore Verbatim.
$, il carattere della stringa interpolata.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
- interpolazione di stringhe (riferimenti in C
21/04/2020 • 8 minutes to read • Edit Online

Il carattere speciale $ identifica una stringa letterale come stringa interpolata. Una stringa interpolata è un
valore letterale stringa che può contenere espressioni di interpolazione. Quando una stringa interpolata viene
risolta in una stringa di risultato, gli elementi con espressioni di interpolazione vengono sostituiti dalle
rappresentazioni stringa dei risultati dell'espressione. Questa funzionalità è disponibile a partire da C .
L'interpolazione di stringhe offre una sintassi più leggibile e pratica per creare stringhe formattate rispetto alla
funzionalità di formattazione composita delle stringhe. L'esempio seguente usa entrambe le funzionalità per
produrre lo stesso output:

string name = "Mark";


var date = DateTime.Now;

// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.

Struttura di una stringa interpolata


Per identificare un valore letterale stringa come stringa interpolata, anteporre a questa il simbolo $ . Tra $ e il
simbolo " all'inizio del valore letterale stringa non possono essere presenti spazi vuoti,
La struttura di un elemento con un'espressione di interpolazione è la seguente:

{<interpolationExpression>[,<alignment>][:<formatString>]}

Gli elementi tra parentesi quadre sono facoltativi. La tabella seguente descrive i singoli elementi:

EL EM EN TO DESC RIZ IO N E

interpolationExpression Espressione che produce un risultato da formattare. La


rappresentazione di stringa di null is . String.Empty

alignment Espressione costante il cui valore definisce il numero minimo


di caratteri nella rappresentazione di stringa del risultato
dell'espressione. Se è positivo, la rappresentazione stringa è
allineata a destra; se è negativo la rappresentazione stringa è
allineata a sinistra. Per altre informazioni, vedere
Componente di allineamento.

formatString Stringa di formato supportata dal tipo di risultato


dell'espressione. Per altre informazioni, vedere Componente
della stringa di formato.

L'esempio seguente usa i componenti di formattazione facoltativi descritti in precedenza:


Console.WriteLine($"|{"Left",-7}|{"Right",7}|");

const int FieldWidthRightAligned = 20;


Console.WriteLine($"{Math.PI,FieldWidthRightAligned} - default formatting of the pi number");
Console.WriteLine($"{Math.PI,FieldWidthRightAligned:F3} - display only three decimal digits of the pi
number");
// Expected output is:
// |Left | Right|
// 3.14159265358979 - default formatting of the pi number
// 3.142 - display only three decimal digits of the pi number

Caratteri speciali
Per includere una parentesi graffa, "{" o "}", nel testo prodotto da una stringa interpolata, digitare due parentesi
graffe, ovvero "{{" o "}}". Per altre informazioni, vedere Sequenze di escape delle parentesi graffe.
Dato che i due punti (":") hanno un significato speciale in un elemento espressione di interpolazione, se si vuole
usare un operatore condizionale in un'espressione di interpolazione, racchiudere l'espressione tra parentesi.
L'esempio seguente illustra come includere una parentesi graffa in una stringa di risultato e come usare un
operatore condizionale in un'espressione di interpolazione:

string name = "Horace";


int age = 34;
Console.WriteLine($"He asked, \"Is your name {name}?\", but didn't wait for a reply :-{{");
Console.WriteLine($"{name} is {age} year{(age == 1 ? "" : "s")} old.");
// Expected output is:
// He asked, "Is your name Horace?", but didn't wait for a reply :-{
// Horace is 34 years old.

Una stringa verbatim interpolata $ inizia con @ il carattere seguito dal carattere. Per altre informazioni sulle
stringhe verbatim, vedere gli argomenti relativi a string e all'identificatore verbatim.

NOTE
A partire dalla versione 8.0 $ di @ C, è possibile $@"..." @$"..." usare i token e in qualsiasi ordine: entrambi e
sono stringhe letterali interpolate valide. Nelle versioni precedenti di $ C, il @ token deve essere visualizzato prima del
token.

Conversioni implicite e IFormatProvider come specificare


l'implementazione
Da una stringa interpolata vengono effettuate tre conversioni implicite:
1. Conversione di una stringa interpolata in un'istanza String che rappresenta il risultato della risoluzione
della stringa interpolata, dove gli elementi dell'espressione di interpolazione vengono sostituiti con le
rappresentazioni stringa dei risultati, formattate correttamente. Questa conversione CurrentCulture
utilizza l'oggetto to format expression results.
2. Conversione di una stringa interpolata in un'istanza di FormattableString che rappresenta una stringa di
formato composito e i risultati dell'espressione da formattare. Questa opzione consente di creare più
stringhe risultato con contenuto specifico delle impostazioni cultura da una singola istanza di
FormattableString. A tale scopo, chiamare uno dei seguenti metodi:
Un overload ToString() che produce una stringa risultato per CurrentCulture.
Un metodo Invariant che produce una stringa risultato per InvariantCulture.
Un metodo ToString(IFormatProvider) che produce una stringa risultato per impostazioni cultura
specifiche.
È inoltre possibile ToString(IFormatProvider) utilizzare il metodo per fornire IFormatProvider
un'implementazione definita dall'utente dell'interfaccia che supporta la formattazione personalizzata. Per
altre informazioni, vedere la sezione Formattazione personalizzata con ICustomFormatter dell'articolo
Formattazione dei tipi in .NET.
3. Conversione di una stringa interpolata in un'istanza di IFormattable che consente anche di creare più
stringhe risultato con contenuto associato a impostazioni cultura specifiche da una singola istanza di
IFormattable.
L'esempio seguente usa la conversione implicita a FormattableString per creare stringhe di risultato con
impostazioni cultura specifiche:

double speedOfLight = 299792.458;


FormattableString message = $"The speed of light is {speedOfLight:N3} km/s.";

System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("nl-NL");
string messageInCurrentCulture = message.ToString();

var specificCulture = System.Globalization.CultureInfo.GetCultureInfo("en-IN");


string messageInSpecificCulture = message.ToString(specificCulture);

string messageInInvariantCulture = FormattableString.Invariant(message);

Console.WriteLine($"{System.Globalization.CultureInfo.CurrentCulture,-10} {messageInCurrentCulture}");
Console.WriteLine($"{specificCulture,-10} {messageInSpecificCulture}");
Console.WriteLine($"{"Invariant",-10} {messageInInvariantCulture}");
// Expected output is:
// nl-NL The speed of light is 299.792,458 km/s.
// en-IN The speed of light is 2,99,792.458 km/s.
// Invariant The speed of light is 299,792.458 km/s.

Risorse aggiuntive
Se non si ha familiarità con l'interpolazione di stringhe, vedere l'esercitazione interattiva Interpolazione di
stringhe in C#. È anche possibile controllare un'altra interpolazione di stringhe nell'esercitazione di C , che
illustra come usare le stringhe interpolate per produrre stringhe formattate.

Compilazione di stringhe interpolate


Se una stringa interpolata è di tipo string , viene in genere trasformata in una chiamata al metodo
String.Format. Il compilatore può sostituire String.Format con String.Concat se il comportamento analizzato è
equivalente alla concatenazione.
Se una stringa interpolata dispone del tipo IFormattable o FormattableString, il compilatore genera una
chiamata al metodo FormattableStringFactory.Create.

Specifiche del linguaggio C#


Per altre informazioni, vedere la sezione Stringhe interpolate della specifica del linguaggio C#.

Vedere anche
Informazioni di riferimento su C#
Caratteri speciali di C#
Stringhe
Stringhe di formato numerico standard
Formattazione composita
String.Format
@ (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

Il carattere speciale @ funge da identificatore verbatim. Può essere usato nei modi seguenti:
1. Per abilitare le parole chiave di C# da usare come identificatori. Il carattere @ precede un elemento di
codice che il compilatore deve interpretare come identificatore piuttosto che come parola chiave di C#.
Nell'esempio seguente il carattere @ viene usato per definire un identificatore denominato for che
verrà usato in un ciclo for .

string[] @for = { "John", "James", "Joan", "Jamie" };


for (int ctr = 0; ctr < @for.Length; ctr++)
{
Console.WriteLine($"Here is your gift, {@for[ctr]}!");
}
// The example displays the following output:
// Here is your gift, John!
// Here is your gift, James!
// Here is your gift, Joan!
// Here is your gift, Jamie!

2. Per indicare che un valore letterale stringa deve essere interpretato come verbatim. Il carattere @ in
questa istanza definisce un valore letterale stringa verbatim. Le sequenze di escape semplici, ad esempio
"\\" per una barra rovesciata, le sequenze di escape esadecimali, ad esempio "\x0041" per una A
maiuscola, le sequenze di escape Unicode, ad esempio "\u0041" per una A maiuscola, vengono
interpretate letteralmente. Solo una sequenza di escape delle virgolette ( "" ) non viene interpretata
letteralmente; produce una virgoletta doppia. Inoltre, in caso di una stringa interpolata verbatim, le
sequenze di escape con parentesi graffa ( {{ e }} ) non vengono interpretate letteralmente; producono
caratteri singoli di parentesi graffa. Nell'esempio seguente vengono definiti due percorsi di file identici,
uno usando un valore letterale stringa normale e l'altro usando un valore letterale stringa verbatim.
Questo è uno degli usi più comuni dei valori letterali stringa verbatim.

string filename1 = @"c:\documents\files\u0066.txt";


string filename2 = "c:\\documents\\files\\u0066.txt";

Console.WriteLine(filename1);
Console.WriteLine(filename2);
// The example displays the following output:
// c:\documents\files\u0066.txt
// c:\documents\files\u0066.txt

Nell'esempio seguente viene illustrato l'effetto della definizione di un valore letterale stringa normale e di
un valore letterale stringa verbatim che contengono le sequenze di caratteri identiche.

string s1 = "He said, \"This is the last \u0063hance\x0021\"";


string s2 = @"He said, ""This is the last \u0063hance\x0021""";

Console.WriteLine(s1);
Console.WriteLine(s2);
// The example displays the following output:
// He said, "This is the last chance!"
// He said, "This is the last \u0063hance\x0021"
3. Per consentire al compilatore di distinguere tra gli attributi in caso di conflitto di denominazione. Un
attributo è una classe che deriva da Attribute. Il nome del relativo tipo in genere include il suffisso
Attribute , anche se il compilatore non applica questa convenzione. È possibile fare riferimento
all'attributo nel codice usando il nome completo del tipo, ad esempio [InfoAttribute] , o il nome
abbreviato, ad esempio, [Info] . Tuttavia, si verifica un conflitto di denominazione se due nomi di tipo di
attributo abbreviati sono identici e se un nome di tipo include il suffisso Attribute e l'altro no. Ad
esempio, il codice seguente non viene compilato perché il compilatore non è in grado di determinare se
l'attributo Info o InfoAttribute viene applicato alla classe Example . Per altre informazioni, vedere
CS1614.

using System;

[AttributeUsage(AttributeTargets.Class)]
public class Info : Attribute
{
private string information;

public Info(string info)


{
information = info;
}
}

[AttributeUsage(AttributeTargets.Method)]
public class InfoAttribute : Attribute
{
private string information;

public InfoAttribute(string info)


{
information = info;
}
}

[Info("A simple executable.")] // Generates compiler error CS1614. Ambiguous Info and InfoAttribute.
// Prepend '@' to select 'Info'. Specify the full name 'InfoAttribute' to select it.
public class Example
{
[InfoAttribute("The entry point.")]
public static void Main()
{
}
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Caratteri speciali di C#
Attributi riservati: attributi a livello di assieme
15/04/2020 • 4 minutes to read • Edit Online

La maggior parte degli attributi viene applicata a elementi specifici del linguaggio quali classi o metodi. Alcuni
attributi sono invece globali e vengono applicati a un intero assembly o a un intero modulo. Ad esempio,
l'attributo AssemblyVersionAttribute può essere usato per incorporare informazioni sulla versione in un
assembly, nel modo seguente:

[assembly: AssemblyVersion("1.0.0.0")]

Gli attributi globali vengono visualizzati using nel codice sorgente dopo qualsiasi direttiva di primo livello e
prima di qualsiasi dichiarazione di tipo, modulo o spazio dei nomi. Gli attributi globali possono apparire in più
file di origine, ma i file devono essere compilati in un'unica operazione di compilazione. Visual Studio aggiunge
attributi globali al file AssemblyInfo.cs nei progetti .NET Framework. Questi attributi non vengono aggiunti ai
progetti .NET Core.These attributes aren't added to .NET Core projects.
Gli attributi dell'assembly sono valori che forniscono informazioni relative a un assembly. Sono suddivisi nelle
seguenti categorie:
Attributi relativi all'identità dell'assembly
Attributi informativi
Attributi relativi al manifesto dell'assembly

Attributi relativi all'identità dell'assembly


Tre attributi (con un nome sicuro, se disponibile), consentono di determinare l'identità di un assembly: il nome,
la versione e le impostazioni cultura. Questi attributi formano il nome completo dell'assembly e sono necessari
per creare riferimenti all'assembly nel codice. È possibile usare gli attributi per impostare la versione e le
impostazioni cultura di un assembly. Tuttavia, il valore del nome viene impostato dal compilatore, dall'IDE di
Visual Studio nella finestra di dialogo Informazioni assemblyo da Assembly Linker (Al.exe) quando viene creato
l'assembly. Il nome dell'assembly è basato sul manifesto dell'assembly. L'attributo AssemblyFlagsAttribute
specifica se è supportata la coesistenza di più copie dell'assembly.
La tabella seguente visualizza gli attributi relativi all'identità.

AT T RIB UTO SC O P O

AssemblyVersionAttribute Specifica la versione di un assembly.

AssemblyCultureAttribute Specifica le impostazioni cultura supportate dall'assembly.

AssemblyFlagsAttribute Specifica se un assembly supporta l'esecuzione side-by-side


nello stesso computer, nello stesso processo o nello stesso
dominio dell'applicazione.

Attributi informativi
Gli attributi informativi vengono utilizzati per fornire informazioni aggiuntive sulla società o sul prodotto per un
assembly. La tabella seguente mostra gli attributi informativi definiti nello spazio dei nomi System.Reflection.
AT T RIB UTO SC O P O

AssemblyProductAttribute Specifica un nome di prodotto per un manifesto


dell'assembly.

AssemblyTrademarkAttribute Specifica un marchio per un manifesto dell'assembly.

AssemblyInformationalVersionAttribute Specifica una versione informativa per un manifesto


dell'assembly.

AssemblyCompanyAttribute Specifica un nome di società per un manifesto dell'assembly.

AssemblyCopyrightAttribute Definisce un attributo personalizzato che specifica un


copyright per un manifesto dell'assembly.

AssemblyFileVersionAttribute Imposta un numero di versione specifico per la risorsa


versione del file Win32.

CLSCompliantAttribute Indica se l'assembly è conforme a CLS (Common Language


Specification).

Attributi relativi al manifesto dell'assembly


È possibile usare gli attributi relativi al manifesto dell'assembly per includere informazioni nel manifesto
dell'assembly. Gli attributi includono titolo, descrizione, alias predefinito e configurazione. La tabella seguente
visualizza gli attributi del manifesto dell'assembly definiti nello spazio dei nomi System.Reflection.

AT T RIB UTO SC O P O

AssemblyTitleAttribute Specifica un titolo dell'assembly per un manifesto


dell'assembly.

AssemblyDescriptionAttribute Specifica una descrizione dell'assembly per un manifesto


dell'assembly.

AssemblyConfigurationAttribute Specifica una configurazione di assembly (ad esempio


vendita al dettaglio o debug) per un manifesto dell'assembly.

AssemblyDefaultAliasAttribute Definisce un alias predefinito descrittivo per un manifesto


dell'assembly.
Attributi riservati: ConditionalAttribute,
ObsoleteAttribute, AttributeUsageAttribute
28/01/2021 • 9 minutes to read • Edit Online

Questi attributi possono essere applicati agli elementi nel codice. Aggiungono un significato semantico a tali
elementi. Il compilatore usa questi significati semantici per modificare l'output e segnalare gli errori possibili da
parte degli sviluppatori che usano il codice.

Attributo Conditional
L'attributo Conditional rende l'esecuzione di un metodo dipendente da un identificatore di pre-elaborazione.
L'attributo Conditional è un alias per ConditionalAttribute e può essere applicato a un metodo o a una classe
Attribute.
Nell'esempio seguente, Conditional viene applicato a un metodo per abilitare o disabilitare la visualizzazione di
informazioni di diagnostica specifiche del programma:

#define TRACE_ON
using System;
using System.Diagnostics;

namespace AttributeExamples
{
public class Trace
{
[Conditional("TRACE_ON")]
public static void Msg(string msg)
{
Console.WriteLine(msg);
}
}

public class TraceExample


{
public static void Main()
{
Trace.Msg("Now in Main...");
Console.WriteLine("Done.");
}
}
}

Se l' TRACE_ON identificatore non è definito, l'output di traccia non viene visualizzato. Esplora le tue attività nella
finestra interattiva.
L' Conditional attributo viene spesso usato con l' DEBUG identificatore per abilitare le funzionalità di traccia e
registrazione per le compilazioni di debug ma non nelle build di rilascio, come illustrato nell'esempio seguente:

[Conditional("DEBUG")]
static void DebugMethod()
{
}

Quando viene chiamato un metodo contrassegnato come condizionale, la presenza o l'assenza del simbolo di
pre-elaborazione specificato determina se la chiamata viene inclusa o omessa. Se il simbolo è definito la
chiamata viene inclusa, in caso contrario viene omessa. Un metodo condizionale deve essere un metodo in una
dichiarazione di classe o struct e deve avere un void tipo restituito. L'utilizzo Conditional di è più pulito, più
elegante e meno soggetto a errori rispetto all'inclusione di metodi all'interno di #if…#endif blocchi.
Se un metodo ha più Conditional attributi, viene inclusa una chiamata al metodo se in uno o più simboli
condizionali è definito (i simboli sono collegati logicamente tra loro usando l'operatore OR). Nell'esempio
seguente la presenza di A o B comporta una chiamata al metodo:

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}

Utilizzo Conditional di con classi Attribute


L'attributo Conditional può essere applicato anche a una definizione di classe Attribute. Nell'esempio seguente
l'attributo personalizzato Documentation aggiungerà informazioni ai metadati solo se DEBUG è definito.

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
string text;

public DocumentationAttribute(string text)


{
this.text = text;
}
}

class SampleClass
{
// This attribute will only be included if DEBUG is defined.
[Documentation("This method displays an integer.")]
static void DoWork(int i)
{
System.Console.WriteLine(i.ToString());
}
}

Attributo Obsolete
L' Obsolete attributo contrassegna un elemento di codice come non più consigliato per l'utilizzo. L'uso di
un'entità contrassegnata come obsoleta genera un avviso o un errore. Obsolete è un attributo monouso e può
essere applicato a qualsiasi entità che supporta gli attributi. Obsolete è un alias per ObsoleteAttribute.
Nell'esempio seguente l' Obsolete attributo viene applicato alla classe A e al metodo B.OldMethod . Poiché il
secondo argomento del costruttore dell'attributo applicato a B.OldMethod è impostato su true questo metodo
genererà un errore del compilatore, mentre l'uso della classe A produrrà semplicemente un avviso. Tuttavia la
chiamata di B.NewMethod non produrrà né un avviso né un errore. Ad esempio, se viene usato con le definizioni
precedenti, il codice che segue genera due avvisi e un errore:
using System;

namespace AttributeExamples
{
[Obsolete("use class B")]
public class A
{
public void Method() { }
}

public class B
{
[Obsolete("use NewMethod", true)]
public void OldMethod() { }

public void NewMethod() { }


}

public static class ObsoleteProgram


{
public static void Main()
{
// Generates 2 warnings:
A a = new A();

// Generate no errors or warnings:


B b = new B();
b.NewMethod();

// Generates an error, compilation fails.


// b.OldMethod();
}
}
}

La stringa fornita come primo argomento del costruttore dell'attributo verrà visualizzata come parte dell'avviso
o dell'errore. Vengono generati due avvisi per la classe A : uno per la dichiarazione del riferimento alla classe e
uno per il costruttore della classe. L' Obsolete attributo può essere usato senza argomenti, ma è consigliabile
includere una spiegazione da usare.

Attributo AttributeUsage
L' AttributeUsage attributo determina il modo in cui è possibile utilizzare una classe di attributi personalizzata.
AttributeUsageAttribute è un attributo che si applica alle definizioni di attributi personalizzati. L'attributo
AttributeUsage consente di controllare:

Elementi del programma a cui l'attributo può essere applicato. Se non se ne limita l'utilizzo, un attributo può
essere applicato a uno qualsiasi degli elementi del programma seguenti:
assembly
module
campo
event
method
param
proprietà
return
tipo
Se un attributo può essere applicato più volte a un singolo elemento del programma.
Se gli attributi vengono ereditati dalle classi derivate.
Le impostazioni predefinite sono simili all'esempio seguente quando vengono applicate in modo esplicito:

[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }

In questo esempio la classe NewAttribute può essere applicata a qualsiasi elemento del programma supportato,
ma solo una volta a ogni entità. L'attributo viene ereditato dalle classi derivate se applicato a una classe base.
Gli argomenti AllowMultiple e Inherited sono facoltativi, quindi il codice seguente ha lo stesso effetto:

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

Il primo argomento AttributeUsageAttribute deve consistere di uno o più elementi dell'enumerazione


AttributeTargets. Più tipi di destinazione possono essere collegati con l'operatore OR, nel modo illustrato
nell'esempio seguente:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

A partire da C# 7.3, gli attributi possono essere applicati alla proprietà o al campo sottostante per una proprietà
implementata automaticamente. L'attributo si applica alla proprietà, a meno che non si specifichi l'identificatore
field per l'attributo. Entrambe le situazioni sono illustrate nell'esempio seguente:

class MyClass
{
// Attribute attached to property:
[NewPropertyOrField]
public string Name { get; set; }

// Attribute attached to backing field:


[field:NewPropertyOrField]
public string Description { get; set; }
}

Se l'argomento AllowMultiple è true , l'attributo restituito può essere applicato più volte a una singola entità,
come illustrato nell'esempio seguente:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]


class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

In questo caso, MultiUseAttribute può essere applicato più volte perché AllowMultiple è impostato su true .
Entrambi i formati illustrati per applicare più attributi sono validi.
Se Inherited è false , l'attributo non viene ereditato dalle classi derivate da una classe con attributi. Ad esempio:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

In questo caso NonInheritedAttribute non viene applicato a DClass attraverso l'ereditarietà.

Vedere anche
Attribute
System.Reflection
Attributes (Attributi)
Reflection
Attributi riservati: determinare le informazioni sul
chiamante
15/04/2020 • 5 minutes to read • Edit Online

Utilizzando gli attributi info, si ottengono informazioni sul chiamante di un metodo. Ottenere il percorso del file
del codice sorgente, il numero di riga nel codice sorgente e il nome del membro del chiamante. È possibile
ottenere informazioni sul chiamante usando gli attributi applicati ai parametri facoltativi. Ogni parametro
facoltativo specifica un valore predefinito. Nella tabella seguente sono elencati gli attributi di informazioni sul
chiamante definiti nello spazio dei nomi System.Runtime.CompilerServices:

AT T RIB UTO DESC RIZ IO N E TYPE

CallerFilePathAttribute Percorso completo del file di origine String


contenente il chiamante. Il percorso
completo è il percorso in fase di
compilazione.

CallerLineNumberAttribute Numero di riga nel file di origine da cui Integer


viene chiamato il metodo.

CallerMemberNameAttribute Nome di una proprietà o di un metodo String


del chiamante.

Queste informazioni consentono di scrivere l'analisi, il debug e la creazione di strumenti di diagnostica.


Nell'esempio seguente viene illustrato come utilizzare gli attributi di informazioni sul chiamante. Per ogni
chiamata al metodo TraceMessage , le informazioni sul chiamante vengono sostituite come argomenti dei
parametri facoltativi.

public void DoProcessing()


{
TraceMessage("Something happened.");
}

public void TraceMessage(string message,


[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}

// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31

Specificare un valore predefinito esplicito per ogni parametro facoltativo. Non è possibile applicare gli attributi
di informazioni del chiamante a parametri non specificati come facoltativi. Gli attributi info del chiamante non
rendono un parametro facoltativo. ma influiscono sul valore predefinito passato quando l'argomento è omesso.
I valori delle informazioni del chiamante vengono generati come valori letterali nel linguaggio intermedio (IL) in
fase di compilazione. A differenza dei risultati della proprietà StackTrace per le eccezioni, i risultati non sono
interessati da offuscamento. È possibile fornire esplicitamente gli argomenti facoltativi per esaminare o
nascondere le informazioni sul chiamante.
Nomi dei membri
È possibile utilizzare l'attributo CallerMemberName per specificare il nome del membro come argomento String
al metodo chiamato. Usando questa tecnica, si evita il problema per cui il refactoring di ridenominazione
non modifica i valori String . Questo vantaggio è particolarmente utile per le attività seguenti:
Utilizzo della tracciatura e delle routine di diagnostica.
Implementazione dell'interfaccia INotifyPropertyChanged durante l'associazione dei dati. Questa interfaccia
consente alla proprietà di un oggetto di notificare a un controllo associato la modifica della proprietà stessa
in modo che il controllo possa visualizzare le informazioni aggiornate. Senza l'attributo CallerMemberName , è
necessario specificare il nome della proprietà come valore letterale.
Nel grafico seguente vengono mostrati i nomi dei membri restituiti quando si utilizza l'attributo
CallerMemberName .

L E C H IA M AT E AVVEN GO N O EN T RO N O M E DEL M EM B RO REST IT UITO

Metodo, proprietà o evento Nome del metodo, della proprietà o dell'evento da cui la
chiamata ha avuto origine.

Costruttore Stringa ".ctor"

Costruttore statico Stringa ".cctor"

Distruttore Stringa "Finalize"

Operatori o conversioni definiti dall'utente Nome generato per il membro, ad esempio "op_Addition".

Costruttore dell'attributo Nome del metodo o della proprietà cui viene applicato
l'attributo. Se l'attributo è un qualsiasi elemento in un
membro (ad esempio un parametro, un valore restituito o
un parametro di tipo generico), il risultato è il nome del
membro associato a tale elemento.

Nessun membro contenitore (ad esempio a livello di Valore predefinito del parametro facoltativo.
assembly o attributi applicati a tipi)

Vedere anche
Argomenti denominati e facoltativi
System.Reflection
Attribute
Attributi
Gli attributi riservati contribuiscono all'analisi statica
dello stato null del compilatore
02/11/2020 • 27 minutes to read • Edit Online

In un contesto Nullable, il compilatore esegue l'analisi statica del codice per determinare lo stato null di tutte le
variabili di tipo riferimento:
not null: l'analisi statica determina che a una variabile viene assegnato un valore non null.
forse null: l'analisi statica non è in grado di determinare che a una variabile è assegnato un valore non null.
È possibile applicare diversi attributi che forniscono informazioni al compilatore sulla semantica delle API. Tali
informazioni consentono al compilatore di eseguire l'analisi statica e determinare quando una variabile non è
null. In questo articolo viene fornita una breve descrizione di ognuno di questi attributi e viene illustrato come
usarli. Tutti gli esempi presuppongono C# 8,0 o versione successiva e il codice si trova in un contesto Nullable.
Iniziamo con un esempio familiare. Si supponga che la libreria disponga dell'API seguente per recuperare una
stringa di risorsa:

bool TryGetMessage(string key, out string message)

L'esempio precedente segue il Try* modello familiare in .NET. Per questa API sono disponibili due argomenti di
riferimento: key e message . Questa API presenta le regole seguenti relative al valore null di questi argomenti:
I chiamanti non devono passare null come argomento per key .
I chiamanti possono passare una variabile il cui valore è null come argomento per message .
Se il TryGetMessage metodo restituisce true , il valore di message non è null. Se il valore restituito è false,
il valore di message (e il relativo stato null) è null.
La regola per key può essere espressa dal tipo di variabile: key deve essere un tipo di riferimento non
nullable. Il message parametro è più complesso. Consente null come argomento, ma garantisce che, in seguito
all'esito positivo, l' out argomento non sia null. Per questi scenari, è necessario un vocabolario più completo
per descrivere le aspettative.
Sono stati aggiunti diversi attributi per esprimere informazioni aggiuntive sullo stato null delle variabili. Tutto il
codice scritto prima di C# 8 ha introdotto tipi di riferimento Nullable è un valore null ignaro. Ciò significa che
qualsiasi variabile di tipo riferimento può essere null, ma non sono necessari controlli null. Quando il codice
ammette i valori null, tali regole cambiano. I tipi di riferimento non devono mai essere il null valore e i tipi di
riferimento nullable devono essere controllati null prima di essere dereferenziati.
Le regole per le API sono probabilmente più complesse, come si è visto con lo TryGetValue scenario API. Molte
API hanno regole più complesse quando le variabili possono o non possono essere null . In questi casi, si
userà uno degli attributi seguenti per esprimere queste regole:
AllowNull: un argomento di input che non ammette i valori null può essere null.
DisallowNull: un argomento di input nullable non deve mai essere null.
MaybeNull: un valore restituito non nullable può essere null.
NotNull: un valore restituito nullable non sarà mai null.
MaybeNullWhen: un argomento di input che non ammette i valori null può essere null quando il metodo
restituisce il bool valore specificato.
NotNullWhen: un argomento di input nullable non sarà null quando il metodo restituisce il bool valore
specificato.
NotNullIfNotNull: un valore restituito non è null se l'argomento per il parametro specificato non è null.
DoesNotReturn: un metodo non restituisce mai. In altre parole, genera sempre un'eccezione.
DoesNotReturnIf: questo metodo non restituisce mai se il bool valore specificato per il parametro associato
è.
Le descrizioni precedenti sono un riferimento rapido a ogni attributo. Ogni sezione seguente descrive il
comportamento e il significato più approfondito.
L'aggiunta di questi attributi offre al compilatore ulteriori informazioni sulle regole per l'API. Quando il codice
chiamante viene compilato in un contesto abilitato Nullable, il compilatore avvisa i chiamanti quando violano
tali regole. Questi attributi non abilitano controlli aggiuntivi nell'implementazione.

Specificare le precondizioni: AllowNull e DisallowNull


Si consideri una proprietà di lettura/scrittura che non restituisce mai null perché ha un valore predefinito
ragionevole. I chiamanti passano null alla funzione di accesso set quando vengono impostati su tale valore
predefinito. Si consideri, ad esempio, un sistema di messaggistica che richiede un nome di schermata in una
chat room. Se non viene specificato alcun valore, il sistema genera un nome casuale:

public string ScreenName


{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;

Quando si compila il codice precedente in un contesto ignaro Nullable, tutto funziona correttamente. Quando si
abilitano i tipi di riferimento Nullable, la ScreenName proprietà diventa un riferimento che non ammette i valori
null. Questo è corretto per la get funzione di accesso: non restituisce mai null . I chiamanti non devono
controllare la proprietà restituita per null . Ma ora impostando la proprietà su viene null generato un avviso.
Per continuare a supportare questo tipo di codice, aggiungere l'
System.Diagnostics.CodeAnalysis.AllowNullAttribute attributo alla proprietà, come illustrato nel codice seguente:

[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();

Potrebbe essere necessario aggiungere una using direttiva per System.Diagnostics.CodeAnalysis per usare
questo e altri attributi descritti in questo articolo. L'attributo viene applicato alla proprietà, non alla set
funzione di accesso. L' AllowNull attributo specifica le condizioni preliminarie si applica solo agli input. La get
funzione di accesso ha un valore restituito, ma nessun argomento di input. Pertanto, l' AllowNull attributo si
applica solo alla set funzione di accesso.
Nell'esempio precedente viene illustrato cosa cercare quando si aggiunge l' AllowNull attributo in un
argomento:
1. Il contratto generale per tale variabile è che non deve essere null , pertanto si desidera un tipo di
riferimento non nullable.
2. Esistono scenari per la variabile di input null , sebbene non siano l'utilizzo più comune.

Spesso è necessario questo attributo per le proprietà, gli in argomenti, out e ref . L' AllowNull attributo è
la scelta migliore quando una variabile è in genere non null, ma è necessario consentirla null come
precondizione.
Si confronti con gli scenari per l'utilizzo di DisallowNull : si utilizza questo attributo per specificare che una
variabile di input di un tipo di riferimento nullable non deve essere null . Si consideri una proprietà null in
cui è il valore predefinito, ma i client possono impostarlo solo su un valore non null. Osservare il codice
seguente:

public string ReviewComment


{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;

Il codice precedente rappresenta il modo migliore per esprimere la progettazione che ReviewComment può essere
null , ma non può essere impostato su null . Una volta che il codice ammette i valori null, è possibile
esprimere questo concetto in modo più chiaro ai chiamanti utilizzando
System.Diagnostics.CodeAnalysis.DisallowNullAttribute :

[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;

In un contesto Nullable la ReviewComment get funzione di accesso può restituire il valore predefinito null . Il
compilatore avverte che è necessario verificarlo prima dell'accesso. Inoltre, avvisa i chiamanti che, anche se
potrebbero essere, i null chiamanti non devono impostarlo in modo esplicito su null . L' DisallowNull
attributo specifica anche una condizione preliminare, non influisce sulla get funzione di accesso. Usare l'
DisallowNull attributo quando si osservano queste caratteristiche:

1. La variabile potrebbe trovarsi null negli scenari principali, spesso quando viene creata per la prima volta.
2. La variabile non deve essere impostata in modo esplicito su null .

Queste situazioni sono comuni nel codice che originariamente era null ignaro. È possibile che le proprietà
dell'oggetto siano impostate in due operazioni di inizializzazione distinte. È possibile che alcune proprietà siano
impostate solo dopo il completamento di alcune operazioni asincrone.
Gli AllowNull DisallowNull attributi e consentono di specificare che le precondizioni sulle variabili potrebbero
non corrispondere alle annotazioni Nullable di tali variabili. Sono disponibili informazioni più dettagliate sulle
caratteristiche dell'API. Queste informazioni aggiuntive consentono ai chiamanti di usare correttamente l'API.
Ricordare di specificare le precondizioni usando gli attributi seguenti:
AllowNull: un argomento di input che non ammette i valori null può essere null.
DisallowNull: un argomento di input nullable non deve mai essere null.

Specificare le post-conditions: MaybeNull e NotNull


Si supponga di avere un metodo con la firma seguente:
public Customer FindCustomer(string lastName, string firstName)

Probabilmente è stato scritto un metodo come questo per restituire null quando il nome cercato non è stato
trovato. null Indica chiaramente che il record non è stato trovato. In questo esempio è probabile che si
modifichi il tipo restituito da Customer a Customer? . La dichiarazione del valore restituito come tipo di
riferimento Nullable specifica lo scopo di questa API in modo chiaro.
Per motivi trattati in definizioni generiche e supporto di valori null, la tecnica non funziona con i metodi generici.
È possibile che si disponga di un metodo generico che segue un modello simile:

public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)

Non è possibile specificare che il valore restituito è T? . Il metodo restituisce null quando l'elemento cercato
non viene trovato. Poiché non è possibile dichiarare un T? tipo restituito, è necessario aggiungere l' MaybeNull
annotazione al metodo restituito:

[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)

Il codice precedente informa i chiamanti che il contratto implica un tipo non nullable, ma il valore restituito può
essere effettivamente null. Usare l' MaybeNull attributo quando l'API deve essere un tipo non nullable, in genere
un parametro di tipo generico, ma possono essere presenti istanze in cui null verrebbe restituito.
È anche possibile specificare che un valore restituito o un out ref argomento o non sia null anche se il tipo è
un tipo di riferimento Nullable. Si consideri un metodo che garantisce che una matrice sia sufficientemente
grande da mantenere un numero di elementi. Se l'argomento di input non dispone di capacità, la routine alloca
una nuova matrice e copia tutti gli elementi esistenti al suo interno. Se l'argomento di input è null , la routine
allocherà la nuova risorsa di archiviazione. Se la capacità è sufficiente, la routine non esegue alcuna operazione:

public void EnsureCapacity<T>(ref T[] storage, int size)

È possibile chiamare questa routine come segue:

// messages has the default value (null) when EnsureCapacity is called:


EnsureCapacity<string>(ref messages, 10);
// messages is not null.
EnsureCapacity<string>(messages, 50);

Dopo aver abilitato i tipi di riferimento null, è necessario assicurarsi che il codice precedente venga compilato
senza avvisi. Quando il metodo restituisce un risultato, l' storage argomento è sicuramente not null. Tuttavia, è
accettabile chiamare EnsureCapacity con un riferimento null. È possibile creare storage un tipo di riferimento
nullable e aggiungere la NotNull post-condizione alla dichiarazione del parametro:

public void EnsureCapacity<T>([NotNull] ref T[]? storage, int size)

Il codice precedente esprime chiaramente il contratto esistente: i chiamanti possono passare una variabile con il
null valore, ma il valore restituito non è mai null. L' NotNull attributo è particolarmente utile per ref gli out
argomenti e null , dove può essere passato come argomento, ma tale argomento è sicuramente not null
quando il metodo restituisce.
È possibile specificare postcondizioni non condizionali usando gli attributi seguenti:
MaybeNull: un valore restituito non nullable può essere null.
NotNull: un valore restituito nullable non sarà mai null.

Specificare le condizioni postali condizionali: NotNullWhen ,


MaybeNullWhen e NotNullIfNotNull
È probabile che si abbia familiarità con il string metodo String.IsNullOrEmpty(String) . Questo metodo
restituisce true quando l'argomento è null o una stringa vuota. Si tratta di un tipo di controllo null: i chiamanti
non devono verificare se il metodo restituisce l'argomento false . Per rendere un metodo simile a Nullable,
impostare l'argomento su un tipo di riferimento nullable e aggiungere l' NotNullWhen attributo:

bool IsNullOrEmpty([NotNullWhen(false)] string? value);

Che informa il compilatore che il codice in cui il valore restituito non deve false essere verificato come null.
L'aggiunta dell'attributo informa l'analisi statica del compilatore che IsNullOrEmpty esegue il controllo null
necessario: quando restituisce false , l'argomento di input non lo è null .

string? userInput = GetUserInput();


if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.

Il String.IsNullOrEmpty(String) metodo verrà annotato come illustrato in precedenza per .NET Core 3,0. Nella
codebase potrebbero essere presenti metodi simili che controllano lo stato degli oggetti per i valori null. Il
compilatore non riconosce i metodi di controllo null personalizzati ed è necessario aggiungervi le annotazioni.
Quando si aggiunge l'attributo, l'analisi statica del compilatore sa quando la variabile testata è stata verificata
come null.
Un altro uso di questi attributi è il Try* modello. Le postcondizioni per ref le out variabili e vengono
comunicate tramite il valore restituito. Si consideri questo metodo illustrato in precedenza:

bool TryGetMessage(string key, out string message)

Il metodo precedente segue un normale idioma .NET: il valore restituito indica se message è stato impostato sul
valore trovato o, se non viene trovato alcun messaggio, al valore predefinito. Se il metodo restituisce true , il
valore di message non è null; in caso contrario, il metodo imposta message su null.
È possibile comunicare tale idioma usando l' NotNullWhen attributo. Quando si aggiorna la firma per i tipi di
riferimento Nullable, creare message un oggetto string? e aggiungere un attributo:

bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)

Nell'esempio precedente, il valore di message è noto come not null quando TryGetMessage restituisce true .È
necessario annotare metodi simili nella codebase nello stesso modo: gli argomenti potrebbero essere null ,e
sono noti come not null quando il metodo restituisce true .
È possibile che sia necessario anche un attributo finale. A volte lo stato null di un valore restituito dipende dallo
stato null di uno o più argomenti di input. Questi metodi restituiranno un valore non null ogni volta che
determinati argomenti di input non sono null . Per annotare correttamente questi metodi, usare l'
NotNullIfNotNull attributo. Si consideri il seguente metodo:

string GetTopLevelDomainFromFullUrl(string url);

Se l' url argomento non è null, l'output non è null . Una volta abilitati i riferimenti Nullable, la firma funziona
correttamente, purché l'API non accetti mai un input null. Tuttavia, se l'input può essere null, il valore restituito
può anche essere null. Pertanto, è possibile modificare la firma con il codice seguente:

string? GetTopLevelDomainFromFullUrl(string? url);

Che funziona, ma spesso impone ai chiamanti di implementare controlli aggiuntivi null . Il contratto è che il
valore restituito null è solo quando l'argomento di input url è null . Per esprimere il contratto, è necessario
annotare questo metodo come illustrato nel codice seguente:

[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url);

Il valore restituito e l'argomento sono entrambi annotati con l'oggetto ? che indica che può essere null .
L'attributo chiarisce ulteriormente che il valore restituito non sarà null quando l' url argomento non è null .
È possibile specificare le postcondizioni condizionali usando questi attributi:
MaybeNullWhen: un argomento di input che non ammette i valori null può essere null quando il metodo
restituisce il bool valore specificato.
NotNullWhen: un argomento di input nullable non sarà null quando il metodo restituisce il bool valore
specificato.
NotNullIfNotNull: un valore restituito non è null se l'argomento di input per il parametro specificato non è
null.

Verificare il codice non eseguibile


Alcuni metodi, in genere helper di eccezioni o altri metodi di utilità, vengono sempre terminati generando
un'eccezione. In alternativa, un helper può generare un'eccezione in base al valore di un argomento booleano.
Nel primo caso, è possibile aggiungere l' DoesNotReturn attributo alla dichiarazione del metodo. Il compilatore
consente di in tre modi. Prima di tutto, il compilatore genera un avviso se è presente un percorso in cui il
metodo può uscire senza generare un'eccezione. In secondo luogo, il compilatore contrassegna il codice dopo
una chiamata a tale metodo come non raggiungibile, fino a quando non catch viene rilevata una clausola
appropriata. In terzo luogo, il codice non eseguibile non influirà sugli stati null. Si consideri il metodo seguente:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}

public void SetState(object containedField)


{
if (!isInitialized)
{
FailFast();
}

// unreachable code:
_field = containedField;
}

Nel secondo caso, si aggiunge l' DoesNotReturnIf attributo a un parametro booleano del metodo. È possibile
modificare l'esempio precedente nel modo seguente:

private void FailFast([DoesNotReturnIf(false)] bool isValid)


{
if (!isValid)
{
throw new InvalidOperationException();
}
}

public void SetState(object containedField)


{
FailFast(isInitialized);

// unreachable code when "isInitialized" is false:


_field = containedField;
}

Summary
IMPORTANT
La documentazione ufficiale tiene traccia della versione più recente di C#. È in corso la scrittura per C# 9,0. A seconda della
versione di C# utilizzata, potrebbero non essere disponibili diverse funzionalità. La versione C# predefinita per il progetto è
basata sul Framework di destinazione. Per altre informazioni, vedere impostazioni predefinite per il controllo delle versioni
del linguaggio C#.

L'aggiunta di tipi di riferimento Nullable fornisce un vocabolario iniziale per descrivere le aspettative delle API
per le variabili che potrebbero essere null . Gli attributi aggiuntivi forniscono un vocabolario più completo per
descrivere lo stato null delle variabili come precondizioni e postcondizioni. Questi attributi descrivono in modo
più chiaro le aspettative e offrono un'esperienza migliore per gli sviluppatori che usano le API.
Quando si aggiornano le librerie per un contesto Nullable, aggiungere questi attributi per guidare gli utenti delle
API all'uso corretto. Questi attributi consentono di descrivere completamente lo stato null degli argomenti di
input e dei valori restituiti:
AllowNull: un argomento di input che non ammette i valori null può essere null.
DisallowNull: un argomento di input nullable non deve mai essere null.
MaybeNull: un valore restituito non nullable può essere null.
NotNull: un valore restituito nullable non sarà mai null.
MaybeNullWhen: un argomento di input che non ammette i valori null può essere null quando il metodo
restituisce il bool valore specificato.
NotNullWhen: un argomento di input nullable non sarà null quando il metodo restituisce il bool valore
specificato.
NotNullIfNotNull: un valore restituito non è null se l'argomento di input per il parametro specificato non è
null.
DoesNotReturn: un metodo non restituisce mai. In altre parole, genera sempre un'eccezione.
DoesNotReturnIf: questo metodo non restituisce mai se il bool valore specificato per il parametro associato
è.
Direttive per il preprocessore C#
28/01/2021 • 2 minutes to read • Edit Online

Questa sezione contiene informazioni sulle seguenti direttive per il preprocessore C#:
#if
#else
#elif
#endif
#define
#undef
#warning
#error
#line
#nullable
#region
#endregion
#pragma
avviso #pragma
checksum #pragma
Per altre informazioni ed esempi, vedere i singoli argomenti.
Anche se il compilatore non ha un preprocessore indipendente, le direttive descritte in questa sezione vengono
elaborate come se ne esistesse uno. Vengono usate come supporto nella compilazione condizionale. A
differenza delle direttive di C e C++, non è possibile usare queste direttive per creare macro.
Una direttiva del preprocessore deve essere l'unica istruzione su una riga.

Vedi anche
Riferimenti per C#
Guida per programmatori C#
#if (riferimenti per C#)
02/11/2020 • 5 minutes to read • Edit Online

Quando il compilatore C# trova una direttiva #if seguita da una direttiva #endif, compila il codice tra tali
direttive solo se il simbolo specificato è definito. Diversamente da C e C++, non è possibile assegnare un valore
numerico a un simbolo. L' #if istruzione in C# è booleana e verifica solo se il simbolo è stato definito o meno.
Esempio:

#if DEBUG
Console.WriteLine("Debug version");
#endif

È possibile utilizzare gli operatori == (uguaglianza) e ! = (disuguaglianza) solo per verificare i valori bool true
o false . true indica che il simbolo è definito. L'istruzione #if DEBUG ha lo stesso significato di
#if (DEBUG == true) . È possibile utilizzare il && (e), || (or)e ! (non) operatori per valutare se sono stati definiti
più simboli. È anche possibile raggruppare simboli e operatori tra parentesi.

Osservazioni
#if , insieme alle direttive #else, #elif, #endif, #definee #undef , consente di includere o escludere il codice in
base all'esistenza di uno o più simboli. Questo può essere utile quando si compila il codice per una build di
debug o per una configurazione specifica.
Una direttiva condizionale che inizia con #if deve terminare in modo esplicito con una direttiva #endif .
#define consente di definire un simbolo. Usando poi il simbolo come espressione passata alla direttiva #if ,
l'espressione restituisce true .
È anche possibile definire un simbolo con l'opzione -define del compilatore. Per rimuovere la definizione di un
simbolo, è possibile usare #undef.
Un simbolo definito tramite -define o #define non provoca conflitti con una variabile avente lo stesso nome.
Il nome di una variabile, infatti, non può essere passato a una direttiva del preprocessore e un simbolo può
essere valutato solo da una direttiva del preprocessore.
L'ambito di un simbolo creato con #define è il file in cui è stato definito.
Il sistema di compilazione è inoltre in grado di riconoscere i simboli predefiniti del preprocessore che
rappresentano i diversi Framework di destinazione nei progetti in stile SDK. Sono utili quando si creano
applicazioni che possono essere destinate a più di una versione di .NET.

F RA M EW O RK DI DEST IN A Z IO N E SIM B O L I

.NET Framework NETFRAMEWORK , NET48 , NET472 , NET471 , NET47 ,


NET462 , NET461 , NET46 , NET452 , NET451 , NET45 ,
NET40 , NET35 , NET20

.NET Standard NETSTANDARD , NETSTANDARD2_1 , NETSTANDARD2_0 ,


NETSTANDARD1_6 , NETSTANDARD1_5 , NETSTANDARD1_4 ,
NETSTANDARD1_3 , NETSTANDARD1_2 , NETSTANDARD1_1 ,
NETSTANDARD1_0
F RA M EW O RK DI DEST IN A Z IO N E SIM B O L I

.NET 5 (e .NET Core) NET5_0 , NETCOREAPP , NETCOREAPP3_1 , NETCOREAPP3_0 ,


NETCOREAPP2_2 , NETCOREAPP2_1 , NETCOREAPP2_0 ,
NETCOREAPP1_1 , NETCOREAPP1_0

NOTE
Per i progetti di tipo non SDK tradizionali, è necessario configurare manualmente i simboli di compilazione condizionale
per i diversi framework di destinazione in Visual Studio tramite le pagine delle proprietà del progetto.

Altri simboli predefiniti includono le costanti DEBUG e TRACE. È possibile sostituire i valori impostati per il
progetto con #define . Il simbolo DEBUG, ad esempio, viene impostato automaticamente a seconda delle
proprietà di configurazione della build (modalità "Debug" o "Versione").

Esempi
L'esempio seguente illustra come definire un simbolo MYTEST su un file e quindi testare i valori dei simboli
MYTEST e DEBUG. L'output di questo esempio dipende dal fatto che il progetto sia stato compilato con la
modalità di configurazione Debug o Versione.

#define MYTEST
using System;
public class MyClass
{
static void Main()
{
#if (DEBUG && !MYTEST)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
Console.WriteLine("DEBUG and MYTEST are defined");
#else
Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
}
}

L'esempio seguente illustra come eseguire test per framework di destinazione diversi, in modo da poter usare le
API più recenti quando possibile:

public class MyClass


{
static void Main()
{
#if NET40
WebClient _client = new WebClient();
#else
HttpClient _client = new HttpClient();
#endif
}
//...
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
Procedura: Compilare in modo condizionale con traccia e debug
#else (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#else consente di creare una direttiva condizionale composita. In questo modo, se nessuna delle espressioni
delle direttive #if o #elif (facoltativa) precedenti ha restituito true , il compilatore valuterà tutto il codice tra
#else e l'espressione #endif successiva.

Osservazioni
La direttiva per il preprocessore #else deve essere seguita da #endif. Per un esempio di utilizzo di #else ,
vedere #if.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#elif (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#elif consente di creare una direttiva condizionale composita. L'espressione #elif viene valutata quando né
l'espressione #if che la precede, né eventuali espressioni delle direttive #elif facoltative che la precedono,
restituiscono true . Se un'espressione #elif restituisce true , il compilatore valuterà tutto il codice compreso
tra #elif e la direttiva condizionale successiva. Ad esempio:

#define VC7
//...
#if debug
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif

È possibile usare gli operatori == (uguaglianza), != (disuguaglianza), && (and), e || (or) per valutare più
simboli. È anche possibile raggruppare simboli e operatori tra parentesi.

Osservazioni
#elif equivale a usare:

#else
#if

L'utilizzo di #elif è più semplice perché ogni espressione #if richiede un'espressione #endif, mentre
un'espressione #elif può essere usata anche senza un'espressione #endif corrispondente.
Per un esempio di utilizzo di #elif , vedere #if.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#endif (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#endif specifica la fine di una direttiva condizionale iniziata con la direttiva #if. Ad esempio:

#define DEBUG
// ...
#if DEBUG
Console.WriteLine("Debug version");
#endif

Osservazioni
Una direttiva condizionale che inizia con #if deve terminare in modo esplicito con una direttiva #endif . Per un
esempio di utilizzo di #endif , vedere #if.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#define (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Si usa #define per definire un simbolo. Quando si usa il simbolo come espressione passata alla direttiva #if,
l'espressione restituisce true , come illustrato nell'esempio seguente:

#define DEBUG

Osservazioni
NOTE
Non è possibile usare la direttiva #define per dichiarare valori costanti come in C e in C++. Le costanti in C# possono
essere definite come membri statici di una classe o di uno struct. Se sono presenti più costanti di questo tipo, per usarle
può essere utile creare una classe di costanti separata.

Per specificare le condizioni per la compilazione è possibile usare simboli. È possibile eseguire il test del simbolo
tramite #if o #elif. È anche possibile usare ConditionalAttribute per eseguire una compilazione condizionale.
È possibile definire un simbolo ma non è possibile assegnare a questo un valore. La direttiva #define deve
essere inserita in un file prima di usare istruzioni che non siano anche direttive del preprocessore.
È anche possibile definire un simbolo con l'opzione -define del compilatore. Per rimuovere la definizione di un
simbolo, è possibile usare #undef.
Un simbolo definito tramite -define o #define non provoca conflitti con una variabile avente lo stesso nome.
Il nome di una variabile, infatti, non può essere passato a una direttiva del preprocessore e un simbolo può
essere valutato solo da una direttiva del preprocessore.
L'ambito di un simbolo creato tramite #define è il file in cui il simbolo è stato definito.
Come illustrato nell'esempio seguente, è necessario inserire direttive #define all'inizio del file.
#define DEBUG
//#define TRACE
#undef TRACE

using System;

public class TestDefine


{
static void Main()
{
#if (DEBUG)
Console.WriteLine("Debugging is enabled.");
#endif

#if (TRACE)
Console.WriteLine("Tracing is enabled.");
#endif
}
}
// Output:
// Debugging is enabled.

Per un esempio su come annullare la definizione di un simbolo, vedere #undef.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
const
Procedura: Compilare in modo condizionale con traccia e debug
#undef
#if
#undef (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

#undef consente di rimuovere la definizione di un simbolo. In questo modo, se si usa il simbolo come
espressione in una direttiva #if, l'espressione restituirà false .
Un simbolo può essere definito con la direttiva #define o con l'opzione -define del compilatore. È necessario che
la direttiva #undef venga visualizzata in un file prima di usare istruzioni che non siano anche direttive.

Esempio
// preprocessor_undef.cs
// compile with: /d:DEBUG
#undef DEBUG
using System;
class MyClass
{
static void Main()
{
#if DEBUG
Console.WriteLine("DEBUG is defined");
#else
Console.WriteLine("DEBUG is not defined");
#endif
}
}

DEBUG non è stato definito

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#warning (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#warning consente di generare l'avviso CS1030 di livello uno del compilatore da una posizione specifica del
codice. Ad esempio:

#warning Deprecated code in this method.

Osservazioni
La direttiva #warning viene generalmente usata nelle direttive condizionali. È possibile anche generare un
errore definito dall'utente tramite #error.

Esempio
// preprocessor_warning.cs
// CS1030 expected
#define DEBUG
class MainClass
{
static void Main()
{
#if DEBUG
#warning DEBUG is defined
#endif
}
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#error (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#error consente di generare l'errore definito dall'utente CS1029 da una posizione specifica nel codice. Ad
esempio:

#error Deprecated code in this method.

NOTE
Il compilatore tratta #error version in modo speciale e segnala un errore del compilatore, CS8304, con un messaggio
contenente le versioni del compilatore e del linguaggio utilizzate.

Commenti
La direttiva #error viene generalmente usata nelle direttive condizionali.
È possibile anche generare un avviso definito dall'utente tramite #warning.

Esempio
// preprocessor_error.cs
// CS1029 expected
#define DEBUG
class MainClass
{
static void Main()
{
#if DEBUG
#error DEBUG is defined
#endif
}
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#line (Riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online

#line consente di modificare il numero di riga del compilatore e, facoltativamente, l'output del nome del file
per gli errori e gli avvisi.
Nell'esempio seguente viene illustrata la modalità di segnalazione di due avvisi associati a numeri di riga. La
direttiva #line 200 forza l'impostazione del numero di riga successivo su 200 (anche se l'impostazione
predefinita è 6) e, fino alla successiva direttiva #line , il nome file viene indicato come "Special". La direttiva
#line default reimposta la numerazione predefinita delle righe, con il conteggio delle righe rinumerate dalla
direttiva precedente.

class MainClass
{
static void Main()
{
#line 200 "Special"
int i;
int j;
#line default
char c;
float f;
#line hidden // numbering not affected
string s;
double d;
}
}

La compilazione produce l'output seguente:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used

Commenti
La direttiva #line può essere usata in un'istruzione automatizzata intermedia nel processo di compilazione. Se,
ad esempio, sono state rimosse delle righe dal file del codice sorgente originale e si vuole che il compilatore
generi comunque un output basato sulla numerazione originale delle righe del file, è possibile rimuovere le
righe e simulare la numerazione originale tramite #line .
La direttiva #line hidden nasconde al debugger le righe successive, in modo che quando lo sviluppatore
eseguirà il codice un'istruzione alla volta, verranno eseguite tutte le righe racchiuse tra una direttiva
#line hidden e la successiva direttiva #line (supposto che non si tratti di un'altra direttiva #line hidden ).
Questa opzione può essere usata anche per consentire ad ASP.NET di distinguere il codice definito dall'utente da
quello generato dal computer. Sebbene ASP.NET rappresenti il consumer principale di questa funzionalità, è
probabile che ne usufruiscano anche altri generatori di codice sorgente.
Una direttiva #line hidden non influisce sui nomi di file o sui numeri di riga nella segnalazione degli errori. In
questo modo, se viene rilevato un errore in un blocco nascosto, nel compilatore verrà segnalato il nome del file
corrente e il numero di riga dell'errore.
La direttiva #line filename specifica il nome del file che si vuole venga visualizzato nell'output del compilatore.
Per impostazione predefinita, viene usato il nome effettivo del file del codice sorgente. Il nome del file deve
essere racchiuso tra virgolette doppie (" ") e deve essere preceduto da un numero di riga.
I file del codice sorgente possono contenere più direttive #line .

Esempio 1
Nell'esempio seguente viene illustrato come il debugger ignori le righe nascoste nel codice. Durante
l'esecuzione dell'esempio, verranno visualizzate tre righe di testo. Tuttavia, se si imposta un punto di
interruzione, come illustrato nell'esempio, e si preme F10 per eseguire il codice un'istruzione alla volta, si noterà
che la riga nascosta verrà ignorata. La stessa cosa si verifica anche se si imposta un punto di interruzione in
corrispondenza della riga nascosta.

// preprocessor_linehidden.cs
using System;
class MainClass
{
static void Main()
{
Console.WriteLine("Normal line #1."); // Set break point here.
#line hidden
Console.WriteLine("Hidden line.");
#line default
Console.WriteLine("Normal line #2.");
}
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#nullable (riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

La #nullable direttiva per il preprocessore imposta il contesto di annotazione Nullable e il contesto di avviso
Nullable. Questa direttiva controlla se le annotazioni Nullable hanno effetto e se vengono specificati avvisi di
supporto dei valori null. Ogni contesto è disabilitato o abilitato.
Entrambi i contesti possono essere specificati a livello di progetto (all'esterno del codice sorgente C#). La
#nullable direttiva controlla i contesti di annotazione e avviso e ha la precedenza sulle impostazioni a livello di
progetto. Una direttiva imposta i contesti che controlla finché un'altra direttiva ne esegue l'override o fino alla
fine del file di origine.
L'effetto delle direttive è il seguente:
#nullable disable : Imposta l'annotazione nullable e i contesti di avviso su disabled.
#nullable enable : Imposta l'annotazione nullable e i contesti di avviso su Enabled.
#nullable restore : Ripristina i contesti di avviso e di annotazione Nullable nelle impostazioni del progetto.
#nullable disable annotations : Imposta il contesto di annotazione Nullable su disabled.
#nullable enable annotations : Imposta il contesto di annotazione Nullable su Enabled.
#nullable restore annotations : Ripristina il contesto di annotazione Nullable nelle impostazioni del progetto.
#nullable disable warnings : Imposta il contesto di avviso Nullable su disabled.
#nullable enable warnings : Imposta il contesto di avviso Nullable su Enabled.
#nullable restore warnings : Ripristina il contesto di avviso Nullable nelle impostazioni del progetto.

Vedi anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#region (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#region consente di specificare un blocco di codice che è possibile espandere o comprimere quando si usa la
funzionalità di struttura dell'editor di codice. Nei file di codice più lunghi, è consigliabile essere in grado di
comprimere o nascondere una o più aree in modo da potersi concentrare sulla parte del file su cui si sta
lavorando. L'esempio seguente illustra come definire un'area:

#region MyClass definition


public class MyClass
{
static void Main()
{
}
}
#endregion

Osservazioni
Un blocco #region deve terminare con la direttiva #endregion.
Non è possibile sovrapporre un blocco #region con un blocco #if. È tuttavia possibile annidare un blocco
#region in un blocco #if e un blocco #if in un blocco #region .

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#endregion (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#endregion indica la fine di un blocco #region. Ad esempio:

#region MyClass definition


class MyClass
{
static void Main()
{
}
}
#endregion

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
#pragma (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

#pragma fornisce al compilatore istruzioni speciali per la compilazione del file in cui si trova. Le istruzioni
devono essere supportate dal compilatore. In altre parole, non è possibile usare #pragma per creare istruzioni di
pre-elaborazione personalizzate. Il compilatore Microsoft C# supporta le due istruzioni #pragma seguenti:
avviso #pragma
checksum #pragma

Sintassi
#pragma pragma-name pragma-arguments

Parametri
pragma-name
Nome di un pragma riconosciuto.
pragma-arguments
Argomenti specifici del pragma.

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
avviso #pragma
checksum #pragma
#pragma warning (Riferimenti per C#)
28/01/2021 • 2 minutes to read • Edit Online

#pragma warning consente di abilitare o disabilitare alcuni avvisi.

Sintassi
#pragma warning disable warning-list
#pragma warning restore warning-list

Parametri
warning-list
Elenco separato da virgole di numeri di avviso. Il prefisso "CS" è facoltativo.
Quando non viene specificato alcun numero di avviso, disable disabilita tutti gli avvisi e restore abilita tutti
gli avvisi.

NOTE
Per trovare i numeri di avviso in Visual Studio, compilare il progetto e quindi cercare i numeri di avviso nella finestra
Output .

Esempio
// pragma_warning.cs
using System;

#pragma warning disable 414, CS3021


[CLSCompliant(false)]
public class C
{
int i = 1;
static void Main()
{
}
}
#pragma warning restore CS3021
[CLSCompliant(false)] // CS3021
public class D
{
int i = 1;
public static void F()
{
}
}

Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
Errori del compilatore C#
#pragma checksum (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online

Genera i checksum per i file di origine per favorire il debug delle pagine ASP.NET.

Sintassi
#pragma checksum "filename" "{guid}" "checksum bytes"

Parametri
"filename"
Nome del file che richiede il monitoraggio per modifiche o aggiornamenti.
"{guid}"
Identificatore univoco globale (GUID, Globally Unique Identifier) dell'algoritmo hash.
"checksum_bytes"
Stringa di cifre esadecimali che rappresenta i byte del checksum. Deve essere un numero pari di cifre
esadecimali. Un numero dispari di cifre genera un avviso in fase di compilazione, pertanto la direttiva viene
ignorata.

Osservazioni
Il debugger di Visual Studio usa un checksum per trovare sempre l'origine corretta. Il compilatore calcola il
checksum di un file di origine, quindi genera l'output nel file del database di programma (PDB). Il PDB viene
quindi usato dal debugger per eseguire il confronto con il checksum calcolato per il file di origine.
Questa soluzione non funziona per i progetti ASP.NET, perché il checksum calcolato fa riferimento al file di
origine generato anziché al file con estensione aspx. Per risolvere questo problema, #pragma checksum offre il
supporto del checksum per le pagine ASP.NET.
Quando si crea un progetto ASP.NET in Visual C# il file di origine generato contiene un checksum per il file con
estensione aspx, dal quale viene generata l'origine. Queste informazioni vengono quindi scritte dal compilatore
nel file PDB.
Se il compilatore non rileva alcuna direttiva #pragma checksum nel file, calcola il checksum e scrive il valore nel
file PDB.

Esempio
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
}
}
Vedere anche
Riferimenti per C#
Guida per programmatori C#
Direttive per il preprocessore C#
Opzioni del compilatore C#
02/11/2020 • 2 minutes to read • Edit Online

Tramite il compilatore vengono generati file eseguibili (con estensione exe), librerie a collegamento dinamico
(con estensione dll) o moduli di codice (con estensione netmodule).
Ogni opzione del compilatore è disponibile in due forme: -opzione e (opzione) . La documentazione mostra
solo la forma -opzione .
In Visual Studio è possibile impostare le opzioni del compilatore nel file di web.config . Per ulteriori informazioni,
vedere <compiler> elemento.

Contenuto della sezione


Compilazione dalla riga di comando con csc.exe Informazioni sulla compilazione di un'applicazione Visual
C# dalla riga di comando.
Come impostare le variabili di ambiente per la riga di comando di Visual Studio Viene descritta la
procedura per eseguire VsDevCmd.bat per abilitare le compilazioni da riga di comando.
Opzioni del compilatore C# elencate per categoria Elenco categorico delle opzioni del compilatore.
Opzioni del compilatore C# elencate in ordine alfabetico Elenco alfabetico delle opzioni del compilatore.

Sezioni correlate
Pagina Compila, Progettazione progetti Impostazione delle proprietà che determinano la modalità di
compilazione, compilazione e debug del progetto. Include informazioni sulle istruzioni di compilazione
personalizzate nei progetti Visual C#.
Compilazioni predefinite e personalizzate Informazioni sui tipi e le configurazioni di compilazione.
Preparazione e gestione delle compilazioni Procedure per la compilazione nell'ambiente di sviluppo di
Visual Studio.
Compilazione dalla riga di comando con csc.exe
28/01/2021 • 7 minutes to read • Edit Online

È possibile richiamare il compilatore C# digitando il nome del relativo file eseguibile (csc.exe) al prompt dei
comandi.
Se si usa la finestra Prompt dei comandi per gli sviluppatori per Visual Studio , tutte le variabili di
ambiente necessarie sono impostate automaticamente. Per informazioni su come accedere a questo strumento,
vedere Developer Command Prompt for Visual Studio (Prompt dei comandi per gli sviluppatori per Visual
Studio).
Se si utilizza una finestra del prompt dei comandi standard, è necessario modificare il percorso prima di poter
richiamare csc.exe da qualsiasi sottodirectory del computer. È inoltre necessario eseguire VsDevCmd.bat per
impostare le variabili di ambiente appropriate per supportare le compilazioni da riga di comando. Per ulteriori
informazioni su VsDevCmd.bat, incluse le istruzioni su come trovarlo ed eseguirlo, vedere come impostare le
variabili di ambiente per la riga di comando di Visual Studio.
Se nel computer in uso è disponibile solo Windows Software Development Kit (SDK), è possibile usare il
compilatore C# al Prompt dei comandi di SDK che viene visualizzato dall'opzione di menu Microsoft .NET
Framework SDK .
È inoltre possibile utilizzare MSBuild per compilare programmi C# a livello di codice. Per altre informazioni,
vedere MSBuild.
Il csc.exe file eseguibile si trova in genere nella cartella Microsoft. NET\Framework nella \ <Version> directory
Windows . La posizione del file può variare in base all'esatta configurazione di un determinato computer. Se nel
computer sono installate più versioni di .NET Framework, saranno disponibili più versioni di questo file. Per altre
informazioni su queste installazioni, vedere How to: determine which versions of the .NET Framework are
installed (Procedura: Determinare le versioni di .NET Framework installate).

TIP
Quando si compila un progetto usando l'IDE di Visual Studio, è possibile visualizzare il comando csc e le relative opzioni
del compilatore associate nella finestra Output . Per visualizzare queste informazioni, seguire le istruzioni in Procedura:
Visualizzare, salvare e configurare file di log di compilazione per impostare il livello di dettaglio dei dati di log su Normale
o Dettagliato . Al termine della ricompilazione del progetto, nella finestra Output cercare csc per trovare la chiamata del
compilatore C#.

Contenuto dell'argomento
Regole per la sintassi della riga di comando
Righe di comando di esempio
Differenze tra il compilatore C# e l'output del compilatore C++

Regole per la sintassi della riga di comando per il compilatore C#


Il compilatore C# usa le regole seguenti per interpretare gli argomenti visualizzati nella riga di comando del
sistema operativo:
Gli argomenti sono delimitati da spazi vuoti, ovvero da uno spazio o da una tabulazione.
L'accento circonflesso (^) non viene riconosciuto come carattere di escape o delimitatore. Il carattere
viene gestito dal parser della riga di comando nel sistema operativo prima di essere passato alla matrice
argv del programma.

Una stringa racchiusa tra virgolette doppie ("string") viene interpretata come argomento singolo,
indipendentemente dalla presenza di spazi al suo interno. Una stringa tra virgolette può essere
incorporata in un argomento.
Le virgolette doppie precedute da una barra rovesciata (\") vengono interpretate come carattere letterale
virgolette doppie (").
Le barre rovesciate vengono interpretate letteralmente, a meno che non precedano virgolette doppie.
Se un numero pari di barre rovesciate è seguito da virgolette doppie, viene inserita solo una barra
rovesciata nella matrice argv per ogni coppia di barre rovesciate e le virgolette doppie vengono
interpretate come delimitatore di stringa.
Se un numero dispari di barre rovesciate è seguito da virgolette doppie, viene inserita solo una barra
rovesciata nella matrice argv per ogni coppia di barre rovesciate e le virgolette doppie vengono
"ignorate" dalla barra rovesciata rimanente. Questo determina l'aggiunta di un valore letterale virgolette
doppie (") in argv .

Righe di comando di esempio per il compilatore C#


Compila file.cs producendo File.exe:

csc File.cs

Compila file.cs producendo File.dll:

csc -target:library File.cs

Compila file.cs e crea My.exe:

csc -out:My.exe File.cs

Compila tutti i file C# della directory corrente con le ottimizzazioni attivate e definisce il simbolo DEBUG.
L'output è File2.exe:

csc -define:DEBUG -optimize -out:File2.exe *.cs

Compila tutti i file C# della directory corrente generando una versione di debug di File2.dll. Non viene
visualizzato nessun logo e nessun avviso:

csc -target:library -out:File2.dll -warn:0 -nologo -debug *.cs

Compila tutti i file C# della directory corrente con something. xyz (una dll):

csc -target:library -out:Something.xyz *.cs

Differenze tra output del compilatore C# e output del compilatore


C++
Non sono presenti file oggetto (obj) creati come risultato della chiamata del compilatore C#. i file di output
vengono creati direttamente. Di conseguenza il compilatore C# non richiede un linker.

Vedere anche
Opzioni del compilatore C#
Opzioni del compilatore C# in ordine alfabetico
Opzioni del compilatore C# elencate per categoria
Argomenti Main () e Command-Line
Argomenti della riga di comando
Come visualizzare gli argomenti della riga di comando
Valori restituiti da Main()
Come impostare le variabili di ambiente per la riga
di comando di Visual Studio
28/01/2021 • 2 minutes to read • Edit Online

Il file VsDevCmd.bat imposta le variabili di ambiente appropriate per abilitare le compilazioni da riga di
comando.

NOTE
Visual Studio 2015 e versioni precedenti usavano VSVARS32.bat, non VsDevCmd.bat per lo stesso scopo. Il file era
archiviato in \Programmi\Microsoft Visual Studio\Versione\Common7\Tools o Programmi (x86)\Microsoft Visual
Studio\Versione\Common7\Tools.

Se la versione corrente di Visual Studio è installata in un computer che include anche la versione precedente di
Visual Studio, non eseguire VsDevCmd.bat e VSVARS32.BAT da diverse versioni nella stessa finestra del prompt
dei comandi. È invece necessario eseguire il comando per ogni versione in una finestra specifica.
Per eseguire VsDevCmd.BAT
1. Dal menu Star t aprire il Prompt dei comandi per gli sviluppatori per vs 2019 . Si trova nella
cartella Visual Studio 2019 .
2. Passare a \Programmi\Microsoft Visual Studio \ Version \ offering\Common7\Tools o \Program Files
(x86) \Microsoft Visual Studio \ Version \ offering\Common7\Tools subdirectory dell'installazione. (La
versione è 2019 per la versione corrente. Offerta è Enterprise, Professional o Community.)
3. Eseguire VsDevCmd.bat digitando VsDevCmd .
Cau t i on

Il file VsDevCmd.bat può variare da computer a computer. Non sostituire un file VsDevCmd.bat mancante
o danneggiato con un file VsDevCmd.bat da un altro computer. Rieseguire invece l'installazione per
sostituire il file mancante.
Opzioni disponibili per VsDevCmd.BAT
Per visualizzare le opzioni disponibili per VsDevCmd.BAT, eseguire il comando con l'opzione -help :

VsDevCmd.bat -help

Vedere anche
Compilazione dalla riga di comando con csc.exe
Opzioni del compilatore C# elencate per categoria
02/11/2020 • 7 minutes to read • Edit Online

Le seguenti opzioni del compilatore sono ordinate per categoria. Per un elenco alfabetico, vedere Opzioni del
compilatore C# in ordine alfabetico.

Optimization
O P Z IO N E SC O P O

-filealign Specifica le dimensioni delle sezioni nel file di output.

-Ottimizza Abilita/disabilita le ottimizzazioni.

File di output
O P Z IO N E SC O P O

-deterministic Fa sì che l'output del compilatore sia un assembly il cui


contenuto binario è identico in tutte le compilazioni se gli
input sono identici.

-doc Specifica un file XML in cui vengono scritti i commenti sulla


documentazione elaborati.

-out Specifica il file di output.

-pathmap Specifica un mapping per i nomi di percorsi di origine


restituiti dal compilatore

-pdb Specifica il nome file e il percorso del file pdb.

-piattaforma Specificare la piattaforma di output.

-preferreduilang Specificare una lingua per l'output del compilatore.

-refout Generare un assembly di riferimento oltre all'assembly


principale.

-refonly Generare un assembly di riferimento invece dell'assembly


principale.

-destinazione Specifica il formato del file di output usando una delle


opzioni seguenti: -target: appcontainerexe, -target: exe, -
target: Library, -target: module, -target: winexeo -target:
winmdobj.

moduleName<string> Specificare il nome del modulo di origine


Assembly .NET
O P Z IO N E SC O P O

-addmodule Specifica uno o più moduli che devono fare parte di questo
assembly.

-delaysign Indica al compilatore di aggiungere la chiave pubblica e di


lasciare l'assembly non firmato.

-keycontainer Specifica il nome del contenitore di chiavi crittografiche.

-keyfile Specifica il nome file contenente la chiave crittografica.

-lib Specifica la posizione degli assembly a cui si fa riferimento


tramite -reference.

-nostdlib Indica al compilatore non di non fare importare la libreria


standard (mscorlib.dll).

-publicsign Applica una chiave pubblica senza firmare l'assembly, ma


imposta il bit nell'assembly che indica che l'assembly è
firmato.

-riferimento Importa i metadati da un file contenente un assembly.

-analyzer Esegue gli analizzatori da questo assembly (forma breve: /a)

-additionalfile Assegna un nome ad altri file che non influiscono


direttamente sulla generazione del codice, ma possono
essere usati dagli analizzatori per produrre errori o avvisi.

-embed Incorporare tutti i file di origine nel file PDB.

incorporare<file list> Incorporare file specifici nel file PDB.

Debug/Controllo errori
O P Z IO N E SC O P O

-bugreport Crea un file contenente informazioni che rendono più


semplice segnalare un bug.

-selezionato Specifica se il calcolo di interi che supera i limiti del tipo di


dati genererà un'eccezione in fase di esecuzione.

-debug Indica al compilatore di generare informazioni di debug.

-errorreport Imposta il comportamento relativo alla segnalazione degli


errori.

-fullpaths Specifica il percorso assoluto del file nell'output del


compilatore.
O P Z IO N E SC O P O

-nowarn Elimina la generazione degli avvisi specificati da parte del


compilatore.

-nullable Specifica l'opzione di contesto Nullable.

-warn Imposta il livello degli avvisi.

-warnaserror Alza il livello degli avvisi a errori.

RuleSet<file> Specificare un file di set di regole che disabilita la diagnostica


specifica.

Preprocessore
O P Z IO N E SC O P O

-definizione Definisce i simboli del preprocessore.

Risorse
O P Z IO N E SC O P O

-collegamento Rende disponibili per il progetto le informazioni sui tipi COM


negli assembly specificati.

-linkresource Crea un collegamento a una risorsa gestita.

-risorsa Incorpora una risorsa .NET nel file di output.

-win32icon Specifica un file ico da inserire nel file di output.

-win32res Specifica una risorsa Win32 da inserire nel file di output.

Varie
O P Z IO N E SC O P O

@ Specifica un file di risposta.

-? Elenca le opzioni del compilatore in stdout.

-baseaddress Specifica l'indirizzo di base preferenziale in cui caricare una


DLL.

-codepage Specifica la tabella codici da usare per tutti i file del codice
sorgente nella compilazione.

-Guida Elenca le opzioni del compilatore in stdout.


O P Z IO N E SC O P O

-highentropyva Specifica che il file eseguibile supporta ASLR (Address Space


Layout Randomization).

-langversion Specifica la versione del linguaggio: Default, ISO-1, ISO-2, 3,


4, 5, 6, 7, 7.1, 7.2, 7.3 o Latest

-main Specifica il percorso del metodo Main .

-noconfig Indica al compilatore di non eseguire la compilazione con


csc.rsp.

-nologo Elimina i messaggi informativi del compilatore.

-recurse Cerca nelle sottodirectory i file di origine da compilare.

-subsystemversion Specifica la versione minima del sottosistema che può essere


utilizzata dal file eseguibile.

-unsafe Abilita la compilazione del codice che usa la parola chiave


unsafe.

-utf8output Visualizza l'output del compilatore usando la codifica UTF-8.

-parallel[+|-] Specifica se usare la compilazione simultanea (+).

-checksumalgorithm:<alg> Specificare l'algoritmo per il calcolo del checksum del file di


origine archiviato nel file PDB. I valori supportati sono:
SHA256 (impostazione predefinita) o SHA1.
A causa di problemi di conflitto con SHA1, Microsoft
consiglia SHA256.

Opzioni obsolete
O P Z IO N E SC O P O

-incremental Abilita la compilazione incrementale.

Vedi anche
Opzioni del compilatore C#
Opzioni del compilatore C# in ordine alfabetico
Come impostare le variabili di ambiente per la riga di comando di Visual Studio
Opzioni del compilatore C# in ordine alfabetico
02/11/2020 • 7 minutes to read • Edit Online

Le seguenti opzioni del compilatore sono ordinate alfabeticamente. Per un elenco organizzato per categorie,
vedere Opzioni del compilatore C# elencate per categoria.

O P Z IO N E SC O P O

@ Legge un file di risposta per altre opzioni

-? Visualizza un messaggio relativo all'utilizzo in stdout.

-additionalfile Assegna un nome ad altri file che non influiscono


direttamente sulla generazione del codice, ma possono
essere usati dagli analizzatori per produrre errori o avvisi.

-addmodule Collega i moduli specificati nell'assembly

-analyzer Esegue gli analizzatori da questo assembly (forma breve: -a)

-appconfig Specifica il percorso del file app.config in fase di associazione


di assembly.

-baseaddress Specifica l'indirizzo di base della libreria da compilare.

-bugreport Crea un file di report sui bug. Questo file verrà inviato con le
informazioni sull'arresto anomalo se viene usato con -
errorreport:prompt o -errorreport:send.

-selezionato Fa generare al compilatore i controlli dell'overflow.

-checksumalgorithm:<alg> Specifica l'algoritmo per il calcolo del checksum del file di


origine archiviato nel file PDB. I valori supportati sono:
SHA256 (impostazione predefinita) o SHA1.
A causa di problemi di conflitto con SHA1, Microsoft
consiglia SHA256.

-codepage Specifica la tabella di codici da utilizzare per l'apertura dei file


di origine.

-debug Crea informazioni di debug.

-define Definisce i simboli di compilazione condizionale.

-delaysign Ritarda la firma dell'assembly usando solo la parte pubblica


della chiave con nome sicuro.

-deterministic Fa sì che l'output del compilatore sia un assembly il cui


contenuto binario è identico in tutte le compilazioni se gli
input sono identici.
O P Z IO N E SC O P O

-doc Specifica un file XML della documentazione da generare.

-embed Incorporare tutti i file di origine nel file PDB.

incorporare<file list> Incorporare file specifici nel file PDB.

-errorendlocation Riga e colonna di output della posizione finale di ogni errore.

ErrorLog<file> Specificare un file per registrare tutti i dati di diagnostica del


compilatore e dell'analizzatore.

-errorreport Specifica come gestire gli errori interni del compilatore:


prompt, send o none. Il valore predefinito è none.

-filealign Specifica l'allineamento usato per le sezioni del file di output.

-fullpaths Fa generare al compilatore percorsi completi.

-Guida Visualizza un messaggio relativo all'utilizzo in stdout.

-highentropyva Specifica che è supportata la funzionalità ASLR a entropia


elevata.

-incremental Abilita la compilazione incrementale [obsoleto]

-keycontainer Specifica un contenitore di chiavi con nome sicuro.

-keyfile Specifica un file di chiave con nome sicuro.

langversion<string> Specifica la versione del linguaggio: Default, ISO-1, ISO-2, 3,


4, 5, 6, 7, 7.1, 7.2, 7.3 o Latest

-lib Specifica directory aggiuntive in cui cercare i riferimenti.

-collegamento Rende disponibili per il progetto le informazioni sui tipi COM


negli assembly specificati.

-linkresource Collega la risorsa specificata all'assembly.

-main Specifica il tipo che contiene il punto di ingresso, ignorando


tutti gli altri punti di ingresso possibili.

-moduleassemblyname Specifica l'assembly i cui tipi non pubblici sono accessibili da


un file con estensione NETMODULE.

moduleName<string> Specificare il nome del modulo di origine

-noconfig Indica al compilatore di non includere automaticamente il file


CSC.RSP.

-nologo Impedisce la visualizzazione del messaggio di copyright del


compilatore.
O P Z IO N E SC O P O

-nostdlib Indica al compilatore non di non fare riferimento alla libreria


standard (mscorlib.dll).

-nowarn Disabilita messaggi di avviso specifici

-nowin32manifest Indica il compilatore di non incorporare un manifesto


dell'applicazione nel file eseguibile.

-nullable Specifica l'opzione di contesto Nullable.

-Ottimizza Abilita/disabilita le ottimizzazioni.

-out Specifica il nome del file di output (impostazione predefinita:


nome base del file con la classe principale o il primo file).

-parallel[+|-] Specifica se usare la compilazione simultanea (+).

-pathmap Specifica un mapping per i nomi di percorsi di origine


restituiti dal compilatore.

-pdb Specifica il nome file e il percorso del file pdb.

-piattaforma Limita le piattaforme in cui è possibile eseguire il codice: x86,


Itanium, x64, anycpu o anycpu32bitpreferred. Il valore
predefinito è anycpu.

-preferreduilang Specifica la lingua da utilizzare per l'output del compilatore.

-publicsign Applica una chiave pubblica senza firmare l'assembly, ma


imposta il bit nell'assembly che indica che l'assembly è
firmato.

-recurse Include tutti i file presenti nella directory corrente e nelle


relative sottodirectory in base alle specifiche dei caratteri
jolly.

-riferimento Fa riferimento ai metadati dei file di assembly specificati.

-refout Generare un assembly di riferimento oltre all'assembly


principale.

-refonly Generare un assembly di riferimento invece dell'assembly


principale.

-reportanalyzer Restituire informazioni aggiuntive dell'analizzatore, ad


esempio il tempo di esecuzione.

-risorsa Incorpora la risorsa specificata.

RuleSet<file> Specificare un file di set di regole che disabilita la diagnostica


specifica.
O P Z IO N E SC O P O

-subsystemversion Specifica la versione minima del sottosistema che può essere


utilizzata dal file eseguibile.

-destinazione Specifica il formato del file di output usando una delle


opzioni seguenti: -target: appcontainerexe, -target: exe, -
target: Library, -target: module, -target: winexe, -target:
winmdobj.

-unsafe Consente codice unsafe.

-utf8output Restituisce i messaggi del compilatore usando la codifica


UTF-8.

-version Visualizza il numero di versione del compilatore ed esce.

-warn Imposta il livello degli avvisi (0-4).

-warnaserror Segnala determinati avvisi come errori.

-win32icon Usa questa icona per l'output.

-win32manifest Specifica un file manifesto win32 personalizzato.

-win32res Specifica il file di risorse win32 (res).

Vedere anche
Opzioni del compilatore C#
Opzioni del compilatore C# elencate per categoria
Come impostare le variabili di ambiente per la riga di comando di Visual Studio
<compiler> Elemento
@ (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

L'opzione @ consente di specificare un file che contiene le opzioni del compilatore e i file di codice sorgente da
compilare.

Sintassi
@response_file

Argomenti
response_file
File che elenca le opzioni del compilatore o i file di codice sorgente da compilare.

Osservazioni
Le opzioni del compilatore e i file di codice sorgente verranno elaborati dal compilatore come se fossero stati
specificati nella riga di comando.
Per specificare più di un file di risposta in una compilazione, specificare più opzioni di file di risposta. Ad
esempio:

@file1.rsp @file2.rsp

In un file di risposta, più opzioni del compilatore e file di codice sorgente possono essere contenuti in una sola
riga. La specificazione di una singola opzione del compilatore deve essere contenuta in una sola riga (non può
estendersi su più righe). I file di risposta possono contenere commenti che iniziano con il simbolo #.
Specificare le opzioni del compilatore da un file di risposta equivale a eseguire tali comandi dalla riga di
comando. Per altre informazioni, vedere Compilazione dalla riga di comando.
Il compilatore elabora le opzioni di comando quando vengono rilevate. Di conseguenza, gli argomenti della riga
di comando possono eseguire l'override di opzioni elencate in precedenza nei file di risposta. Viceversa, le
opzioni in un file di risposta eseguiranno l'override delle opzioni elencate in precedenza nella riga di comando o
in altri file di risposta.
In C# è disponibile il file csc.rsp, che si trova nella stessa directory del file csc.exe. Per ulteriori informazioni su
CSC. rsp, vedere -noconfig .
Questa opzione del compilatore non può essere impostata nell'ambiente di sviluppo di Visual Studio, né
modificata a livello di codice.

Esempio
Di seguito sono riportate alcune righe da un file di risposta di esempio:

# build the first output file


-target:exe -out:MyExe.exe source1.cs source2.cs
Vedere anche
Opzioni del compilatore C#
-addmodule (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa opzione aggiunge un modulo creato con l'opzione target:module nella compilazione in corso.

Sintassi
-addmodule:file[;file2]

Argomenti
file , file2
Un file di output che contiene i metadati. Il file non può contenere un manifesto dell'assembly. Per importare più
di un file, separare i nomi dei file con una virgola o con un punto e virgola.

Osservazioni
In fase di esecuzione tutti i moduli aggiunti con -addmodule devono essere nella stessa directory del file di
output. Ovvero, è possibile specificare un modulo in una directory in fase di compilazione, ma il modulo deve
essere nella directory dell'applicazione in fase di esecuzione. Se il modulo non è nella directory dell'applicazione
in fase di esecuzione, verrà restituita un'eccezione TypeLoadException.
file non può contenere un assembly. Ad esempio, se il file di output è stato creato con -target:module, i relativi
metadati possono essere importati con -addmodule .
Se il file di output è stato creato con un'opzione -target diversa da -target:module , i relativi metadati non
possono essere importati con l'opzione -addmodule , ma possono essere impostati con l'opzione -reference.
L'opzione del compilatore non è disponibile in Visual Studio. Un progetto non può fare riferimento a un modulo.
Inoltre, l'opzione del compilatore non può essere modificata a livello di codice.

Esempio
Compilare un file di origine input.cs e aggiungere i metadati da metad1.netmodule e metad2.netmodule per
creare out.exe :

csc -addmodule:metad1.netmodule;metad2.netmodule -out:out.exe input.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
Assembly su più file
Procedura: compilare un assembly su più file
-appconfig (opzioni del compilatore C#)
02/11/2020 • 3 minutes to read • Edit Online

L'opzione -appconfig del compilatore consente a un'applicazione C# di specificare il percorso del file di
configurazione (app.config) dell'applicazione di un assembly per Common Language Runtime (CLR) in fase di
associazione degli assembly.

Sintassi
-appconfig:file

Argomenti
file
Obbligatorio. Il file di configurazione dell'applicazione che contiene le impostazioni di associazione degli
assembly.

Osservazioni
L'opzione -appconfig può essere usata anche in scenari avanzati nei quali un assembly deve fare riferimento
allo stesso tempo alla versione di .NET Framework e a quella di .NET Framework per Silverlight di un particolare
assembly di riferimento. Ad esempio, una finestra di progettazione XAML scritta in Windows Presentation
Foundation (WPF) potrebbe fare riferimento al desktop WPF, per l'interfaccia utente della finestra di
progettazione, e al subset di WPF incluso in Silverlight. Lo stesso assembly della finestra di progettazione deve
accedere a entrambi gli assembly. Per impostazione predefinita, i riferimenti separati provocano un errore del
compilatore, poiché l'associazione di assembly considera uguali i due assembly.
L'opzione -appconfig del compilatore consente di specificare il percorso del file app.config che disabilita il
comportamento predefinito tramite un tag <supportPortability> , come illustrato nell'esempio seguente.
<supportPortability PKT="7cec85d7bea7798e" enable="false"/>

Il compilatore passa il percorso del file alla logica di associazione degli assembly di CLR.

NOTE
Se si usa Microsoft Build Engine (MSBuild) per compilare l'applicazione, è possibile impostare l'opzione -appconfig del
compilatore aggiungendo un tag di proprietà al file con estensione csproj. Per usare il file app.config già impostato nel
progetto, aggiungere un tag di proprietà <UseAppConfigForCompiler> al file con estensione csproj e impostarne il valore
su true . Per specificare un file app.config diverso, aggiungere un tag di proprietà <AppConfigForCompiler> e
impostarne il valore sul percorso del file.

Esempio
L'esempio seguente illustra un file app.config che consente a un'applicazione di fare riferimento
all'implementazione di .NET Framework e di .NET Framework per Silverlight per qualsiasi assembly di .NET
Framework presente in entrambe le implementazioni. L'opzione -appconfig del compilatore specifica il
percorso di questo file app.config.
<configuration>
<runtime>
<assemblyBinding>
<supportPortability PKT="7cec85d7bea7798e" enable="false"/>
<supportPortability PKT="31bf3856ad364e35" enable="false"/>
</assemblyBinding>
</runtime>
</configuration>

Vedere anche
<supportPortability> Elemento
Opzioni del compilatore C# in ordine alfabetico
-baseaddress (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -baseaddress consente di specificare l'indirizzo di base preferito in cui caricare una DLL. Per altre
informazioni su quando e perché usare questa opzione, vedere il blog di Larry Osterman.

Sintassi
-baseaddress:address

Argomenti
address
Indirizzo di base per la DLL. Questo indirizzo può essere specificato come numero decimale, esadecimale o
ottale.

Osservazioni
L'indirizzo di base predefinito per una DLL viene impostato dal Common Language Runtime .NET.
Tenere presente che la parola di ordine inferiore in questo indirizzo verrà arrotondata. Ad esempio, se si
specifica 0x11110001, il valore verrà arrotondato a 0x11110000.
Per completare il processo di firma di una DLL, usare SN.EXE con l'opzione -R.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Indirizzo di base DLL .
Per impostare questa opzione del compilatore a livello di codice, vedere BaseAddress.

Vedere anche
ProcessModule.BaseAddress
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-bugreport (opzioni del compilatore C#s)
28/01/2021 • 2 minutes to read • Edit Online

Specifica che le informazioni di debug devono essere inserite in un file per analisi successive.

Sintassi
-bugreport:file

Argomenti
file
Nome del file in cui si vuole inserire il report sui bug.

Osservazioni
L'opzione -bugrepor t ( specifica che devono essere inserite le informazioni seguenti in file :
Una copia di tutti i file di codice sorgente nella compilazione.
Un elenco delle opzioni del compilatore usate nella compilazione.
Informazioni sulla versione per il compilatore, il runtime e il sistema operativo.
Assembly e moduli a cui viene fatto riferimento, salvati come cifre esadecimali, ad eccezione degli
assembly forniti con .NET e .NET SDK.
L'eventuale output del compilatore.
Una descrizione del problema, che verrà richiesta.
Una descrizione del modo in cui si può risolvere il problema, che verrà richiesta.
Se questa opzione viene usata con -errorrepor t: prompt o -errorrepor t: Send , le informazioni nel file
verranno inviate a Microsoft Corporation.
Dato che una copia di tutti i file di codice sorgente verrà inserita in file , potrebbe essere utile riprodurre il
sospetto difetto del codice nel programma più breve possibile.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.
Si noti che contenuto del file generato espone il codice sorgente e ciò potrebbe causare la divulgazione
accidentale di informazioni.

Vedere anche
Opzioni del compilatore C#
-errorreport (opzioni del compilatore C#)
Gestione delle proprietà di progetti e soluzioni
-checked (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -checked specifica se un'istruzione di calcolo di interi che risulta in un valore non incluso
nell'intervallo dei tipi di dati e nell'ambito di una parola chiave checked o unchecked genera un'eccezione in fase
di esecuzione.

Sintassi
-checked[+ | -]

Osservazioni
Un'istruzione di calcolo di interi inclusa nell'ambito di una parola chiave checked o unchecked non è soggetta
all'opzione -checked .
Se un'istruzione di calcolo di interi che non è compresa nell'ambito di una parola chiave checked o unchecked
risulta in un valore non incluso nell'intervallo del tipo di dati e l'opzione -checked+ (o -checked ) viene usata
nella compilazione, tale istruzione genera un'eccezione in fase di esecuzione. Se l'opzione -checked- viene
usata nella compilazione, tale istruzione non genera un'eccezione in fase di esecuzione.
Il valore predefinito per questa opzione è -checked- ; il controllo dell'overflow è disabilitato.
In alcuni casi gli strumenti automatici usati per compilare applicazioni di grandi dimensioni impostano l'opzione
-checked su +. Uno scenario per usare l'opzione -checked- consiste nell'eseguire l'override del valore
predefinito globale dello strumento specificando -checked-.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto. Per altre informazioni, vedere Pagina Compilazione, Creazione
progetti (C#).
2. Fare clic sulla pagina della proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Controlla overflow aritmetico .
Per accedere all'opzione del compilatore a livello di codice, vedere CheckForOverflowUnderflow.

Esempio
Il comando seguente compila t2.cs . L'uso di -checked nel comando specifica che l'istruzione di calcolo di
interi nel file che non è nell'ambito della parola chiave checked o unchecked e che risulta in un valore non
incluso nell'intervallo dei tipi di dati, genera un'eccezione in fase di esecuzione.

csc t2.cs -checked

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-codepage (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Questa opzione specifica la tabella codici da usare durante la compilazione, se la pagina richiesta non è la tabella
codici predefinita corrente per il sistema.

Sintassi
-codepage:id

Argomenti
id
ID della tabella codici da usare per tutti i file di codice sorgente nella compilazione.

Osservazioni
Il compilatore tenterà innanzitutto di interpretare tutti i file di origine come UTF-8. Se i file del codice sorgente
sono codificati con una codifica diversa da UTF-8 e usano caratteri diversi dai caratteri ASCII a 7 bit, usare
l'opzione -codepage per specificare la tabella codici da usare. -codepage si applica a tutti i file di codice
sorgente nella compilazione.
Per informazioni su come individuare le tabelle codici supportate nel sistema, vedere GetCPInfo.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-debug (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -debug indica al compilatore di generare informazioni di debug e di inserirle nel file o nei file di
output.

Sintassi
-debug[+ | -]
-debug:{full | pdbonly}

Argomenti
+ | -
Se si specifica + , o soltanto -debug , il compilatore genera le informazioni di debug e le inserisce in un
database di programma (file con estensione pdb). Se si specifica - , ovvero non si specifica -debug , non
verranno create informazioni di debug.
full | pdbonly
Specifica il tipo di informazioni di debug generate dal compilatore. L'argomento completo, effettivo se non si
specifica -debug:pdbonly , consente di allegare un debugger al programma in esecuzione. Specificando
pdbonly si consente il debug del codice sorgente quando il programma viene avviato nel debugger, ma
l'assembler viene visualizzato solo se il programma in esecuzione è allegato al debugger.

Osservazioni
Usare questa opzione per creare build di debug. Se non si specifica -debug , -debug+ o -debug:full , non sarà
possibile eseguire il debug del file di output del programma.
Se si usa -debug:full , tenere presente che influisce sulla velocità e sulle dimensioni del codice ottimizzato JIT. In
grado minore, -debug:full influisce sulla qualità del codice. Per la generazione di codice di rilascio, è
consigliabile usare -debug:pdbonly o non usare alcun file PDB.

NOTE
Una differenza tra -debug:pdbonly e -debug:full è che con -debug:full il compilatore genera un
DebuggableAttribute, che viene usato per indicare al compilatore JIT che le informazioni di debug sono disponibili. Di
conseguenza, se nel codice DebuggableAttribute è impostato su false e si usa -debug:full, si otterrà un errore.

Per altre informazioni su come configurare le prestazioni di debug di un'applicazione, vedere Semplificazione
del debug di un'immagine.
Per modificare il percorso del file con estensione pdb, vedere -pdb (opzioni del compilatore C#).
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Informazioni di Debug .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere DebugSymbols.

Esempio
Inserire le informazioni di debug nel file di output app.pdb :

csc -debug -pdb:app.pdb test.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-define (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -define definisce name come simbolo in tutti i file del codice sorgente nel programma.

Sintassi
-define:name[;name2]

Argomenti
name, name2
Nome di uno o più simboli che si vuole definire.

Osservazioni
L'opzione -define ha lo stesso effetto dell'uso di una direttiva #define per il preprocessore, ad eccezione del
fatto che l'opzione del compilatore è valida per tutti i file nel progetto. Un simbolo resta definito in un file del
codice sorgente finché una direttiva #undef nel file non rimuove la definizione. Quando si usa l'opzione -define,
una direttiva #undef in un file non ha effetto in altri file del codice sorgente nel progetto.
È possibile usare i simboli creati da questa opzione con #if, #else, #elif e #endif per la compilazione condizionale
dei file del codice sorgente.
-d è la versione abbreviata di -define .
È possibile definire più simboli con -define separando i nomi di simbolo con virgole o punti e virgola. Ad
esempio:

-define:DEBUG;TUESDAY

Il compilatore C# stesso non definisce alcun simbolo o macro che è possibile usare nel codice sorgente. Tutte le
definizioni dei simboli devono essere definite dall'utente.

NOTE
#define in C# non consente di assegnare un valore a un simbolo, diversamente dai linguaggi come C++. Ad esempio,
non è possibile usare #define per creare una macro o per definire una costante. Se è necessario definire una costante,
usare una variabile enum . Se si vuole creare una macro in stile C++, prendere in considerazione altre alternative, ad
esempio i generics. Poiché le macro sono notoriamente soggette a errori, C# non ne consente l'uso, ma offre alternative
più sicure.

Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio


1. Aprire la pagina Proprietà del progetto.
2. Nella scheda Compila digitare il simbolo che deve essere definito nella casella Simboli di
compilazione condizionale . Se si usa l'esempio di codice seguente, ad esempio, digitare xx nella
casella di testo.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere DefineConstants.

Esempio
// preprocessor_define.cs
// compile with: -define:xx
// or uncomment the next line
// #define xx
using System;
public class Test
{
public static void Main()
{
#if (xx)
Console.WriteLine("xx defined");
#else
Console.WriteLine("xx not defined");
#endif
}
}

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-delaysign (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Questa opzione indica al compilatore di riservare spazio nel file di output in modo che si possa aggiungere una
firma digitale in un secondo tempo.

Sintassi
-delaysign[ + | - ]

Argomenti
+ | -

Usare -delaysign- se si vuole un assembly con firma completa. Usare -delaysign+ se si vuole solo inserire la
chiave pubblica nell'assembly. L'impostazione predefinita è -delaysign- .

Osservazioni
L'opzione -delaysign non ha alcun effetto a meno che non venga usata con -filefile o -container.
Le opzioni -delaysign e -publicsign si escludono a vicenda.
Quando si richiede un assembly con firma completa, il compilatore genera un hash per il file contenente il
manifesto (i metadati dell'assembly) e firma tale hash con la chiave privata. Tale operazione crea una firma
digitale archiviata nel file contenente il manifesto. Quando per un assembly è impostata la firma ritardata, il
compilatore non calcola e archivia la firma, ma riserva spazio nel file in modo che la firma possa essere aggiunta
successivamente.
Ad esempio, l'uso di -delaysign+ consente a un tester di inserire l'assembly nella Global Assembly Cache. Al
termine del test, è possibile firmare completamente l'assembly inserendo la chiave privata nell'assembly con
l'utilità Assembly Linker.
Per altre informazioni, vedere Creazione e uso degli assembly con nome sicuro e Ritardo della firma di un
assembly.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Modificare la proprietà Solo firma ritardata .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere DelaySign.

Vedere anche
Opzione -publicsign C#
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-deterministic
28/01/2021 • 3 minutes to read • Edit Online

Fa sì che il compilatore generi un assembly il cui output byte per byte è identico in tutte le compilazioni se si
usano input identici.

Sintassi
-deterministic

Osservazioni
Per impostazione predefinita, l'output del compilatore da un determinato set di input è univoco, poiché il
compilatore aggiunge un timestamp e un MVID generato da numeri casuali. L'opzione -deterministic si usa
per generare un assembly deterministico il cui contenuto binario è identico in tutte le compilazioni purché
l'input rimanga lo stesso. In tale compilazione, i campi timestamp e MVID verranno sostituiti con i valori derivati
da un hash di tutti gli input di compilazione.
Il compilatore considera i seguenti input al fine del determinismo:
La sequenza dei parametri della riga di comando.
Il contenuto del file di risposta del file RSP del compilatore.
La versione precisa del compilatore in uso e i relativi assembly di riferimento.
Il percorso della directory corrente.
Il contenuto binario di tutti i file passati in modo esplicito al compilatore direttamente o indirettamente, tra
cui:
File di origine
Assembly di riferimento
Moduli a cui viene fatto riferimento
Risorse
Il file di chiave con nome sicuro
@ file di risposta
Analizzatori
Set di regole
File aggiuntivi che possono essere usati dagli analizzatori
Le impostazioni cultura correnti (per la lingua in cui vengono generati la diagnostica e i messaggi di
eccezione).
La codifica predefinita (o la tabella codici corrente) se non è specificata la codifica.
L'esistenza, non esistenza e contenuto dei file nei percorsi di ricerca del compilatore (specificati, ad esempio,
da -lib o -recurse ).
La piattaforma CLR in cui viene eseguito il compilatore.
Il valore di %LIBPATH% , che può influenzare il caricamento delle dipendenze dell'analizzatore.
Quando le origini sono disponibili pubblicamente, la compilazione deterministica può essere usata per stabilire
se un file binario viene compilato da un'origine attendibile. Può anche essere utile in un sistema di compilazione
continua per determinare se è necessario eseguire le istruzioni di compilazione che dipendono dalle modifiche
apportate a un file binario.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-doc (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -doc consente di inserire commenti per la documentazione in un file XML.

Sintassi
-doc:file

Argomenti
file
File di output in XML, con i commenti presenti nei file del codice sorgente della compilazione.

Commenti
Nei file del codice sorgente è possibile elaborare e aggiungere al file XML i commenti di documentazione che
precedono quanto segue:
Tipi definiti dall'utente, ad esempio una classe, un delegato o un'interfaccia
Membri quali un campo, un evento, una proprietà o un metodo
Il primo output inserito nel file XML è quello del file di codice sorgente che contiene Main.
Per usare il file XML generato con la funzionalità IntelliSense, assegnare al file XML lo stesso nome dell'assembly
che si vuole supportare, quindi accertarsi che il file XML si trovi nella stessa directory dell'assembly. In questo
modo, quando si farà riferimento all'assembly nel progetto Visual Studio, verrà trovato anche il file XML. Per
altre informazioni, vedere Inserimento di commenti al codice XML.
A meno che non si compili con -target: module, conterrà file tag che <assembly> </assembly> specificano il
nome del file contenente il manifesto dell'assembly per il file di output della compilazione.

NOTE
L'opzione -doc può essere usata per tutti i file di input o, se definita in Impostazioni progetto, per tutti i file di un
progetto. Per disabilitare la visualizzazione degli avvisi relativi ai commenti di documentazione per una sezione di codice o
un file specifico, usare #pragma warning.

Per informazioni sulla generazione di documentazione da commenti presenti nel codice, vedere Tag consigliati
per i commenti relativi alla documentazione.
Per impostare questa opzione del compilatore nell'ambiente di sviluppo di Visual Studio 2019
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla scheda Generazione .
3. Modificare la proprietà File di documentazione XML .
Per impostare questa opzione del compilatore nell'ambiente di sviluppo Visual Studio per Mac
1. Aprire la pagina delle Opzioni del progetto.
2. Selezionare la scheda del compilatore .
3. Selezionare Genera documentazione XML e immettere il nome del file nella casella di testo.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere
DocumentationFile.

Vedi anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-errorreport (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

Questa opzione rappresenta un modo pratico per segnalare a Microsoft un errore del compilatore interno C#.

NOTE
In Windows Vista e Windows Server 2008 le impostazioni di segnalazione errori apportate per Visual Studio non
eseguono l'override delle impostazioni apportate tramite Segnalazioni errori Windows. Le impostazioni di Segnalazione
errori Windows hanno sempre la precedenza sulle impostazioni della funzione di segnalazione errori di Visual Studio.

Sintassi
-errorreport:{ none | prompt | queue | send }

Argomenti
nessuna
Le segnalazioni sugli errori interni del compilatore non verranno raccolte o inviate a Microsoft.
messaggio di richiesta Richiede di inviare un report quando si riceve un errore interno del compilatore.
prompt è l'impostazione predefinita se si compila un'applicazione all'interno dell'ambiente di sviluppo.
coda di Accoda la segnalazione errori. Se si accede con credenziali amministrative, è possibile segnalare
qualsiasi errore dall'ultima volta che è stato effettuato l'accesso. Non verrà richiesto di inviare report di errori
più di una volta ogni tre giorni. queue è l'impostazione predefinita se si compila un'applicazione dalla riga di
comando.
Invia Invia automaticamente i report degli errori interni del compilatore a Microsoft. Per abilitare questa
opzione, è necessario prima di tutto accettare i Criteri per la raccolta dati Microsoft. La prima volta che si
specifica -errorrepor t:send in un computer, viene visualizzato un messaggio del compilatore che indirizza a un
sito Web contenente questi criteri.

Osservazioni
Se il compilatore non è in grado di elaborare un file del codice sorgente, viene restituito un errore interno del
compilatore (ICE, Internal Compiler Error). Quando si verifica un ICE, il compilatore non genera né un file di
output né informazioni di diagnostica utili per correggere il codice.
Nelle versioni precedenti, quando si riceveva un ICE si riceveva anche l'invito a contattare il Servizio supporto
tecnico Microsoft per segnalare il problema. Con -errorrepor t è possibile offrire informazioni sugli errori
interni del compilatore al team Visual C#. Le segnalazioni degli errori consentono di migliorare le versioni future
del compilatore.
La capacità di un utente di inviare report dipende dalle autorizzazioni relative ai criteri utente e computer.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto. Per altre informazioni, vedere Pagina Compilazione, Creazione
progetti (C#).
2. Fare clic sulla pagina della proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Segnalazione errori interni del compilatore .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere ErrorReport.

Vedere anche
Opzioni del compilatore C#
-filealign (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -filealign consente di specificare le dimensioni delle sezioni nel file di output.

Sintassi
-filealign:number

Argomenti
number
Valore che specifica le dimensioni delle sezioni nel file di output. I valori validi sono 512, 1024, 2048, 4096 e
8192. I valori sono in byte.

Osservazioni
Ogni sezione sarà allineata in base a un limite corrispondente multiplo del valore -filealign . Non vi è alcun
valore predefinito fisso. Se -filealign non è specificato, Common Language Runtime sceglie un valore
predefinito in fase di compilazione.
Specificando le dimensioni della sezione, si influisce sulla dimensione del file di output. La modifica delle
dimensioni della sezione può essere utile per i programmi che verranno eseguiti su dispositivi di piccole
dimensioni.
Usare DUMPBIN per visualizzare informazioni sulle sezioni nel file di output.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Allineamento file .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere FileAlignment.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-fullpaths (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

L'opzione -fullpaths fa in modo che il compilatore specifichi il percorso completo del file quando vengono
elencati gli avvisi e gli errori di compilazione.

Sintassi
-fullpaths

Osservazioni
Per impostazione predefinita, per gli errori e gli avvisi generati dalla compilazione viene specificato il nome del
file in cui si è verificato un errore. L'opzione -fullpaths fa in modo che il compilatore specifichi il percorso
completo del file.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
-help, -? (Opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa opzione invia un elenco di opzioni del compilatore e una breve descrizione di ogni opzione a stdout.

Sintassi
-help
-?

Osservazioni
Se questa opzione è inclusa in una compilazione, non verrà creato alcun file di output e non avrà luogo alcuna
compilazione.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-highentropyva (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione del compilatore -highentropyva indica al kernel di Windows se un particolare eseguibile supporta la
funzionalità ASLR (Address Space Layout Randomization) a entropia elevata.

Sintassi
-highentropyva[+ | -]

Argomenti
+ | -
Questa opzione consente di specificare che un eseguibile a 64 bit o un eseguibile contrassegnato dall'opzione
del compilatore -platform:anycpu supporta uno spazio indirizzi virtuali a entropia elevata. L'opzione è
disabilitata per impostazione predefinita. Per abilitarla, usare -highentropyva+ o -highentropyva .

Osservazioni
Con l'opzione -highentropyva , nelle versioni compatibili del kernel di Windows è possibile usare livelli di
entropia più elevati per la scelta casuale del layout dello spazio degli indirizzi di un processo in quanto parte di
ASLR. L'utilizzo di livelli più elevati di entropia indica la possibilità di allocare un numero maggiore di indirizzi ad
aree della memoria quali stack e heap. Di conseguenza, è più difficile indovinare la posizione di una determinata
area di memoria.
Quando l'opzione del compilatore -highentropyva è specificata, l'eseguibile di destinazione e tutti i moduli da
cui dipende devono essere in grado di gestire valori di puntatore maggiori di 4 gigabyte (GB), quando sono in
esecuzione come processi a 64 bit.
-keycontainer (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Specifica il nome del contenitore di chiavi crittografiche.

Sintassi
-keycontainer:string

Argomenti
string
Nome del contenitore di chiavi con nome sicuro.

Osservazioni
Quando si usa l'opzione -keycontainer , il compilatore crea un componente condivisibile. Il compilatore
inserisce una chiave pubblica dal contenitore specificato nel manifesto dell'assembly e firma l'assembly finale
con la chiave privata. Per generare un file di chiave, digitare sn -k file nella riga di comando. sn -i installa la
coppia di chiavi in un contenitore. Questa opzione non è supportata quando il compilatore viene eseguito in
CoreCLR. Per firmare un assembly quando si compila in CoreCLR, usare l'opzione -keyfile.
Se si esegue la compilazione con -target:module, il nome del file di chiave verrà mantenuto nel modulo e
incorporato nell'assembly quando il modulo verrà compilato in un assembly con -addmodule.
Questa opzione può essere specificata anche come attributo personalizzato
System.Reflection.AssemblyKeyNameAttribute nel codice sorgente di qualsiasi modulo MSIL (Microsoft
Intermediate Language).
È possibile passare al compilatore le informazioni di crittografia anche tramite -keyfile. Usare -delaysign se si
vuole aggiungere la chiave pubblica al manifesto dell'assembly, ma si preferisce rimandare la firma
dell'assembly a dopo il test di quest'ultimo.
Per altre informazioni, vedere Creazione e uso degli assembly con nome sicuro e Ritardo della firma di un
assembly.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Questa opzione del compilatore non è disponibile nell'ambiente di sviluppo di Visual Studio.
È possibile accedere a questa opzione del compilatore a livello di codice con AssemblyKeyContainerName.

Vedere anche
Opzione -keyfile del compilatore C#
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-keyfile (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

Specifica il nome file contenente la chiave crittografica.

Sintassi
-keyfile:file

Argomenti
T ERM IN E DEF IN IZ IO N E

file Nome del file che contiene la chiave con nome sicuro.

Osservazioni
Se si usa questa opzione, il compilatore inserisce la chiave pubblica dal file specificato nel manifesto
dell'assembly e quindi firma l'assembly finale con la chiave privata. Per generare un file di chiave, digitare sn -k
file nella riga di comando.

Se si esegue la compilazione con -target:module , il nome del file di chiave verrà mantenuto nel modulo e
incorporato nell'assembly creato quando si compila un assembly con -addmodule.
È possibile passare al compilatore le informazioni di crittografia anche tramite -keycontainer. Usare -delaysign
se si vuole un assembly con firma parziale.
Se nella stessa compilazione vengono specificate entrambe le opzioni -keyfile e -keycontainer (tramite opzione
della riga di comando o attributo personalizzato), verrà tentato prima il contenitore di chiavi. Se l'operazione
riesce, l'assembly viene firmato con le informazioni incluse nel contenitore di chiavi. Se invece il compilatore
non trova il contenitore di chiavi, effettua un tentativo con il file specificato in -keyfile. Se l'operazione riesce,
l'assembly verrà firmato con le informazioni contenute nel file di chiave e le informazioni sulla chiave verranno
installate nel contenitore di chiavi (in modo analogo a sn -i) in modo che nella compilazione successiva il
contenitore di chiavi sia valido.
Si noti che un file di chiave può contenere solo la chiave pubblica.
Per altre informazioni, vedere Creazione e uso degli assembly con nome sicuro e Ritardo della firma di un
assembly.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Firma .
3. Modificare la proprietà Scegli un file chiave con nome sicuro .
È possibile accedere a questa opzione del compilatore a livello di codice con AssemblyOriginatorKeyFile.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-langversion (opzioni del compilatore C#)
02/11/2020 • 7 minutes to read • Edit Online

Imposta il compilatore in modo da accettare solo sintassi inclusa nella specifica di linguaggio C# selezionata.

Sintassi
-langversion:option

Argomenti
option

I valori seguenti sono validi:

VA LO RE SIGN IF IC ATO

preview Il compilatore accetta tutte le sintassi di linguaggio valide


dalla versione di anteprima più recente.

latest Il compilatore accetta la sintassi dalla versione rilasciata più


recente del compilatore (inclusa la versione secondaria).

latestMajor ( default ) Il compilatore accetta la sintassi dalla versione principale più


recente rilasciata del compilatore.

9.0 Il compilatore accetta solo la sintassi inclusa in C# 9,0 o


versioni precedenti.

8.0 Il compilatore accetta solo la sintassi inclusa in C# 8.0 o


versione precedente.

7.3 Il compilatore accetta solo la sintassi inclusa in C# 7.3 o


versione precedente.

7.2 Il compilatore accetta solo la sintassi inclusa in C# 7.2 o


versione precedente.

7.1 Il compilatore accetta solo la sintassi inclusa in C# 7.1 o


versione precedente.

7 Il compilatore accetta solo la sintassi inclusa in C# 7.0 o


versione precedente.

6 Il compilatore accetta solo la sintassi inclusa in C# 6.0 o


versione precedente.

5 Il compilatore accetta solo la sintassi inclusa in C# 5.0 o


versione precedente.
VA LO RE SIGN IF IC ATO

4 Il compilatore accetta solo la sintassi inclusa in C# 4.0 o


versione precedente.

3 Il compilatore accetta solo la sintassi inclusa in C# 3.0 o


versione precedente.

ISO-2 (o 2 ) Il compilatore accetta solo la sintassi inclusa in ISO/IEC


23270:2006 C# (2,0).

ISO-1 (o 1 ) Il compilatore accetta solo la sintassi inclusa in ISO/IEC


23270:2003 C# (1.0/1.2).

La versione del linguaggio predefinita dipende dal framework di destinazione per l'applicazione e dalla versione
dell'SDK o di Visual Studio installata. Tali regole sono definite nell'articolo configurazione della versione della
lingua .

Commenti
I metadati a cui viene fatto riferimento nell'applicazione C# non sono soggetti all'opzione del compilatore -
langversion .
Poiché ogni versione del compilatore C# contiene estensioni per la specifica del linguaggio, -langversion non
offre la funzionalità equivalente di una versione precedente del compilatore.
Inoltre, mentre gli aggiornamenti di versione di C# coincidono in genere con le versioni principali di .NET
Framework, la nuova sintassi e le nuove funzionalità non sono necessariamente correlate alla versione specifica
del framework. Sebbene le nuove funzionalità richiedano un nuovo aggiornamento del compilatore, anch'esso
rilasciato insieme alla revisione di C#, ogni funzionalità specifica presenta requisiti minimi in termini di API .NET
o Common Language Runtime che ne consentono l'esecuzione in versioni di Framework di livello inferiore,
inclusi pacchetti NuGet o altre librerie.
Indipendentemente dall'impostazione di langversion usata, usare la versione corrente del Common Language
Runtime per creare il file con estensione exe o dll. Un'eccezione è costituita dagli assembly Friend e -
moduleassemblyname (opzione del compilatore C#), che funziona in -langversion: ISO-1 .
Per altri modi per specificare la versione del linguaggio C#, vedere l'articolo selezionare la versione del
linguaggio c# .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere LanguageVersion.

Specifiche del linguaggio C#


VERSIO N E C O L L EGA M EN TO DESC RIZ IO N E

C# 7.0 e versioni successive Attualmente non disponibile

C# 6.0 Collegamento Specifica del linguaggio C# versione 6 -


Bozza non ufficiale: .NET Foundation

C# 5.0 Scarica il PDF Standard ECMA-334, quinta edizione

C# 3.0 Scaricare DOC Specifica del linguaggio C# versione


3.0: Microsoft Corporation
VERSIO N E C O L L EGA M EN TO DESC RIZ IO N E

C# 2.0 Scarica il PDF Standard ECMA-334, quarta edizione

C# 1.2 Scaricare DOC Specifica del linguaggio C# versione


1.2: Microsoft Corporation

C# 1.0 Scaricare DOC Specifica del linguaggio C# versione


1.0: Microsoft Corporation

Versione minima dell'SDK necessaria per supportare tutte le


funzionalità del linguaggio
La tabella seguente elenca le versioni minime dell'SDK con il compilatore C# che supporta la versione del
linguaggio corrispondente:

VERSIO N E C # VERSIO N E M IN IM A DEL L 'SDK

C# 8.0 Microsoft Visual Studio/Build Tools 2019, versione 16,3 o


.NET Core 3,0 SDK

C# 7.3 Microsoft Visual Studio/Build Tools 2017 versione 15.7

C# 7.2 Microsoft Visual Studio/Build Tools 2017 versione 15.5

C# 7.1 Microsoft Visual Studio/Build Tools 2017 versione 15.3

C# 7.0 Microsoft Visual Studio/Build Tools 2017

C# 6 Microsoft Visual Studio/Build Tools 2015

C# 5 Microsoft Visual Studio/Build Tools 2012 o compilatore di


.Net Framework 4.5 in bundle

C# 4 Microsoft Visual Studio/Build Tools 2010 o compilatore di


.Net Framework 4.0 in bundle

C# 3 Microsoft Visual Studio/Build Tools 2008 o compilatore di


.Net Framework 3.5 in bundle

C# 2 Microsoft Visual Studio/Build Tools 2005 o compilatore di


.Net Framework 2.0 in bundle

C# 1.0/1.2 Microsoft Visual Studio/Build Tools .NET 2002 o bundled


.NET Framework 1,0 compilatore

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-lib (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -lib specifica il percorso degli assembly a cui si fa riferimento tramite l'opzione -Reference (opzioni
del compilatore C#) .

Sintassi
-lib:dir1[,dir2]

Argomenti
dir1
Directory in cui il compilatore può effettuare la ricerca se un assembly cui viene fatto riferimento non si trova
nella cartella di lavoro corrente (quella da cui si chiama il compilatore) o nella directory di sistema di Common
Language Runtime.
dir2
Una o più directory aggiuntive in cui effettuare la ricerca dei riferimenti agli assembly. Separare i nomi delle
directory aggiuntive con una virgola, senza inserire spazi.

Osservazioni
La ricerca dei riferimenti non completi agli assembly viene operata nell'ordine seguente:
1. Directory di lavoro corrente, ovvero la directory da cui viene chiamato il compilatore.
2. Directory di sistema di Common Language Runtime.
3. Directory specificate da -lib .
4. Directory specificate dalla variabile di ambiente LIB.
Per specificare un riferimento a un assembly, usare -reference .
L'opzione -lib è di tipo additivo: se viene specificata più volte, ogni nuovo valore verrà aggiunto a eventuali
valori precedenti.
In alternativa a -lib , è possibile copiare nella directory di lavoro tutti gli assembly necessari. Sarà quindi
sufficiente passare a -reference il nome dell'assembly. In seguito sarà possibile eliminare gli assembly dalla
directory di lavoro. Dal momento che il percorso dell'assembly dipendente non è specificato nel manifesto
dell'assembly, sarà possibile avviare l'applicazione sul computer di destinazione perché trovi e usi l'assembly
nella Global Assembly Cache.
Il fatto che nel compilatore sia possibile fare riferimento all'assembly non implica che Common Language
Runtime sarà in grado di trovare e caricare l'assembly in fase di runtime. Per informazioni dettagliate sulla
modalità di ricerca degli assembly a cui viene fatto riferimento in fase di esecuzione, vedere Come il runtime
individua gli assembly.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la finestra di dialogo Pagine delle proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Percorso riferimenti .
3. Modificare il contenuto della casella di riepilogo.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere ReferencePath.

Esempio
Compilare t2.cs per creare un file con estensione exe. Verranno cercati i riferimenti agli assembly nella directory
di lavoro e nella directory radice dell'unità C.

csc -lib:c:\ -reference:t2.dll t2.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-link (opzioni del compilatore C#)
02/11/2020 • 7 minutes to read • Edit Online

Indica al compilatore di rendere disponibili al progetto in fase di compilazione le informazioni sui tipi COM
presenti negli assembly specificati.

Sintassi
-link:fileList
// -or-
-l:fileList

Argomenti
fileList
Obbligatorio. Elenco di nomi di file di assembly delimitato da virgole. Se il nome del file contiene uno spazio,
racchiudere il nome tra virgolette.

Osservazioni
L'opzione -link consente di distribuire un'applicazione in cui sono incorporate informazioni sul tipo.
L'applicazione può quindi usare i tipi in un assembly di runtime che implementano le informazioni sul tipo
incorporate senza dovere far riferimento all'assembly di runtime. Se vengono pubblicate diverse versioni
dell'assembly di runtime, l'applicazione che contiene le informazioni sul tipo incorporate può funzionare con le
diverse versioni senza che sia necessaria la ricompilazione. Per un esempio, vedere Procedura dettagliata:
incorporamento dei tipi da assembly gestiti.
L'opzione -link è particolarmente utile quando si usa l'interoperabilità COM. È possibile incorporare tipi COM
in modo che per l'applicazione non sia più necessario un assembly di interoperabilità primario nel computer di
destinazione. L'opzione -link indica al compilatore di incorporare le informazioni sul tipo COM dall'assembly
di interoperabilità a cui si fa riferimento nel codice compilato risultante. Il tipo COM viene identificato dal valore
CLSID (GUID). Di conseguenza, l'applicazione può essere eseguita in un computer di destinazione in cui sono
stati installati gli stessi tipi COM con gli stessi valori CLSID. Le applicazioni che consentono di automatizzare
Microsoft Office costituiscono un valido esempio. Poiché applicazioni come Office mantengono in genere lo
stesso valore CLSID in versioni diverse, l'applicazione può usare i tipi COM a cui si fa riferimento purché .NET
Framework 4 o versioni successive sia installato nel computer di destinazione e l'applicazione usi metodi,
proprietà o eventi inclusi nei tipi COM a cui si fa riferimento.
L'opzione -link incorpora solo interfacce, strutture e delegati. L'incorporamento di classi COM non è
supportato.

NOTE
Quando si crea un'istanza di un tipo COM incorporato nel codice, è necessario creare l'istanza usando l'interfaccia
appropriata. Il tentativo di creare un'istanza di un tipo COM incorporato usando la coclasse genera un errore.

Per impostare l'opzione -link in Visual Studio, aggiungere un riferimento all'assembly e impostare la proprietà
Embed Interop Types su true . Il valore predefinito della proprietà Embed Interop Types è false .
Se si collega a un assembly COM (assembly A) che fa riferimento a un altro assembly COM (assembly B), è
necessario eseguire il collegamento anche all'assembly B se si verifica una delle condizioni seguenti:
Un tipo dell'assembly A eredita da un tipo o implementa un'interfaccia dall'assembly B.
Viene richiamato un campo, una proprietà, un evento o un metodo che presenta un tipo restituito o un
tipo di parametro proveniente dall'assembly B.
Analogamente all'opzione del compilatore -Reference , l' -link opzione del compilatore usa il file di risposta
CSC. rsp, che fa riferimento a assembly .NET di uso frequente. Usare l'opzione del compilatore -noconfig se non
si vuole che il compilatore usi il file csc.rsp.
La forma breve di -link è -l .

Generics e tipi incorporati


Nelle sezioni seguenti vengono descritte le limitazioni all'uso di tipi generici in applicazioni che incorporano tipi
di interoperabilità.
Interfacce generiche
Le interfacce generiche incorporate da un assembly di interoperabilità non possono essere usate, come illustrato
nell'esempio seguente.

// The following code causes an error if ISampleInterface is an embedded interop type.


ISampleInterface<SampleType> sample;

Tipi con parametri generici


I tipi che hanno un parametro generico il cui tipo è incorporato da un assembly di interoperabilità non possono
essere usati se tale tipo proviene da un assembly esterno. Tale restrizione non si applica tuttavia alle interfacce.
Si consideri ad esempio l'interfaccia Range definita nell'assembly Microsoft.Office.Interop.Excel. Se una libreria
incorpora tipi di interoperabilità dall'assembly Microsoft.Office.Interop.Excel ed espone un metodo che
restituisce un tipo generico che ha un parametro il cui tipo è l'interfaccia Range, il metodo deve restituire
un'interfaccia generica, come illustrato nell'esempio di codice seguente.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;

public class Utility


{
// The following code causes an error when called by a client assembly.
public List<Range> GetRange1()
{
return null;
}

// The following code is valid for calls from a client assembly.


public IList<Range> GetRange2()
{
return null;
}
}

Nell'esempio seguente, il codice client può chiamare il metodo che restituisce l'interfaccia generica IList senza
errori.
public class Client
{
public void Main()
{
Utility util = new Utility();

// The following code causes an error.


List<Range> rangeList1 = util.GetRange1();

// The following code is valid.


List<Range> rangeList2 = (List<Range>)util.GetRange2();
}
}

Esempio
Nel codice riportato di seguito viene compilato il file di origine OfficeApp.cs e viene fatto riferimento agli
assembly di COMData1.dll e COMData2.dll per generare OfficeApp.exe .

csc -link:COMData1.dll,COMData2.dll -out:OfficeApp.exe OfficeApp.cs

Vedere anche
Opzioni del compilatore C#
Procedura dettagliata: incorporamento dei tipi da assembly gestiti
-reference (opzioni del compilatore C#)
-noconfig (opzioni del compilatore C#)
Compilazione dalla riga di comando con csc.exe
Cenni preliminari sull'interoperabilità
-linkresource (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

Crea un collegamento a una risorsa .NET nel file di output. Il file di risorse non viene aggiunto al file di output.
Questa opzione è diversa dall'opzione -resource, che invece incorpora un file di risorse nel file di output.

Sintassi
-linkresource:filename[,identifier[,accessibility-modifier]]

Argomenti
filename
Il file di risorse .NET in cui si desidera eseguire il collegamento dall'assembly.
identifier (facoltativo)
Nome logico della risorsa, usato per caricare la risorsa stessa. L'impostazione predefinita corrisponde al nome
del file.
accessibility-modifier (facoltativo)
Accessibilità della risorsa: public o private. Il valore predefinito è public.

Osservazioni
Per impostazione predefinita, le risorse collegate sono pubbliche nell'assembly quando vengono create con il
compilatore C#. Per renderle private, specificare private come modificatore di accessibilità. Non è consentito
alcun modificatore diverso da public o private .
Per -linkresource è necessaria un'opzione -target diversa da -target:module .
Se filename è un file di risorse .NET creato, ad esempio, da Resgen.exe o nell'ambiente di sviluppo, è possibile
accedervi con membri nello System.Resources spazio dei nomi. Per altre informazioni, vedere
System.Resources.ResourceManager. Per tutte le altre risorse, per accedere alla risorsa in fase di esecuzione
usare i metodi GetManifestResource della classe Assembly.
Il file specificato in filename può avere qualsiasi formato. Può ad esempio risultare opportuno rendere una DLL
nativa parte dell'assembly in modo che possa essere installata nella Global Assembly Cache e che sia possibile
accedervi dal codice gestito nell'assembly. Nel secondo degli esempi seguenti viene illustrato come effettuare
questa operazione. È possibile eseguire la stessa operazione in Assembly Linker. Nel terzo degli esempi seguenti
viene illustrato come effettuare questa operazione. Per altre informazioni, vedere Al.exe (Assembly Linker) e Uso
di assembly e della Global Assembly Cache.
-linkres rappresenta la versione abbreviata di -linkresource .
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Esempio
Compilare in.cs e stabilire il collegamento al file di risorse rf.resource :
csc -linkresource:rf.resource in.cs

Esempio
Compilare A.cs in una DLL, creare un collegamento a una DLL N.dll nativa e inserire l'output nella Global
Assembly Cache. In questo esempio i file A.dll e N.dll verranno memorizzati entrambi nella Global Assembly
Cache.

csc -linkresource:N.dll -t:library A.cs


gacutil -i A.dll

Esempio
In questo esempio viene eseguita la stessa operazione descritta in precedenza, ma vengono usate le opzioni di
Assembly Linker.

csc -t:module A.cs


al -out:A.dll A.netmodule -link:N.dll
gacutil -i A.dll

Vedere anche
Opzioni del compilatore C#
Al.exe (assembly linker)
Utilizzo di assembly e della Global Assembly Cache
Gestione delle proprietà di progetti e soluzioni
-main (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa opzione specifica la classe che contiene il punto di ingresso al programma, quando più classi
contengono un metodo Main .

Sintassi
-main:class

Argomenti
class
Il tipo contenente il metodo Main .
Il nome della classe specificato deve essere completo. Deve includere lo spazio dei nomi completo che contiene
la classe, seguito dal nome della classe. Ad esempio, quando il metodo Main è all'interno della classe Program
nello spazio dei nomi MyApplication.Core , l'opzione del compilatore deve essere
-main:MyApplication.Core.Program .

Osservazioni
Se la compilazione include più tipi con un metodo Main, è possibile specificare il tipo che contiene il metodo
Main che si vuole usare come punto di ingresso nel programma.
Questa opzione è utilizzabile quando si compila un file con estensione exe .
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Oggetto di avvio .
Per impostare questa opzione del compilatore a livello di codice, vedere StartupObject.
Per impostare questa opzione del compilatore modificando manualmente il file con estensione csproj
Per impostare questa opzione, è possibile modificare il file con estensione csproj e aggiungere un elemento
StartupObject nella PropertyGroup sezione. Ad esempio:

<PropertyGroup>
...
<StartupObject>MyApplication.Core.Program</StartupObject>
</PropertyGroup>

Esempio
Compilare t2.cs e t3.cs specificando che il metodo Main si trova in Test2 :

csc t2.cs t3.cs -main:Test2


Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-moduleassemblyname (opzione del compilatore
C#)
28/01/2021 • 2 minutes to read • Edit Online

Specifica l'assembly i cui tipi non pubblici sono accessibili da un file con estensione NETMODULE.

Sintassi
-moduleassemblyname:assembly_name

Argomenti
assembly_name
Nome dell'assembly i cui tipi non pubblici sono accessibili dal file con estensione netmodule.

Osservazioni
È necessario usare -moduleassemblyname quando si compila un file con estensione netmodule, se sono
soddisfatte le condizioni seguenti:
Tramite il file con estensione netmodule deve essere possibile accedere a tipi non pubblici in un assembly
esistente.
L'utente conosce il nome dell'assembly in cui verrà compilato il file con estensione netmodule.
L'assembly esistente ha concesso l'accesso assembly Friend all'assembly in cui verrà compilato il file con
estensione netmodule.
Per altre informazioni sulla compilazione di un file con estensione netmodule, vedere -target:module (opzione
del compilatore C#).
Per altre informazioni sugli assembly Friend, vedere Assembly Friend .
L'opzione non è disponibile all'interno dell'ambiente di sviluppo, ma soltanto durante la compilazione dalla riga
di comando.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Esempio
In questo esempio viene generato un assembly con un tipo privato e viene concesso all'assembly Friend
l'accesso a un assembly denominato csman_an_assembly.
// moduleassemblyname_1.cs
// compile with: -target:library
using System;
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo ("csman_an_assembly")]

class An_Internal_Class
{
public void Test()
{
Console.WriteLine("An_Internal_Class.Test called");
}
}

Esempio
In questo esempio viene compilato un file con estensione netmodule tramite cui si accede a un tipo non
pubblico nel file moduleassemblyname_1.dll dell'assembly. Sapendo che questo file con estensione netmodule
verrà compilato in un assembly denominato csman_an_assembly, è possibile specificare -
moduleassemblyname per consentire al file con estensione netmodule di accedere a tipi non pubblici
nell'assembly che ha concesso all'assembly Friend l'accesso a csman_an_assembly.

// moduleassemblyname_2.cs
// compile with: -moduleassemblyname:csman_an_assembly -target:module -reference:moduleassemblyname_1.dll
class B {
public void Test() {
An_Internal_Class x = new An_Internal_Class();
x.Test();
}
}

Esempio
In questo esempio di codice viene compilato l'assembly csman_an_assembly, facendo riferimento all'assembly e
al file con estensione netmodule compilati in precedenza.

// csman_an_assembly.cs
// compile with: -addmodule:moduleassemblyname_2.netmodule -reference:moduleassemblyname_1.dll
class A {
public static void Main() {
B bb = new B();
bb.Test();
}
}

An_Internal_Class.Test called

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-noconfig (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -noconfig indica al compilatore di non eseguire la compilazione con il file CSC. rsp, che si trova in e
viene caricato dalla stessa directory del file csc.exe.

Sintassi
-noconfig

Osservazioni
Il file CSC. rsp fa riferimento a tutti gli assembly forniti con .NET Framework. I riferimenti effettivi inclusi
nell'ambiente di sviluppo di Visual Studio .NET dipendono dal tipo di progetto.
È possibile modificare il file CSC. rsp e specificare altre opzioni del compilatore da includere in ogni
compilazione dalla riga di comando con csc.exe (eccetto l'opzione -noconfig ).
Le opzioni passate al comando csc verranno elaborate nel compilatore per ultime. Le opzioni della riga di
comando eseguiranno pertanto l'override delle impostazioni relative alle stesse opzioni nel file csc.rsp.
Se non si vuole che il compilatore cerchi e usi le impostazioni del file CSC. rsp, specificare -noconfig .
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-nologo (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -nologo impedisce la visualizzazione del banner di accesso quando il compilatore viene avviato e
visualizza messaggi informativi durante la compilazione.

Sintassi
-nologo

Osservazioni
L'opzione non è disponibile all'interno dell'ambiente di sviluppo, ma soltanto durante la compilazione dalla riga
di comando.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-nostdlib (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

-nostdlib impedisce l'importazione di mscorlib.dll, che definisce l'intero spazio dei nomi di sistema.

Sintassi
-nostdlib[+ | -]

Osservazioni
Usare questa opzione se si vuole definire o creare uno spazio dei nomi e oggetti System personalizzati.
Se non si specifica -nostdlib , mscorlib.dll viene importata nel programma (equivale a specificare -nostdlib- ).
Specificare -nostdlib equivale a specificare -nostdlib+ .
Per impostare questa opzione del compilatore in Visual Studio

NOTE
Le istruzioni seguenti si applicano solo a Visual Studio 2015 (e versioni precedenti). Non fare riferimento mscorlib.dll
proprietà di compilazione non esiste nelle versioni più recenti di Visual Studio.

1. Aprire la pagina Proprietà del progetto.


2. Fare clic sulla pagina di proprietà Compilazione .
3. Fare clic sul pulsante Avanzate .
4. Modificare la proprietà Ometti riferimenti a libreria standard (mscorlib.dll) .
Per impostare l'opzione del compilatore a livello di codice
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere NoStdLib.

Vedere anche
Opzioni del compilatore C#
-nowarn (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -nowarn impedisce al compilatore di visualizzare uno o più avvisi. Separare più numeri di avviso con
una virgola.

Sintassi
-nowarn:number1[,number2,...]

Argomenti
number1 , number2
Il numero o i numeri degli avvisi che il compilatore non deve visualizzare.

Osservazioni
Specificare solo la parte numerica dell'identificatore dell'avviso. Ad esempio, per eliminare l'avviso CS0028 è
possibile specificare -nowarn:28 .
Il compilatore ignorerà automaticamente i numeri di avviso passati a -nowarn validi nelle versioni precedenti
ma rimossi dal compilatore. Ad esempio, CS0679 era valido nel compilatore in Visual Studio .NET 2002 ma è
stato rimosso successivamente.
Gli avvisi seguenti non possono essere eliminati dall'opzione -nowarn :
Avviso del compilatore (livello 1) CS2002
Avviso del compilatore (livello 1) CS2023
Avviso del compilatore (livello 1) CS2029
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto. Per informazioni dettagliate, vedere Pagina Compilazione,
Creazione progetti (C#).
2. Fare clic sulla pagina della proprietà Compilazione .
3. Modificare la proprietà Non visualizzare avvisi .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere DelaySign.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
Errori del compilatore C#
-nowin32manifest (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Usare l'opzione -nowin32manifest per indicare al compilatore di non incorporare un manifesto


dell'applicazione nel file eseguibile.

Sintassi
-nowin32manifest

Osservazioni
Quando viene usata questa opzione, l'applicazione è soggetta a virtualizzazione in Windows Vista a meno che
non venga specificato un manifesto dell'applicazione in un file di risorsa Win32 file o durante una istruzione di
compilazione successiva.
In Visual Studio impostare l'opzione nella pagina delle proprietà dell'applicazione selezionando l'opzione
Crea applicazione senza manifesto nell'elenco a discesa Manifesto . Per altre informazioni, vedere pagina
applicazione, Progettazione progetti (C#).
Per altre informazioni sulla creazione di manifesti, vedere /win32manifest (opzioni del compilatore C#).

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-Nullable (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -Nullable consente di specificare il contesto Nullable.

Sintassi
-nullable[+ | -]
-nullable:{enable | disable | warnings | annotations}

Argomenti
+ | -
Se + si specifica, o -Nullable , il compilatore Abilita il contesto Nullable. - Se si specifica, che è attivo se non si
specifica -Nullable , Disabilita il contesto Nullable.
enable | disable | warnings | annotations
Specifica l'opzione di contesto Nullable. Simile a + o - , per abilitare e disabilitare, ma consente una maggiore
granularità della specificità del contesto Nullable. L' enable argomento, che è attivo come se si specifica -
Nullable , Abilita il contesto Nullable. Se disable si specifica, il contesto nullable viene disabilitato. Quando si
specifica l' warnings argomento, -Nullable: Warnings , il contesto di avviso Nullable è abilitato. Quando si
specifica l' annotations argomento Nullable: Annotations , il contesto dell'annotazione Nullable è abilitato.

Commenti
L'analisi del flusso viene utilizzata per dedurre il supporto dei valori Null delle variabili all'interno del codice
eseguibile. Il supporto di valori null derivati di una variabile è indipendente dal supporto di valori null dichiarato
della variabile. Le chiamate al metodo vengono analizzate anche quando sono omesse in modo condizionale. Ad
esempio, Debug.Assert in modalità di rilascio.
La chiamata dei metodi annotati con gli attributi seguenti influirà anche sull'analisi del flusso:
Condizioni preliminari semplici: AllowNullAttribute e DisallowNullAttribute
Semplici post-condizioni: MaybeNullAttribute e NotNullAttribute
Condizioni postali condizionali: MaybeNullWhenAttribute e NotNullWhenAttribute
DoesNotReturnIfAttribute (ad esempio, DoesNotReturnIf(false) per Debug.Assert ) e
DoesNotReturnAttribute
NotNullIfNotNullAttribute
Post-condizioni del membro: MemberNotNullAttribute(String) e MemberNotNullAttribute(String[])
IMPORTANT
Il contesto Nullable globale non è applicabile per i file di codice generati. Indipendentemente da questa impostazione, il
contesto Nullable è disabilitato per qualsiasi file di origine contrassegnato come generato. Sono disponibili quattro modi
per contrassegnare un file come generato:
1. In. EditorConfig specificare generated_code = true in una sezione che si applica a tale file.
2. Inserire <auto-generated> o <auto-generated/> in un commento all'inizio del file. Può trovarsi in qualsiasi riga del
commento, ma il blocco di commento deve essere il primo elemento del file.
3. Avviare il nome file con TemporaryGeneratedFile_
4. Terminare il nome del file con estensione designer.cs, generated.cs, g.cs o g.i.cs.
I generatori possono acconsentire esplicitamente usando la direttiva per il #nullable preprocessore.

Per impostare questa opzione del compilatore in un progetto


Modificare il file con estensione csproj per aggiungere il <Nullable> tag all'interno di una
Project/PropertyGroup gerarchia:

<Project Sdk="...">

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

Vedi anche
Opzioni del compilatore C#
#nullable direttiva per il preprocessore
Tipi riferimento nullable
-optimize (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -optimize abilita o disabilita le ottimizzazioni eseguite dal compilatore per ridurre le dimensioni del
file di output e aumentarne la velocità e l'efficienza.

Sintassi
-optimize[+ | -]

Osservazioni
-optimize comunica poi a Common Language Runtime di ottimizzare il codice in fase di esecuzione.
Per impostazione predefinita, le ottimizzazioni sono disabilitate. Per abilitarle, specificare -optimize+ .
Durante la compilazione di un modulo per l'uso da parte di un assembly, specificare per -optimize le stesse
impostazioni usate per l'assembly.
-o è la versione abbreviata di -optimize .
Le opzioni -optimize e -debug possono essere usate in modo combinato.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Modificare la proprietà Ottimizza codice .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere Optimize.

Esempio
Compilare t2.cs e abilitare le ottimizzazioni del compilatore:

csc t2.cs -optimize

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-out (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -out specifica il nome del file di output.

Sintassi
-out:filename

Argomenti
filename
Il nome del file di output creato dal compilatore.

Osservazioni
Nella riga di comando è possibile specificare più file di output per la compilazione. Dopo l'opzione -out è
prevista la presenza di uno o più file di codice sorgente. Tutti i file di codice sorgente verranno quindi compilati
nel file di output specificato con l'opzione -out .
Specificare il nome completo e l'estensione del file che si vuole creare.
Se non si specifica il nome del file di output:
Un file con estensione exe corrisponderà al nome del file di codice sorgente che contiene il metodo Main .
Un file con estensione dll o netmodule corrisponderà al nome del primo file di codice sorgente.
Non è possibile usare per la compilazione di un file di output un file di codice sorgente già usato per compilare
un altro file di output nella stessa compilazione.
Quando si generano più file di output in una compilazione da riga di comando, tenere presente che solo uno dei
file di output può essere un assembly e che l'assembly può essere solo il primo file di output specificato (in
modo implicito o esplicito con l'opzione -out ).
I moduli prodotti durante una compilazione diventano file associati a un assembly prodotto anch'esso in fase di
compilazione. Per visualizzare il manifesto dell'assembly e i file associati, usare ildasm.exe.
L'opzione del compilatore -out è necessaria perché un file eseguibile sia la destinazione di un assembly Friend.
Per altre informazioni, vedere Assembly Friend.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Nome assembly .
Per impostare l'opzione del compilatore a livello di codice: OutputFileName è una proprietà di sola lettura
caratterizzata dalla combinazione del tipo di progetto (file eseguibile, libreria e così via) e del nome
dell'assembly. Per impostare il nome del file di output sarà necessario modificare una o entrambe queste
proprietà.
Esempio
Per compilare t.cs e creare il file di output t.exe , nonché per generare t2.cs e creare il file di output del
modulo mymodule.netmodule :

csc t.cs -out:mymodule.netmodule -target:module t2.cs

Vedere anche
Opzioni del compilatore C#
Assembly Friend
Gestione delle proprietà di progetti e soluzioni
-pathmap (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione del compilatore -pathmap specifica come eseguire il mapping di percorsi fisici ai nomi di percorso di
origine restituiti dal compilatore.

Sintassi
-pathmap:path1=sourcePath1,path2=sourcePath2

Argomenti
path1 Percorso completo per i file di origine nell'ambiente corrente
sourcePath1 Percorso di origine con cui sostituire path1 in tutti i file di output.
Per specificare più percorsi di origine mappati, separarli con una virgola.

Osservazioni
Il compilatore scrive il percorso di origine nell'output per i motivi seguenti:
1. Il percorso di origine viene sostituito per un argomento quando si applica CallerFilePathAttribute a un
parametro facoltativo.
2. Il percorso di origine è incorporato in un file PDB.
3. Il percorso del file PDB è incorporato in un file PE (eseguibile portabile).
Questa opzione esegue il mapping di ogni percorso fisico nel computer in cui viene eseguito il compilatore in un
percorso corrispondente che deve essere scritto nei file di output.

Esempio
Compilare t.cs nella directory c:\work\tests ed eseguire il mapping di tale directory a \publish nell'output:

csc -pathmap:C:\work\tests=\publish t.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-pdb (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione del compilatore -pdb consente di specificare il nome e il percorso del file dei simboli di debug.

Sintassi
-pdb:filename

Argomenti
filename
Nome e percorso del file di simboli di debug.

Osservazioni
Quando si specifica -debug (opzioni del compilatore C#), il compilatore crea nella stessa directory in cui verrà
creato il file di output (con estensione exe o dll) un file con estensione pdb il cui nome è identico a quello del file
di output.
-pdb consente di specificare un nome e un percorso non predefiniti per il file con estensione pdb.
Questa opzione del compilatore non può essere impostata nell'ambiente di sviluppo di Visual Studio, né
modificata a livello di codice.

Esempio
Compilare t.cs e creare un file con estensione pdb denominato tt.pdb:

csc -debug -pdb:tt t.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-platform (opzioni del compilatore C#)
28/01/2021 • 4 minutes to read • Edit Online

Specifica la versione di Common Language Runtime (CLR) in grado di eseguire l'assembly.

Sintassi
-platform:string

Parametri
string
anycpu (impostazione predefinita), anycpu32bitpreferred, ARM, x64, x86 o Itanium.

Osservazioni
anycpu (valore predefinito) consente di compilare l'assembly in modo da poter essere eseguito su
qualsiasi piattaforma. L'applicazione viene eseguita come processo a 64 bit, quando possibile, e tramite
essa viene eseguito il fallback a 32 bit solo quando questa modalità è disponibile.
anycpu32bitpreferred consente di compilare l'assembly in modo da poter essere eseguito su qualsiasi
piattaforma. L'applicazione viene eseguita in modalità a 32 bit nei sistemi che supportano sia le
applicazioni a 64 bit sia quelle a 32 bit. È possibile specificare questa opzione solo per i progetti destinati
a .NET Framework 4,5 o versione successiva.
ARM compila l'assembly in modo da poter essere eseguito su un computer con processore Advanced
RISC Machine (ARM).
ARM64 compila l'assembly in modo che possa essere eseguito da CLR a 64 bit in un computer con un
processore Advanced RISC Machine (ARM) che supporta il set di istruzioni A64.
x64 compila l'assembly in modo che possa essere eseguito da CLR a 64 bit in un computer che supporta
il set di istruzioni AMD64 o EM64T.
x86 compila l'assembly in modo che possa essere eseguito da CLR a 32 bit, compatibile con x86.
Itanium compila l'assembly in modo che possa essere eseguito da CLR a 64 bit in un computer dotato di
processore Itanium.
In un sistema operativo Windows a 64 bit:
Gli assembly compilati con -platform:x86 vengono eseguiti dalla versione di Common Language
Runtime a 32 bit in WOW64.
Una DLL compilata con l'opzione -platform:anycpu viene eseguita dallo stesso Common Language
Runtime del processo in cui viene caricata.
Gli eseguibili compilati con -platform:anycpu vengono eseguiti dalla versione di Common Language
Runtime a 64 bit.
Gli eseguibili compilati con l'opzione -platform:anycpu32bitpreferred vengono eseguiti dalla
versione di Common Language Runtime a 32 bit.
L'impostazione anycpu32bitpreferred è valida solo per eseguibile (. File EXE) e richiede .NET Framework 4,5 o
versione successiva.
Per altre informazioni sullo sviluppo di un'applicazione da eseguire su un sistema operativo Windows a 64 bit,
vedere Applicazioni a 64 bit.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Modificare la proprietà piattaforma di destinazione e, per i progetti destinati a .NET Framework 4,5 o
versione successiva, selezionare o deselezionare la casella di controllo preferisci 32 bit .

NOTE
-platform non è disponibile nell'ambiente di sviluppo in Visual C# Express.

Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere PlatformTarget.

Esempio
L'esempio seguente illustra come usare l'opzione -platform per specificare che l'applicazione deve essere
eseguita dalla versione di Common Language Runtime a 64 bit in un sistema operativo Windows a 64 bit.

csc -platform:anycpu filename.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-preferreduilang (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

Utilizzando l'opzione del compilatore -preferreduilang , è possibile specificare la lingua in cui tramite il
compilatore C# viene visualizzato l'output, ad esempio i messaggi di errore.

Sintassi
-preferreduilang: language

Argomenti
language
Nome della lingua da usare per l'output del compilatore.

Osservazioni
È possibile utilizzare l'opzione del compilatore -preferreduilang per specificare la lingua che si desidera venga
utilizzata dal compilatore C# per i messaggi di errore e altri output della riga di comando. Se il Language Pack
della lingua non è installato, viene utilizzata l'impostazione della lingua del sistema operativo e non viene
segnalato alcun errore.

csc.exe -preferreduilang:ja-JP

Requisiti
Vedere anche
Opzioni del compilatore C#
-publicsign (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

Questa opzione indica al compilatore di applicare una chiave pubblica ma non firma effettivamente l'assembly.
L'opzione -publicsign imposta anche un bit nell'assembly che indica al runtime che il file è effettivamente
firmato.

Sintassi
-publicsign

Argomenti
No.

Osservazioni
L'opzione -publicsign richiede l'uso di -keyfile o -keycontainer. Le opzioni keyfile o keycontainer specificano
la chiave pubblica.
Le opzioni -publicsign e -delaysign si escludono a vicenda.
Talvolta denominata "firma falsa" o "firma OSS", la firma pubblica include la chiave pubblica in un assembly di
output e imposta il flag "firmato", ma non firma effettivamente l'assembly con una chiave privata. Ciò è utile per
progetti open source in cui le persone devono poter compilare assembly compatibili con gli assembly rilasciati
con "firma completa", ma non hanno accesso alla chiave privata usata per firmare gli assembly. Dato che
praticamente nessun consumer ha effettivamente la necessità di verificare se l'assembly è firmato
completamente, questi assembly compilati pubblicamente sono utilizzabili in quasi tutti gli scenari in cui
verrebbe usato un assembly con firma completa.
Per impostare questa opzione del compilatore in un file csproj
Aprire il file con estensione csproj per un progetto e aggiungere l'elemento seguente:

<PublicSign>true</PublicSign>

Vedere anche
Opzione -delaysign del compilatore C#
Opzione -keyfile del compilatore C#
Opzione -keycontainer del compilatore C#
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-recurse (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -recurse consente di compilare i file del codice sorgente in tutte le directory figlio della directory
specificata (dir) o della directory del progetto.

Sintassi
-recurse:[dir\]file

Argomenti
dir (facoltativo)
La directory in cui si vuole che abbia inizio la ricerca. Se non viene specificata alcuna directory, la ricerca avrà
inizio nella directory del progetto.
file
I file da cercare. È consentito l'utilizzo di caratteri jolly.

Osservazioni
L'opzione -recurse consente di compilare file di codice sorgente in tutte le directory figlio della directory
specificata ( dir ) o della directory del progetto.
È possibile usare caratteri jolly in un nome file per compilare tutti i file corrispondenti nella directory del
progetto senza usare -recurse .
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Esempio
Compila tutti i file C# della directory corrente:

csc *.cs

Compila tutti i file C# della directory dir1\dir2 e di eventuali sottodirectory e genera dir2.dll:

csc -target:library -out:dir2.dll -recurse:dir1\dir2\*.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-reference (opzioni del compilatore C#)
28/01/2021 • 6 minutes to read • Edit Online

Con l'opzione -reference il compilatore importa nel progetto corrente le informazioni sui tipi public disponibili
nel file specificato e consente quindi di fare riferimento ai metadati dai file di assembly specificati.

Sintassi
-reference:[alias=]filename
-reference:filename

Argomenti
filename
Nome di un file contenente il manifesto di un assembly. Per importare più file, specificare un'opzione -
reference per ogni file.
alias
Identificatore C# valido che rappresenta uno spazio dei nomi di primo livello contenente tutti gli spazi dei nomi
dell'assembly.

Osservazioni
Per importare da più file, includere un'opzione -reference per ogni file.
È necessario che i file importati contengano un manifesto e che il file di output sia stato compilato specificando
un'opzione -target diversa da -target:module.
-r rappresenta la versione abbreviata di -reference .
Usare -addmodule per importare metadati da un file di output che non contiene un manifesto dell'assembly.
Se si fa riferimento a un assembly (assembly A) che fa a sua volta riferimento a un secondo assembly (assembly
B), è necessario fare riferimento all'assembly B nei casi seguenti:
Se un tipo dell'assembly A eredita da un tipo o implementa un'interfaccia dell'assembly B.
Se si chiama un campo, una proprietà, un evento o un metodo che presenta un tipo restituito o un tipo di
parametro proveniente dall'assembly B.
Per specificare la directory in cui si trovano uno o più assembly cui si fa riferimento, usare -lib. Nell'argomento
dedicato a -lib sono indicate anche le directory in cui il compilatore ricerca gli assembly.
Perché il compilatore sia in grado di riconoscere un tipo in un assembly e non in un modulo, è necessario
imporre la risoluzione del tipo stesso. A questo scopo, è possibile definire un'istanza del tipo. La risoluzione dei
nomi dei tipi in un assembly può avvenire anche con altre modalità. Se, ad esempio, si eredita da un tipo in un
assembly, il nome del tipo sarà riconosciuto dal compilatore.
In alcuni casi è necessario fare riferimento a due versioni diverse dello stesso componente da un unico
assembly. A questo scopo, usare l'opzione di secondo livello alias dell'opzione -reference per ogni file, in modo
che sia possibile distinguere tra le due versioni. Questo alias verrà usato come qualificatore per il nome del
componente, quindi risolto nel componente stesso in uno dei file.
Per impostazione predefinita, viene usato il file di risposta csc (.rsp), che fa riferimento agli assembly .NET
Framework di uso comune. Usare -noconfig se non si vuole che il compilatore usi csc.rsp.

NOTE
In Visual Studio usare la finestra di dialogo Aggiungi riferimento . Per altre informazioni, vedere Procedura: Aggiungere
o rimuovere riferimenti mediante Gestione riferimenti. Per garantire un comportamento equivalente tra l'aggiunta di
riferimenti usare -reference e aggiungere riferimenti tramite la finestra di dialogo Aggiungi riferimento , impostando
la proprietà Incorpora tipi di interoperabilità su False per l'assembly che si vuole aggiungere. Per questa proprietà il
valore predefinito è True .

Esempio
L'esempio seguente illustra come usare la funzionalità extern alias.
Il file di origine viene compilato e i metadati vengono importati dai file grid.dll e grid20.dll , compilati in
precedenza. Nelle due DLL sono presenti versioni distinte dello stesso componente. Usare due opzioni -
reference con opzioni alias per compilare il file di origine. Le opzioni sono analoghe alle seguenti:

-reference:GridV1=grid.dll -reference:GridV2=grid20.dll

In questo modo vengono configurati gli alias esterni GridV1 e GridV2 , da usare nel programma tramite
un'istruzione extern :

extern alias GridV1;


extern alias GridV2;
// Using statements go here.

Una volta completate queste operazioni, è possibile fare riferimento al controllo griglia da grid.dll
aggiungendo il prefisso GridV1 al nome del controllo, come riportato di seguito:

GridV1::Grid

È anche possibile fare riferimento al controllo griglia da grid20.dll aggiungendo il prefisso GridV2 al nome
del controllo, come riportato di seguito:

GridV2::Grid

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-refout (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

L'opzione -refout specifica un percorso file in cui l'assembly di riferimento deve essere restituito come output.
Ciò si converte in metadataPeStream nell'API Emit. Questa opzione corrisponde alla proprietà del progetto
ProduceReferenceAssembly di MSBuild.

Sintassi
-refout:filepath

Argomenti
filepath Il percorso del file dell'assembly di riferimento. In genere corrisponde a quello dell'assembly primario.
La convenzione consigliata (usata da MSBuild) consiste nell'inserire l'assembly di riferimento in una
sottocartella "ref /" relativa all'assembly primario.

Osservazioni
Gli assembly di riferimento sono un tipo speciale di assembly che contiene solo la quantità minima di metadati
necessaria per rappresentare la superficie dell'API pubblica della libreria. Sono incluse le dichiarazioni per tutti i
membri significativi quando si fa riferimento a un assembly negli strumenti di compilazione, ma si escludono
tutte le implementazioni e le dichiarazioni dei membri privati che non hanno alcun impatto osservabile sul
contratto API. Per ulteriori informazioni, vedere assembly di riferimento nella Guida di .NET.
Le -refout Opzioni e si escludono a -refonly vicenda.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-refonly (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -refonly indica che l'output primario deve essere un assembly di riferimento, anziché un assembly di
implementazione. Il parametro -refonly disabilita automaticamente l'output dei file PDB poiché non è possibile
eseguire gli assembly di riferimento. Questa opzione corrisponde alla proprietà del progetto
ProduceOnlyReferenceAssembly di MSBuild.

Sintassi
-refonly

Osservazioni
Gli assembly di riferimento sono un tipo speciale di assembly che contiene solo la quantità minima di metadati
necessaria per rappresentare la superficie dell'API pubblica della libreria. Sono incluse le dichiarazioni per tutti i
membri significativi quando si fa riferimento a un assembly negli strumenti di compilazione, ma si escludono
tutte le implementazioni e le dichiarazioni dei membri privati che non hanno alcun impatto osservabile sul
contratto API. Per ulteriori informazioni, vedere assembly di riferimento nella Guida di .NET.
Le -refonly Opzioni e si escludono a -refout vicenda.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-resource (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Incorpora la risorsa specificata nel file di output.

Sintassi
-resource:filename[,identifier[,accessibility-modifier]]

Argomenti
filename
Il file di risorse .NET che si desidera incorporare nel file di output.
identifier (facoltativo)
Nome logico della risorsa, usato per caricare la risorsa stessa. L'impostazione predefinita corrisponde al nome
del file.
accessibility-modifier (facoltativo)
Accessibilità della risorsa: public o private. Il valore predefinito è public.

Osservazioni
Usare -linkresource per collegare una risorsa a un assembly senza aggiungere il file di risorse al file di output.
Per impostazione predefinita, le risorse sono pubbliche nell'assembly quando vengono create tramite il
compilatore C#. Per renderle private, specificare private come modificatore di accessibilità. Non è consentita
alcuna accessibilità diversa da public o private .
Se filename è un file di risorse .NET creato, ad esempio, da Resgen.exe o nell'ambiente di sviluppo, è possibile
accedervi con membri nello System.Resources spazio dei nomi. Per altre informazioni, vedere
System.Resources.ResourceManager. Per tutte le altre risorse, per accedere alla risorsa in fase di esecuzione
usare i metodi GetManifestResource della classe Assembly.
-res rappresenta la versione abbreviata di -resource .
L'ordine delle risorse nel file di output è determinato dall'ordine specificato nella riga di comando.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aggiungere un file di risorse al progetto.
2. Selezionare il file che si vuole incorporare in Esplora soluzioni .
3. Selezionare Azione di compilazione per il file nella finestra Proprietà .
4. Impostare Azione di compilazione su Risorsa incorporata .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere BuildAction.

Esempio
Compilare in.cs e associare il file di risorse rf.resource :
csc -resource:rf.resource in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-subsystemversion (opzioni del compilatore C#)
02/11/2020 • 3 minutes to read • Edit Online

Specifica la versione minima del sottosistema in cui è possibile eseguire il file eseguibile generato,
determinando le versioni di Windows in cui è possibile eseguire il file eseguibile. In genere, questa opzione
assicura che il file eseguibile possa sfruttare le funzionalità di protezione che non sono disponibili con le versioni
precedenti di Windows.

NOTE
Per specificare il sottosistema stesso, usare l'opzione del compilatore -target.

Sintassi
-subsystemversion:major.minor

Parametri
major.minor

Versione minima richiesta per il sottosistema, espressa in una notazione del punto per le versioni principali e
secondarie. Ad esempio, è possibile specificare che un'applicazione non può essere eseguita su un sistema
operativo precedente a Windows 7 Se si imposta il valore di questa opzione su 6.01, come descritto nella tabella
più avanti in questo argomento. È necessario specificare i valori per major e minor come numeri interi.
Gli zeri iniziali della versione minor non modificano la versione, a differenza degli zeri finali. Ad esempio, 6.1 e
6.01 si fanno riferimento alla stessa versione, ma 6.10 fa riferimento a una versione diversa. È consigliabile
esprimere la versione secondaria con due cifre per evitare confusione.

Commenti
Nella tabella seguente sono elencate le versioni comuni del sottosistema di Windows.

VERSIO N E DI W IN DO W S VERSIO N E DEL SOT TO SIST EM A

Windows 2000 5,00

Windows XP 5,01

Windows Server 2003 5,02

Windows Vista 6,00

Windows 7 6.01

Windows Server 2008 6.01

Windows 8 6.02
Valori predefiniti
Il valore predefinito dell'opzione del compilatore -subsystemversion dipende dalle condizioni elencate di
seguito:
Il valore predefinito è 6.02 se è impostata un'opzione del compilatore nell'elenco seguente:
-target:appcontainerexe
-target:winmdobj
-platform:arm
Il valore predefinito è 6.00 se si usa MSBuild, se la destinazione è .NET Framework 4.5 e se non è stata
impostata una delle opzioni del compilatore specificate in precedenza in questo elenco.
Il valore predefinito è 4.00 se nessuna di queste condizioni è vera.

Impostazione di questa opzione


Per impostare l'opzione del compilatore -subsystemversion in Visual Studio, è necessario aprire il file con
estensione csproj e specificare un valore per la proprietà SubsystemVersion nel codice XML di MSBuild. Non è
possibile impostare questa opzione nell'IDE di Visual Studio. Per altre informazioni, vedere "Valori predefiniti"
più indietro in questo argomento o Proprietà di progetto MSBuild comuni.

Vedere anche
Opzioni del compilatore C#
-target (opzioni del compilatore C#)
02/11/2020 • 2 minutes to read • Edit Online

L'opzione del compilatore -target può essere specificata in uno dei formati seguenti:
-target:appcontainerexe
Per creare un file con estensione exe per le app di Windows 8. x Store.
-target:exe
Per creare un file con estensione exe.
-target:library
Per creare una libreria di codice.
-target:module
Per creare un modulo.
-target:winexe
Per creare un programma di Windows.
-target:winmdobj
Per creare un file con estensione winmdobj intermedio.
A meno che non si specifichi -target: module , -target causa la posizione di un manifesto dell'assembly .NET
Framework in un file di output. Per altre informazioni, vedere assembly in .NET e attributi comuni.
Il manifesto dell'assembly viene inserito nel primo file di output con estensione .exe della compilazione o nel
primo DLL, se non esiste alcun file di output .exe. Ad esempio, nella riga di comando seguente il manifesto verrà
inserito in 1.exe :

csc -out:1.exe t1.cs -out:2.netmodule t2.cs

Il compilatore crea solo un manifesto dell'assembly per ogni compilazione. Le informazioni su tutti i file in una
compilazione vengono inserite nel manifesto dell'assembly. Tutti i file di output tranne quelli creati con -target:
module possono contenere un manifesto dell'assembly. Quando si generano più file di output nella riga di
comando, è possibile creare solo un manifesto e deve essere inseriti nel primo file di output specificato nella
riga di comando. Indipendentemente da quale sia il primo file di output (-target:exe , -target:winexe , -
target:librar y o -target:module ), tutti gli altri file di output generati nella stessa compilazione devono essere
moduli (-target:module ).
Se si crea un assembly, è possibile indicare che tutto o parte del codice è conforme a CLS con l'attributo
CLSCompliantAttribute.

// target_clscompliant.cs
[assembly:System.CLSCompliant(true)] // specify assembly compliance

[System.CLSCompliant(false)] // specify compliance for an element


public class TestClass
{
public static void Main() {}
}

Per altre informazioni sull'impostazione di questa opzione del compilatore a livello di codice, vedere
OutputType.

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-subsystemversion (opzioni del compilatore C#)
-target:appcontainerexe (opzioni del compilatore
C#)
28/01/2021 • 2 minutes to read • Edit Online

Se si usa l'opzione del compilatore -target:appcontainerexe , il compilatore crea un file eseguibile Windows
(con estensione exe) che deve essere eseguito in un contenitore di app. Questa opzione equivale a -target:
winexe , ma è progettata per le app di Windows 8. x Store.

Sintassi
-target:appcontainerexe

Osservazioni
Per richiedere che l'app venga eseguita in un contenitore di app, questa opzione imposta un bit nel file
eseguibile di tipo PE. Quando questo bit è impostato, viene generato un errore se il metodo CreateProcess tenta
di avviare il file eseguibile all'esterno di un contenitore di app.
A meno che non si usi l'opzione -out, il nome del file di output corrisponderà al nome del file di input
contenente il metodo Main.
Quando si specifica questa opzione al prompt dei comandi, tutti i file fino alla successiva opzione -out o -target
vengono usati per creare il file eseguibile.
Per impostare l'opzione del compilatore nell'IDE
1. In Esplora soluzioni aprire il menu di scelta rapida per il progetto, quindi scegliere Proprietà .
2. Nell'elenco Tipo di output della scheda Applicazione scegliere Applicazione Windows Store .
Questa opzione è disponibile solo per i modelli di app di Windows 8. x Store.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Il seguente comando consente di compilare filename.cs in un file eseguibile di Windows che può essere
eseguito solo in un contenitore di app.

csc -target:appcontainerexe filename.cs

Vedere anche
-target (opzioni del compilatore C#)
-target: winexe (opzioni del compilatore C#)
Opzioni del compilatore C#
-target:exe (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -target:exe indica al compilatore di creare un file eseguibile (EXE), applicazione console.

Sintassi
-target:exe

Osservazioni
L'opzione -target:exe è attiva per impostazione predefinita. Il file eseguibile verrà creato con estensione .exe.
Usare -target:winexe per creare l'eseguibile di un programma Windows.
Se non diversamente specificato con l'opzione -out, il nome del file di output corrisponderà al nome del file di
input contenente il metodo Main.
Se specificato dalla riga di comando, tutti i file fino alla successiva opzione -out o -target:module vengono
usati per creare il file con estensione .exe
Un solo metodo Main è necessario nei file del codice sorgente che vengono compilati in un file con estensione
exe. L'opzione del compilatore -main consente di specificare la classe che contiene il metodo Main , nei casi in
cui il codice ha più di una classe con un metodo Main .
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Tipo di output .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Ciascuna delle righe di comando seguenti compilerà in.cs , creando in.exe :

csc -target:exe in.cs


csc in.cs

Vedere anche
-target (opzioni del compilatore C#)
Opzioni del compilatore C#
-target:library (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -target:librar y consente la creazione da parte del compilatore di una libreria di collegamento
dinamico (DLL), anziché di un file eseguibile (EXE).

Sintassi
-target:library

Osservazioni
La libreria creata avrà estensione DLL.
Se non diversamente specificato tramite l'opzione -out, il nome del file di output corrisponderà al nome del
primo file di input.
Quando specificato alla riga di comando, tutti i file fino alla successiva opzione -out o -target:module
vengono usati per creare il file con estensione dll.
Quando si compila un file con estensione dll, non è richiesto alcun metodo Main.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Tipo di output .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Compilare in in.cs creando in in.dll :

csc -target:library in.cs

Vedere anche
-target (opzioni del compilatore C#)
Opzioni del compilatore C#
-target:module (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Questa opzione indica al compilatore di non generare un manifesto dell'assembly.

Sintassi
-target:module

Osservazioni
Per impostazione predefinita, l'estensione del file di output creato eseguendo la compilazione con questa
opzione sarà netmodule.
Un file che non dispone di un manifesto dell'assembly non può essere caricato dal runtime .NET. È tuttavia
possibile incorporare un file di questo tipo nel manifesto di un assembly tramite -addmodule.
Se viene creato più di un modulo in un'unica compilazione, i tipi internal in un modulo saranno disponibili per
gli altri moduli nella compilazione. Se il codice di un modulo fa riferimento a tipi internal in un altro modulo, è
necessario incorporare entrambi i moduli in un manifesto dell'assembly tramite -addmodule .
La creazione di un modulo non è supportata nell'ambiente di sviluppo di Visual Studio.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Compilare in in.cs creando in in.netmodule :

csc -target:module in.cs

Vedere anche
-target (opzioni del compilatore C#)
Opzioni del compilatore C#
-target:winexe (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Con l'opzione -target:winexe il compilatore crea un file eseguibile (EXE), ovvero un programma di Windows.

Sintassi
-target:winexe

Osservazioni
Il file eseguibile verrà creato con estensione .exe. Un programma di Windows è un programma che fornisce
un'interfaccia utente dalla libreria .NET o con le API di Windows.
Usare -target:exe per creare un'applicazione console.
Se non diversamente specificato con l'opzione -out, il nome del file di output corrisponderà al nome del file di
input contenente il metodo Main.
Se specificato alla riga di comando, tutti i file fino alla successiva opzione -out o -target vengono usati per
creare il programma Windows.
Un solo metodo Main è necessario nei file del codice sorgente che vengono compilati in un file con estensione
exe. L'opzione -main consente di specificare la classe che contiene il metodo Main , nei casi in cui il codice ha più
di una classe con un metodo Main .
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Tipo di output .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Compilare in.cs in un programma di Windows:

csc -target:winexe in.cs

Vedere anche
-target (opzioni del compilatore C#)
Opzioni del compilatore C#
-target:winmdobj (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Se si usa l'opzione del compilatore -target:winmdobj , viene creato un file intermedio con estensione
winmdobj che è possibile convertire in un file binario di Windows Runtime (con estensione winmd). Il file con
estensione winmd può quindi essere utilizzato dai programmi C++ e JavaScript, oltre ai programmi di linguaggi
gestiti.

Sintassi
-target:winmdobj

Osservazioni
L'impostazione winmdobj segnala al compilatore che è richiesto un modulo intermedio. In risposta, Visual
Studio consente di compilare la libreria di classi C# come file con estensione winmdobj. Il file con estensione
winmdobj può quindi essere inserito dallo strumento di esportazione WinMDExp per produrre un file di
metadati Windows (winmd). Il file con estensione winmd contiene il codice della libreria originale e i metadati di
WinMD utilizzati da JavaScript o C++ e da Windows Runtime.
L'output di un file compilato usando l'opzione del compilatore -target:winmdobj è progettato per essere usato
solo come input dell'utilità di esportazione WimMDExp. Al file con estensione winmdobj non viene fatto
riferimento direttamente.
A meno che non si usi l'opzione -out, il nome del file di output corrisponderà al nome del primo file di input.
Non è richiesto un metodo principale.
Se si specifica l'opzione -target:winmdobj al prompt dei comandi, tutti i file fino alla successiva opzione -out o -
target:module vengono usati per creare il programma per Windows.
Per impostare l'opzione del compilatore nell'IDE di Visual Studio per un'applicazione Windows Store
1. In Esplora soluzioni aprire il menu di scelta rapida per il progetto, quindi scegliere Proprietà .
2. Scegliere la scheda Applicazione .
3. Nell'elenco Tipo di output scegliere File WinMD .
L'opzione file WinMD è disponibile solo per i modelli di app di Windows 8. x Store.
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere OutputType.

Esempio
Il seguente comando consente di compilare filename.cs in un file intermedio con estensione winmdobj.

csc -target:winmdobj filename.cs

Vedere anche
-target (opzioni del compilatore C#)
Opzioni del compilatore C#
-unsafe (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione del compilatore -unsafe consente la compilazione del codice che usa la parola chiave unsafe.

Sintassi
-unsafe

Osservazioni
Per altre informazioni sul codice unsafe, vedere Codice unsafe e puntatori.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Selezionare la casella di controllo Consenti codice unsafe .
Per aggiungere questa opzione in un file csproj
Aprire il file con estensione csproj per un progetto e aggiungere gli elementi seguenti:

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere
AllowUnsafeBlocks.

Esempio
Compilare in.cs per la modalità unsafe:

csc -unsafe in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-utf8output (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -utf8output visualizza l'output del compilatore tramite la codifica UTF-8.

Sintassi
-utf8output

Osservazioni
In alcune configurazioni internazionali, l'output del compilatore non può essere visualizzato correttamente nella
console. Con queste configurazioni usare -utf8output e reindirizzare l'output del compilatore in un file.
Questa opzione del compilatore non è disponibile in Visual Studio e non può essere modificata a livello di
codice.

Vedere anche
Opzioni del compilatore C#
-warn (opzioni del compilatore C#)
28/01/2021 • 3 minutes to read • Edit Online

L'opzione -warn specifica il livello di avviso da visualizzare nel compilatore.

Sintassi
-warn:option

Argomenti
option
Livello di avviso da visualizzare per la compilazione: i numeri più bassi mostrano solo gli avvisi con un livello di
gravità elevato, i valori più alti mostrano altri avvisi. Il valore deve essere zero o un numero intero positivo:

L IVEL LO AVVISI SIGN IF IC ATO

0 Disattiva l'emissione di tutti i messaggi di avviso.

1 Visualizza i messaggi di avviso gravi.

2 Visualizza gli avvisi di livello 1 oltre ad alcuni avvisi meno


gravi, ad esempio gli avvisi relativi ai membri di classi
nascosti.

3 Visualizza gli avvisi di livello 2 oltre ad alcuni avvisi meno


gravi, ad esempio gli avvisi relativi alle espressioni che
restituiscono sempre true o false .

4 (impostazione predefinita) Visualizza tutti gli avvisi di livello 3 oltre ad avvisi informativi.

5 Visualizza gli avvisi di livello 4 più avvisi aggiuntivi del


compilatore forniti con C# 9,0.

Maggiore di 5 Qualsiasi valore maggiore di 5 verrà considerato come 5. In


genere si inserisce un valore di grandi dimensioni arbitrario
(ad esempio, 9999 ) per assicurarsi di avere sempre tutti gli
avvisi se il compilatore viene aggiornato con nuovi livelli di
avviso.

Osservazioni
Per ottenere informazioni su un errore o un avviso, è possibile cercare il codice di errore nell'indice della Guida.
Per altri modi per ottenere informazioni su un errore o un avviso, vedere Errori del compilatore C#.
Usare -warnaserror per considerare tutti gli avvisi come errori. Usare -nowarn per disabilitare avvisi specifici.
-w è la versione abbreviata di -warn .
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Modificare la proprietà Livello avvisi .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere WarningLevel.

Esempio
Compilare in.cs e fare in modo che il compilatore visualizzi solo gli avvisi di livello 1:

csc -warn:1 in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-warnaserror (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -warnaserror+ considera tutti gli avvisi come errori

Sintassi
-warnaserror[+ | -][:warning-list]

Osservazioni
I messaggi che in genere vengono segnalati come avvisi sono invece segnalati come errori e il processo di
compilazione viene interrotto (non viene compilato alcun file di output).
Per impostazione predefinita, l'opzione -warnaserror- è attiva e fa in modo che gli avvisi non impediscano la
generazione di un file di output. -warnaserror , che equivale a -warnaserror+ , fa in modo che gli avvisi
vengano considerati errori.
Facoltativamente, se si vuole che solo determinati avvisi vengano considerati errori, è possibile specificare un
elenco delimitato da virgole di numeri di avvisi da considerare errori. Il set di tutti gli avvisi relativi al supporto
di valori null può essere specificato con la sintassi abbreviata Nullable .
Usare -warn per specificare il livello degli avvisi da visualizzare nel compilatore. Usare -nowarn per disabilitare
avvisi specifici.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina della proprietà Compilazione .
3. Modificare la proprietà Considera gli avvisi come errori .
Per impostare questa opzione del compilatore a livello di codice, vedere TreatWarningsAsErrors.

Esempio
Compilare in.cs e fare in modo che il compilatore non visualizzi gli avvisi:

csc -warnaserror in.cs


csc -warnaserror:642,649,652,nullable in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-win32icon (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

Con l'opzione -win32icon viene inserito un file con estensione ico nel file di output, che assume l'aspetto
desiderato in Esplora file.

Sintassi
-win32icon:filename

Argomenti
filename
Il file con estensione ico da aggiungere al file di output.

Osservazioni
Un file con estensione ico può essere creato mediante il compilatore di risorse. Il Compilatore di risorse viene
richiamato quando si compila un programma Visual C++. Dal file .rc viene creato un file .ico.
Vedere -linkresource (per fare riferimento a un file di risorse di .NET Framework) o a -resource (per associarlo).
Vedere -win32res per importare un file con estensione res.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Modificare la proprietà Icona dell'applicazione .
Per informazioni su come impostare questa opzione del compilatore a livello di codice, vedere ApplicationIcon.

Esempio
Compilare in.cs e associare un file con estensione ico rf.ico per produrre in.exe :

csc -win32icon:rf.ico in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
-win32manifest (opzioni del compilatore C#)
28/01/2021 • 4 minutes to read • Edit Online

Usare l'opzione -win32manifest per specificare un file manifesto dell'applicazione Win32 definito dall'utente
da incorporare nel file PE (Portable Executable) di un progetto.

Sintassi
-win32manifest: filename

Argomenti
filename
Nome e percorso del file manifesto personalizzato.

Osservazioni
Per impostazione predefinita, il compilatore Visual C# incorpora un manifesto dell'applicazione che specifica il
livello di esecuzione richiesto "asInvoker". Viene creato il manifesto nella stessa cartella in cui viene compilato
l'eseguibile, in genere la cartella bin\Debug o bin\Release quando si utilizza Visual Studio. Se si desidera fornire
un manifesto personalizzato, ad esempio per specificare un livello di esecuzione richiesto "highestAvailable" o
"requireAdministrator", utilizzare questa opzione per specificare il nome del file.

NOTE
Questa opzione e l'opzione -win32res (opzioni del compilatore C#) si escludono a vicenda. Se si tenta di usare entrambe le
opzioni nella stessa riga di comando si otterrà un errore di compilazione.

Un'applicazione senza un manifesto che specifichi il livello di esecuzione richiesto è soggetta alla
virtualizzazione dei file e del Registro di sistema con la funzionalità Controllo account utente in Windows. Per
altre informazioni, vedere Controllo dell'account utente.
L'applicazione sarà sottoposta a virtualizzazione se una di queste condizioni è vera:
Si usa l'opzione -nowin32manifest e non si specifica un manifesto in una fase successiva della
compilazione o come parte di un file di risorse di Windows (con estensione res) usando l'opzione -
win32res .
Si indica un manifesto personalizzato che non specifica un livello di esecuzione richiesto.
Visual Studio crea un file manifesto predefinito e lo memorizza nelle directory di debug e versione insieme al
file eseguibile. Per aggiungere un manifesto personalizzato, crearne uno in qualsiasi editor di testo e quindi
aggiungere il file al progetto. In alternativa, fare clic con il pulsante destro del mouse sull'icona Progetto in
Esplora soluzioni , fare clic su Aggiungi nuovo elemento e quindi scegliere File manifesto applicazione .
Dopo aver aggiunto il file manifesto nuovo o esistente, il file apparirà nell'elenco a discesa Manifesto . Per altre
informazioni, vedere pagina applicazione, Progettazione progetti (C#).
È possibile inserire il manifesto dell'applicazione come passaggio personalizzato dopo la compilazione o come
parte di un file di risorse Win32 usando l'opzione -nowin32manifest (opzioni del compilatore C#). Usare la
stessa opzione se si vuole che l'applicazione sia sottoposta alla virtualizzazione dei file o del Registro di sistema
in Windows Vista. Ciò impedirà al compilatore di creare e incorporare un manifesto predefinito nel file
eseguibile portabile (PE).

Esempio
Nell'esempio seguente viene illustrato il manifesto predefinito che il compilatore Visual C# inserisce in un file
PE.

NOTE
Il compilatore inserisce un nome di applicazione standard "MyApplication.app" nel file XML. Si tratta di una soluzione
alternativa per consentire l'esecuzione delle applicazioni in Windows Server 2003 Service Pack 3.

<?xml version="1.0" encoding="utf-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

Vedere anche
Opzioni del compilatore C#
-nowin32manifest (opzioni del compilatore C#)
Gestione delle proprietà di progetti e soluzioni
-win32res (opzioni del compilatore C#)
28/01/2021 • 2 minutes to read • Edit Online

L'opzione -win32res inserisce una risorsa Win32 nel file di output.

Sintassi
-win32res:filename

Argomenti
filename
Il file di risorse da aggiungere al file di output.

Osservazioni
Un file di risorse Win32 può essere creato con il compilatore di risorse. Il Compilatore di risorse viene
richiamato quando si compila un programma Visual C++. Dal file .rc viene creato un file .res.
In una risorsa Win32 può essere contenute le informazioni sulla versione o sulla bitmap (icona) che consentono
di identificare l'applicazione in Esplora file. Se non si specifica -win32res , il compilatore genererà le
informazioni sulla versione in base alla versione dell'assembly.
Vedere -linkresource (per fare riferimento a un file di risorse di .NET Framework) o a -resource (per associarlo).
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
1. Aprire la pagina Proprietà del progetto.
2. Fare clic sulla pagina delle proprietà Applicazione .
3. Fare clic sul pulsante File di risorse e scegliere un file usando la casella combinata.

Esempio
Compilare in.cs e associare un file di risorse Win32 rf.res per produrre in.exe :

csc -win32res:rf.res in.cs

Vedere anche
Opzioni del compilatore C#
Gestione delle proprietà di progetti e soluzioni
Errori del compilatore C#
02/11/2020 • 2 minutes to read • Edit Online

Per alcuni errori del compilatore C# sono disponibili articoli corrispondenti che spiegano il motivo per cui è
stato generato l'errore e, in alcuni casi, spiegano come correggerlo. Per sapere se sono disponibili informazioni
relative a un particolare messaggio di errore, seguire una di queste procedure.
Individuare il numero dell'errore (ad esempio CS0029) nella finestra di output e quindi cercarlo in
Microsoft Docs.
Scegliere il numero di errore (ad esempio CS0029) nella finestra di output, quindi premere F1.
Nell'indice immettere il numero di errore nella casella Cerca .
Se con nessuno di questi passaggi vengono reperite informazioni sull'errore, andare alla fine della pagina e
inviare un commento inserendo il numero o il testo dell'errore.
Per informazioni su come configurare le opzioni relative agli errori e agli avvisi in C#, vedere Pagina
Compilazione, Progettazione progetti (C#).

NOTE
Nomi o percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero
essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi
elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Vedere anche
Opzioni del compilatore C#
Spiacenti, non abbiamo informazioni specifiche sull'errore C#
Pagina Compilazione, Progettazione progetti (C#)
-Warn (opzioni del compilatore C#)
-nowarn (opzioni del compilatore C#)
Introduzione
02/11/2020 • 101 minutes to read • Edit Online

C#, pronunciato "See Sharp", è un linguaggio di programmazione semplice, moderno, orientato a oggetti e
indipendente dai tipi. C#ha le sue radici nella famiglia di linguaggi C e sarà immediatamente familiare ai
programmatori di C++c, e Java. C#è standardizzato da ECMA International come standard ECMA-334 e da
ISO/IEC come standard iso/IEC 23270 . Il C# compilatore Microsoft per il .NET Framework è
un'implementazione conforme di entrambi questi standard.
C# è un linguaggio orientato a oggetti, ma include anche il supporto per la programmazione orientata ai
componenti . La progettazione software contemporanea è basata in misura sempre maggiore su componenti
software costituiti da pacchetti di funzionalità autonomi e autodescrittivi. L'aspetto chiave di tali componenti è
che presentano un modello di programmazione con proprietà, metodi ed eventi. Presentano inoltre attributi che
forniscono informazioni dichiarative sul componente. Questi componenti, infine, includono la propria
documentazione. C#fornisce costrutti di linguaggio per supportare direttamente questi concetti, C# rendendo
un linguaggio molto naturale in cui creare e utilizzare i componenti software.
Diverse funzionalità C# offrono un valido aiuto per la creazione di applicazioni affidabili e durevoli: Garbage
Collection recupera automaticamente la memoria occupata dagli oggetti inutilizzati; la gestione delle
eccezioni fornisce un approccio strutturato ed estendibile per il rilevamento e il ripristino degli errori. e la
progettazione indipendente dai tipi del linguaggio rende impossibile leggere le variabili non inizializzate,
indicizzare le matrici oltre i limiti o per eseguire cast di tipo non verificati.
C# presenta un sistema di tipi unificato . Tutti i tipi C#, inclusi i tipi di primitiva quali int e double , ereditano
da un unico tipo object radice. Di conseguenza, tutti i tipi condividono un set di operazioni comuni e i valori dei
diversi tipi possono essere archiviati, trasportati e gestiti in modo coerente. C#, inoltre, supporta sia i tipi
riferimento sia i tipi valore definiti dall'utente, consentendo l'allocazione dinamica di oggetti e l'archiviazione
inline di strutture leggere.
Per garantire che C# i programmi e le librerie possano evolversi nel tempo in modo compatibile, è stato
enfatizzato il controllo delle C#versioni nel progetto. Molti linguaggi di programmazione prestano scarsa
attenzione a questo aspetto e, di conseguenza, i programmi scritti in tali linguaggi si interrompono molto più
spesso del necessario quando vengono introdotte nuove versioni delle librerie dipendenti. Gli aspetti C#della
progettazione che sono stati influenzati direttamente dalle considerazioni sul controllo delle virtual versioni
override includono i modificatori e separati, le regole per la risoluzione dell'overload del metodo e il supporto
per le dichiarazioni esplicite dei membri di interfaccia.
Il resto di questo capitolo descrive le funzionalità essenziali del C# linguaggio. Sebbene i capitoli successivi
descrivano le regole e le eccezioni in modo orientato ai dettagli e talvolta matematico, questo capitolo si
impegna per la chiarezza e la brevità a scapito della completezza. Lo scopo è fornire al lettore un'introduzione al
linguaggio che faciliterà la scrittura dei primi programmi e la lettura dei capitoli successivi.

Hello world
Il programma "Hello World" viene tradizionalmente usato per presentare un linguaggio di programmazione. Di
seguito è riportato il programma Hello, World in C#:

using System;

class Hello
{
static void Main() {
Console.WriteLine("Hello, World");
}
}

I file di origine C# hanno in genere l'estensione .cs . Supponendo che il programma "Hello, World" sia
archiviato nel hello.cs file, il programma può essere compilato con il C# compilatore Microsoft tramite la riga
di comando

csc hello.cs

che produce un assembly eseguibile hello.exe denominato. L'output prodotto da questa applicazione quando
viene eseguito è

Hello, World

Il programma "Hello World" inizia con una direttiva using che fa riferimento allo spazio dei nomi System . Gli
spazi dei nomi consentono di organizzare i programmi e le librerie C# in modo gerarchico. Gli spazi dei nomi
contengono tipi e altri spazi dei nomi. Lo stazio dei nomi System , ad esempio, contiene diversi tipi, come la
classe Console a cui viene fatto riferimento nel programma, e altri spazi dei nomi, come IO e Collections .
Una direttiva using che fa riferimento a un determinato spazio dei nomi consente l'uso non qualificato dei tipi
che sono membri di tale spazio dei nomi. Grazie alla direttiva using , il programma può usare
Console.WriteLine come sintassi abbreviata per System.Console.WriteLine .

La classe Hello dichiarata dal programma "Hello World" ha un solo membro, ovvero il metodo denominato
Main . Il Main metodo viene dichiarato con il static modificatore. Mentre i metodi di istanza possono fare
riferimento a una particolare istanza dell'oggetto contenitore usando la parola chiave this , i metodi statici
operano senza riferimento a un determinato oggetto. Per convenzione, un metodo statico denominato Main
funge da punto di ingresso di un programma.
L'output del programma viene prodotto dal metodo WriteLine della classe Console nello spazio dei nomi
System . Questa classe viene fornita dalle librerie di classi .NET Framework, che, per impostazione predefinita,
fanno riferimento al compilatore Microsoft C# automaticamente. Si noti C# che a sua volta non è presente una
libreria di runtime separata. Il .NET Framework è invece la libreria di runtime di C#.

Struttura del programma


I concetti organizzativi chiave di C# sono i programmi , gli spazi dei nomi , i tipi , i membri e gli assembly . I
programmi C# sono costituiti da uno o più file di origine. I programmi dichiarano i tipi, che contengono i
membri e possono essere organizzati in spazi dei nomi. Le classi e le interfacce sono esempi di tipi. I campi, i
metodi, le proprietà e gli eventi sono esempi di membri. Quando vengono compilati, i programmi C# vengono
inseriti fisicamente in assembly. Gli assembly hanno in genere l' .exe estensione .dll di file o, a seconda che
implementino applicazioni o librerie .
Esempio

using System;

namespace Acme.Collections
{
public class Stack
{
Entry top;

public void Push(object data) {


top = new Entry(top, data);
}

public object Pop() {


if (top == null) throw new InvalidOperationException();
object result = top.data;
top = top.next;
return result;
}

class Entry
{
public Entry next;
public object data;

public Entry(Entry next, object data) {


this.next = next;
this.data = data;
}
}
}
}

dichiara una classe denominata Stack in uno spazio dei nomi denominato. Acme.Collections Il nome completo
di questa classe è Acme.Collections.Stack . La classe contiene vari membri: un campo top , due metodi Push e
Pop e una classe annidata Entry . La classe Entry contiene altri tre membri: un campo next , un campo data
e un costruttore. Supponendo che il codice sorgente dell'esempio sia archiviato nel file acme.cs , la riga di
comando
csc /t:library acme.cs

compila l'esempio come libreria (codice senza un punto di ingresso Main ) e genera un assembly denominato
acme.dll .

Gli assembly contengono codice eseguibile sotto forma di istruzioni di linguaggio intermedio (il) e
informazioni sui simboli sotto forma di metadati . Prima di essere eseguito, il codice IL presente in un assembly
viene convertito automaticamente nel codice specifico del processore dal compilatore JIT (Just-In-Time) di .NET
Common Language Runtime.
Poiché un assembly è un'unità autodescrittiva di funzionalità contenente codice e metadati, in C# non sono
necessari file di intestazione e direttive #include . I membri e i tipi pubblici contenuti in un determinato
assembly vengono resi disponibili in un programma C# semplicemente facendo riferimento a tale assembly
durante la compilazione del programma. Questo programma usa ad esempio la classe Acme.Collections.Stack
dell'assembly acme.dll :

using System;
using Acme.Collections;

class Test
{
static void Main() {
Stack s = new Stack();
s.Push(1);
s.Push(10);
s.Push(100);
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
}
}

Se il programma è test.cs archiviato nel file, quando test.cs viene compilato, è /r possibile acme.dll fare
riferimento all'assembly usando l'opzione del compilatore:

csc /r:acme.dll test.cs

In questo modo verrà creato un assembly eseguibile denominato test.exe che, quando viene eseguito, genera
l'output:

100
10
1

C# consente di archiviare il testo di origine di un programma in vari file di origine. Quando viene compilato un
programma C# costituito da più file, tutti i file di origine vengono elaborati insieme e possono fare riferimento
l'uno all'altro. A livello concettuale è come se tutti i file di origine fossero concatenati in un unico grande file
prima di essere elaborati. Le dichiarazioni con prototipo non sono mai necessarie in C# perché, tranne che in
rare eccezioni, l'ordine di dichiarazione non è significativo. C# non limita un file di origine alla dichiarazione di
un solo tipo pubblico e non richiede che il nome del file di origine corrisponda a un tipo dichiarato nel file di
origine.

Tipi e variabili
In C# esistono due generi di tipi: tipi valore e tipi riferimento . Le variabili dei tipi valore contengono
direttamente i propri dati, mentre le variabili dei tipi riferimento archiviano i riferimenti ai propri dati, noti come
oggetti. Con i tipi riferimento, due variabili possono fare riferimento allo stesso oggetto e di conseguenza le
operazioni su una delle due variabili possono influire sull'oggetto a cui fa riferimento l'altra. Con i tipi valore,
ogni variabile ha una propria copia dei dati e non è possibile che le operazioni su una variabile influiscano
sull'altra (tranne nel caso delle variabili di parametro ref e out ).
C#i tipi di valore di sono ulteriormente divisi in tipi semplici , tipi enum , tipi struct e tipi nullable e i tipi C#di
riferimento di sono ulteriormente divisi in tipi di classe , tipi di interfaccia , matrici tipi e tipi delegati .
Nella tabella seguente viene fornita una panoramica C#del sistema di tipi di.
C AT EGO RIA DESC RIZ IO N E

Tipi valore Tipi semplici Signed Integer: sbyte , short , int


, long

Unsigned Integer: byte , ushort ,


uint , ulong

Caratteri Unicode: char

Virgola mobile IEEE: float , double

Decimale ad alta precisione: decimal

Booleano: bool

Tipi enum Tipi definiti dall'utente nel formato


enum E {...}

Tipi struct Tipi definiti dall'utente nel formato


struct S {...}

Tipi nullable Estensioni di tutti gli altri tipi valore


con un valore null

Tipi riferimento Tipi classe Classe di base principale di tutti gli altri
tipi: object

Stringhe Unicode: string

Tipi definiti dall'utente nel formato


class C {...}

Tipi interfaccia Tipi definiti dall'utente nel formato


interface I {...}

Tipi matrice Unidimensionale e multidimensionale,


ad esempio int[] e int[,]

Tipi delegato Tipi definiti dall'utente del modulo, ad


esempio delegate int D(...)

Gli otto tipi integrali offrono supporto per i valori a 8, 16, 32 e 64 bit in formato con segno o senza segno.
I due tipi a virgola float mobile double , e, vengono rappresentati usando i formati IEEE 754 a precisione
singola a 32 bit e a 64 bit.
Il tipo decimal è un tipo dati a 128 bit adatto per i calcoli finanziari e monetari.
C#il bool tipo di viene usato per rappresentare valori booleani, ovvero valori che true sono false o.
Per l'elaborazione di caratteri e stringhe, in C# viene usata la codifica Unicode. Il tipo char rappresenta un'unità
di codice UTF-16, mentre il tipo string rappresenta una sequenza di unità di codice UTF-16.
Nella tabella seguente C#sono riepilogati i tipi numerici.

C AT EGO RIA B IT T IP O IN T ERVA L LO / P REC ISIO N E

Integrale con segno 8 sbyte -128...127

16 short -32768... 32, 767

32 int -2147483648... 2, 147, 483,


647
C AT EGO RIA B IT T IP O IN T ERVA L LO / P REC ISIO N E

64 long -
9.223.372.036.854.775.808
... 9, 223, 372, 036, 854,
775, 807

Unsigned Integer 8 byte 0... 255

16 ushort 0... 65, 535

32 uint 0... 4, 294, 967, 295

64 ulong 0... 18, 446, 744, 073, 709,


551, 615

Virgola mobile 32 float 1,5 × 10 ^ − 45 a 3,4 × 10


^ 38, precisione a 7 cifre

64 double 5,0 × 10 ^ − 324 a 1,7 ×


10 ^ 308, precisione di 15
cifre

Decimal 128 decimal 1,0 × 10 ^ − 28 a 7,9 × 10


^ 28, precisione di 28 cifre

I programmi C# usano le dichiarazioni di tipo per creare nuovi tipi. Una dichiarazione di tipo consente di
specificare il nome e i membri del nuovo tipo. C#Cinque categorie di tipi sono definibili dall'utente: tipi di classe,
tipi di struct, tipi di interfaccia, tipi enum e tipi delegati.
Un tipo di classe definisce una struttura di dati che contiene membri dati (campi) e membri di funzione (metodi,
proprietà e altri). I tipi classe supportano l'ereditarietà singola e il polimorfismo, meccanismi in base ai quali le
classi derivate possono estendere e specializzare le classi di base.
Un tipo di struct è simile a un tipo di classe in quanto rappresenta una struttura con membri dati e membri di
funzione. Tuttavia, a differenza delle classi, gli struct sono tipi valore e non richiedono l'allocazione dell'heap. I
tipi struct non supportano l'ereditarietà specificata dall'utente. Tutti i tipi struct ereditano implicitamente dal tipo
object .

Un tipo di interfaccia definisce un contratto come un set denominato di membri di funzioni pubbliche. Una
classe o uno struct che implementa un'interfaccia deve fornire le implementazioni dei membri della funzione
dell'interfaccia. Un'interfaccia può ereditare da più interfacce di base e una classe o uno struct può implementare
più interfacce.
Un tipo delegato rappresenta i riferimenti ai metodi con un elenco di parametri e un tipo restituito specifici. I
delegati consentono di trattare i metodi come entità che è possibile assegnare a variabili e passare come
parametri. I delegati sono simili al concetto di puntatori a funzione disponibili in altri linguaggi. A differenza dei
puntatori a funzione, tuttavia, i delegati sono orientati agli oggetti e indipendenti dai tipi.
I tipi di classe, struct, interfaccia e delegato supportano tutti i generics, in base ai quali possono essere
parametrizzati con altri tipi.
Un tipo enum è un tipo distinto con costanti denominate. Ogni tipo di enumerazione ha un tipo sottostante, che
deve essere uno degli otto tipi integrali. Il set di valori di un tipo enum corrisponde al set di valori del tipo
sottostante.
C# supporta matrici unidimensionali e multidimensionali di qualsiasi tipo. A differenza dei tipi elencati in
precedenza, i tipi matrice non devono essere dichiarati prima dell'uso. Al contrario, i tipi matrice vengono
costruiti facendo seguire a un nome di tipo delle parentesi quadre. Ad esempio, int[] è una matrice
unidimensionale di int , int[,] è una matrice bidimensionale di int e int[][] è una matrice
unidimensionale di matrici unidimensionali di int .
Anche i tipi nullable non devono essere dichiarati prima di poter essere usati. Per ogni tipo T di valore non
nullable esiste un tipo T? nullable corrispondente, che può avere un valore null aggiuntivo. Ad esempio, int?
è un tipo che può ospitare qualsiasi Integer a 32 bit o il null valore.
C#il sistema di tipi è unificato in modo che un valore di qualsiasi tipo possa essere considerato come un
oggetto. In C# ogni tipo deriva direttamente o indirettamente dal tipo classe object e object è la classe di
base principale di tutti i tipi. I valori dei tipi riferimento vengono trattati come oggetti semplicemente
visualizzando tali valori come tipi object . I valori dei tipi valore vengono trattati come oggetti eseguendo
operazioni di conversione boxing e unboxing . Nell'esempio seguente un valore int viene convertito in
object e quindi convertito nuovamente in int .

using System;

class Test
{
static void Main() {
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing
}
}

Quando un valore di un tipo di valore viene convertito nel object tipo, viene allocata un'istanza dell'oggetto,
denominata anche "box", per mantenere il valore e il valore viene copiato in tale casella. Viceversa, quando
object viene eseguito il cast di un riferimento a un tipo valore, viene eseguito un controllo che l'oggetto a cui si
fa riferimento è una casella del tipo di valore corretto e, se il controllo ha esito positivo, il valore nella casella
viene copiato.
C#con il sistema di tipi unificato, i tipi di valore possono diventare oggetti "su richiesta". Grazie all'unificazione,
le librerie generiche che usano il tipo object possono essere usate con entrambi i tipi riferimento e valore.
In C# sono disponibili diversi tipi di variabili , inclusi campi, elementi matrice, variabili locali e parametri. Le
variabili rappresentano i percorsi di archiviazione e ogni variabile dispone di un tipo che determina quali valori
possono essere archiviati nella variabile, come illustrato nella tabella seguente.

T IP O DI VA RIA B IL E C O N T EN UTO P O SSIB IL E

Tipo valore non-nullable Valore esattamente del tipo indicato

Tipo valore nullable Un valore null o un valore di quel tipo esatto

object Un riferimento null, un riferimento a un oggetto di qualsiasi


tipo di riferimento o un riferimento a un valore boxed di
qualsiasi tipo di valore

Tipo classe Un riferimento null, un riferimento a un'istanza di quel tipo di


classe o un riferimento a un'istanza di una classe derivata da
tale tipo di classe

Tipo interfaccia Un riferimento null, un riferimento a un'istanza di un tipo di


classe che implementa tale tipo di interfaccia o un
riferimento a un valore boxed di un tipo di valore che
implementa tale tipo di interfaccia

Tipo matrice Un riferimento null, un riferimento a un'istanza di tale tipo di


matrice o un riferimento a un'istanza di un tipo di matrice
compatibile

Tipo delegato Un riferimento null o un riferimento a un'istanza del tipo


delegato

Espressioni
Le espressioni sono costituite da operandi e operatori . Gli operatori di un'espressione indicano le operazioni
che devono essere eseguite sugli operandi. Alcuni esempi di operatori sono + , - , * , / e new , mentre i
valori effettivi, i campi, le variabili locali e le espressioni sono esempi di operandi.
Se un'espressione contiene più operatori, la precedenza degli operatori determina l'ordine in cui vengono
valutati i singoli operatori. L'espressione x + y * z , ad esempio, viene valutata come x + (y * z) poiché
l'operatore * ha la precedenza sull'operatore + .
La maggior parte degli operatori può esserein overload . L'overload degli operatori consente di specificare
implementazioni di operatori definite dall'utente per le operazioni in cui uno o entrambi gli operandi
appartengono a un tipo struct o a una classe definita dall'utente.
Nella tabella seguente sono riepilogati C#gli operatori, che elencano le categorie di operatori in ordine di
precedenza, dal più alto al più basso. Gli operatori della stessa categoria hanno uguale precedenza.
C AT EGO RIA ESP RESSIO N E DESC RIZ IO N E

Primario x.m Accesso ai membri

x(...) Chiamata a metodi e delegati

x[...] Accesso a matrici e indicizzatori

x++ Post-incremento

x-- Post-decremento

new T(...) Creazione di oggetti e delegati

new T(...){...} creazione di oggetti con inizializzatore

new {...} inizializzatore di oggetti anonimo

new T[...] creazione di matrici

typeof(T) ottiene l'oggetto System.Type per


T

checked(x) Valutare l'espressione in un contesto


controllato (checked)

unchecked(x) Valutare l'espressione in un contesto


non controllato (unchecked)

default(T) ottiene un valore predefinito di tipo


T

delegate {...} Funzione anonima (metodo anonimo)

Unario +x identità

-x Negazione

!x Negazione logica

~x Negazione bit per bit

++x Pre-incremento

--x Pre-decremento

(T)x converte in modo esplicito x al tipo


T

await x attende in modo asincrono il


completamento di x

Moltiplicazione x * y Moltiplicazione

x / y Divisione

x % y Resto

Addizione x + y Addizione, concatenazione di stringhe,


combinazione di delegati

x - y Sottrazione, rimozione di delegati


C AT EGO RIA ESP RESSIO N E DESC RIZ IO N E

Shift x << y Spostamento a sinistra

x >> y Spostamento a destra

Operatori relazionali e operatori di test x < y Minore di


del tipo

x > y Maggiore di

x <= y Minore o uguale

x >= y Maggiore o uguale a

x is T restituisce true se x è un oggetto


T , altrimenti false

x as T restituisce x tipizzato come T


oppure null se x non è un
oggetto T

Uguaglianza x == y Uguale

x != y Diverso

AND logico x & y AND Integer bit per bit, AND logico
booleano

XOR logico x ^ y XOR Integer bit per bit, XOR logico


booleano

OR logico x | y OR Integer bit per bit, OR logico


booleano

AND condizionale x && y Valuta solo se x è y``true

OR condizionale x || y Valuta solo se x è y``false

Null-coalescing x ?? y y Restituisce se x è, null in x


caso contrario

Condizionale x ? y : z restituisce y se x è true , z se


x è false

Assegnazione o funzione anonima x = y Assegnazione

x op= y Assegnazione composta; gli operatori


supportati *= sono /= %= +=
-= <<= >>= &= ^= |=

(T x) => y Funzione anonima (espressione


lambda)

Istruzioni
Le azioni di un programma vengono espresse mediante istruzioni . C# supporta numerosi tipi di istruzioni,
alcune delle quali sono definite in termini di istruzioni nidificate.
Un blocco consente di scrivere più istruzioni nei contesti in cui ne è consentita una sola. Un blocco è costituito
da un elenco di istruzioni scritte tra i delimitatori { e } .
Le istruzioni di dichiarazione vengono usate per dichiarare le costanti e le variabili locali.
Le istruzioni di espressione vengono usate per valutare le espressioni. Le espressioni che possono essere
utilizzate come istruzioni includono chiamate al metodo, allocazioni di oggetti utilizzando new l'operatore,
assegnazioni = utilizzando e gli operatori di assegnazione composta, operazioni di incremento e decremento
mediante il ++ operatori -- e e espressioni await.
Le istruzioni di selezione vengono usate per selezionare una tra più istruzioni che è possibile eseguire sulla
base del valore di alcune espressioni. In questo gruppo sono incluse le istruzioni if e switch .
Le istruzioni di iterazione vengono usate per eseguire ripetutamente un'istruzione incorporata. In questo
gruppo sono incluse le istruzioni while , do , for e foreach .
Le istruzioni di spostamento vengono usate per trasferire il controllo. In questo gruppo sono incluse le
istruzioni break , continue , goto , throw , return e yield .
L'istruzione try ... catch viene usata per rilevare le eccezioni che si verificano durante l'esecuzione di un blocco,
mentre l'istruzione try ... finally viene usata per specificare il codice di finalizzazione che viene eseguito
sempre, indipendentemente dal fatto che si sia verificata un'eccezione.
Le checked istruzioni unchecked e vengono utilizzate per controllare il contesto di controllo dell'overflow per le
conversioni e le operazioni aritmetiche di tipo integrale.
L'istruzione lock viene usata per ottenere il blocco a esclusione reciproca per un oggetto specificato, eseguire
un'istruzione e quindi rilasciare il blocco.
L'istruzione using viene usata per ottenere una risorsa, eseguire un'istruzione e quindi eliminare la risorsa.
Di seguito sono riportati alcuni esempi di ogni tipo di istruzione
Dichiarazioni di variabili locali

static void Main() {


int a;
int b = 2, c = 3;
a = 1;
Console.WriteLine(a + b + c);
}

Dichiarazione di costante locale

static void Main() {


const float pi = 3.1415927f;
const int r = 25;
Console.WriteLine(pi * r * r);
}

Istruzione Expression

static void Main() {


int i;
i = 123; // Expression statement
Console.WriteLine(i); // Expression statement
i++; // Expression statement
Console.WriteLine(i); // Expression statement
}

Istruzione if

static void Main(string[] args) {


if (args.Length == 0) {
Console.WriteLine("No arguments");
}
else {
Console.WriteLine("One or more arguments");
}
}

Istruzione switch
static void Main(string[] args) {
int n = args.Length;
switch (n) {
case 0:
Console.WriteLine("No arguments");
break;
case 1:
Console.WriteLine("One argument");
break;
default:
Console.WriteLine("{0} arguments", n);
break;
}
}

Istruzione while

static void Main(string[] args) {


int i = 0;
while (i < args.Length) {
Console.WriteLine(args[i]);
i++;
}
}

Istruzione do

static void Main() {


string s;
do {
s = Console.ReadLine();
if (s != null) Console.WriteLine(s);
} while (s != null);
}

Istruzione for

static void Main(string[] args) {


for (int i = 0; i < args.Length; i++) {
Console.WriteLine(args[i]);
}
}

Istruzione foreach

static void Main(string[] args) {


foreach (string s in args) {
Console.WriteLine(s);
}
}

Istruzione break

static void Main() {


while (true) {
string s = Console.ReadLine();
if (s == null) break;
Console.WriteLine(s);
}
}

Istruzione continue

static void Main(string[] args) {


for (int i = 0; i < args.Length; i++) {
if (args[i].StartsWith("/")) continue;
Console.WriteLine(args[i]);
}
}

Istruzione goto
static void Main(string[] args) {
int i = 0;
goto check;
loop:
Console.WriteLine(args[i++]);
check:
if (i < args.Length) goto loop;
}

Istruzione return

static int Add(int a, int b) {


return a + b;
}

static void Main() {


Console.WriteLine(Add(1, 2));
return;
}

Istruzione yield

static IEnumerable<int> Range(int from, int to) {


for (int i = from; i < to; i++) {
yield return i;
}
yield break;
}

static void Main() {


foreach (int x in Range(-10,10)) {
Console.WriteLine(x);
}
}

throw istruzioni try e

static double Divide(double x, double y) {


if (y == 0) throw new DivideByZeroException();
return x / y;
}

static void Main(string[] args) {


try {
if (args.Length != 2) {
throw new Exception("Two numbers required");
}
double x = double.Parse(args[0]);
double y = double.Parse(args[1]);
Console.WriteLine(Divide(x, y));
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
finally {
Console.WriteLine("Good bye!");
}
}

checked istruzioni unchecked e

static void Main() {


int i = int.MaxValue;
checked {
Console.WriteLine(i + 1); // Exception
}
unchecked {
Console.WriteLine(i + 1); // Overflow
}
}

Istruzione lock
class Account
{
decimal balance;
public void Withdraw(decimal amount) {
lock (this) {
if (amount > balance) {
throw new Exception("Insufficient funds");
}
balance -= amount;
}
}
}

Istruzione using

static void Main() {


using (TextWriter w = File.CreateText("test.txt")) {
w.WriteLine("Line one");
w.WriteLine("Line two");
w.WriteLine("Line three");
}
}

Classi e oggetti
Le classi sono i tipi C# più importanti. Una classe è una struttura di dati che combina in una singola unità lo
stato (campi) e le azioni (metodi e altri membri di funzione). Una classe fornisce una definizione per istanze
della classe create dinamicamente, note anche come oggetti . Le classi supportano l'ereditarietà e il
polimorfismo , meccanismi in base ai quali le classi derivate possono estendere e specializzare le classi di
base .
Le nuove classi vengono create tramite dichiarazioni di classe. Una dichiarazione di classe inizia con
un'intestazione che specifica gli attributi e i modificatori della classe, il nome della classe, la classe di base (se
disponibile) e le interfacce implementate dalla classe. L'intestazione è seguita dal corpo della classe, costituito da
un elenco di dichiarazioni di membro scritte tra i delimitatori { e } .
Di seguito è riportata una dichiarazione di una classe semplice denominata Point :

public class Point


{
public int x, y;

public Point(int x, int y) {


this.x = x;
this.y = y;
}
}

Le istanze delle classi vengono create usando l'operatore new , che alloca memoria per una nuova istanza,
richiama un costruttore per inizializzare l'istanza e restituisce un riferimento all'istanza. Le istruzioni seguenti
creano due Point oggetti e archiviano i riferimenti a tali oggetti in due variabili:

Point p1 = new Point(0, 0);


Point p2 = new Point(10, 20);

La memoria occupata da un oggetto viene recuperata automaticamente quando l'oggetto non è più in uso. In C#
non è possibile, né necessario, deallocare oggetti in modo esplicito.
Members
I membri di una classe sono membri statici o membri di istanza . I primi appartengono a classi, mentre i
secondi appartengono a oggetti, ovvero a istanze di classi.
Nella tabella seguente viene fornita una panoramica dei tipi di membri che possono essere contenuti in una
classe.

M EM B RO DESC RIZ IO N E

Costanti Valori costanti associati alla classe


M EM B RO DESC RIZ IO N E

Campi Variabili della classe

Metodi Calcoli e azioni che possono essere eseguiti dalla classe

Proprietà Azioni associate alla lettura e alla scrittura di proprietà


denominate della classe

Indicizzatori Azioni associate all'indicizzazione di istanze della classe, come


una matrice

Eventi Notifiche che possono essere generate dalla classe

Operatori Conversioni e operatori di espressione supportati dalla classe

Costruttori Azioni necessarie per inizializzare istanze della classe o la


classe stessa

Distruttori Azioni da eseguire prima che istanze della classe vengano


eliminate in modo permanente

Tipi Tipi annidati dichiarati dalla classe

Accessibilità
A ogni membro di una classe è associata una caratteristica di accessibilità, che controlla le aree di testo del
programma in grado di accedere al membro. Esistono cinque diverse forme di accessibilità, Questi report sono
riepilogati nella tabella seguente.

A C C ESSIB IL ITÀ SIGN IF IC ATO

public Accesso non limitato

protected Accesso limitato a questa classe o alle classi derivate da


questa classe

internal Accesso limitato a questo programma

protected internal Accesso limitato a questo programma o alle classi derivate


da questa classe

private Accesso limitato a questa classe

Parametri di tipo
Una definizione di classe può specificare un set di parametri di tipo se si fa seguire il nome della classe da un
elenco di nomi di parametri di tipo, racchiuso tra parentesi uncinate. I parametri di tipo possono essere utilizzati
nel corpo delle dichiarazioni di classe per definire i membri della classe. Nell'esempio seguente i parametri di
tipo di Pair sono TFirst e TSecond :

public class Pair<TFirst,TSecond>


{
public TFirst First;
public TSecond Second;
}

Un tipo di classe dichiarato per accetta parametri di tipo è denominato tipo di classe generico. Possono essere
generici anche i tipi struct, interfaccia e delegato.
Quando si usa la classe generica, è necessario specificare argomenti di tipo per ogni parametro di tipo:

Pair<int,string> pair = new Pair<int,string> { First = 1, Second = "two" };


int i = pair.First; // TFirst is int
string s = pair.Second; // TSecond is string

Un tipo generico con argomenti di tipo forniti, Pair<int,string> come sopra, viene definito tipo costruito.
Classi di base
Una dichiarazione di classe può specificare una classe di base se si fa seguire il nome della classe e i parametri di
tipo dai due punti e dal nome della classe di base. L'omissione della specifica della classe di base equivale alla
derivazione dal tipo object . Nell'esempio seguente la classe di base di Point3D è Point e quella di Point è
object :

public class Point


{
public int x, y;

public Point(int x, int y) {


this.x = x;
this.y = y;
}
}

public class Point3D: Point


{
public int z;

public Point3D(int x, int y, int z): base(x, y) {


this.z = z;
}
}

Una classe eredita i membri della relativa classe di base. L'ereditarietà indica che una classe contiene in modo
implicito tutti i membri della relativa classe di base, ad eccezione dell'istanza e dei costruttori statici e dei
distruttori della classe di base. Una classe derivata può aggiungere nuovi membri a quelli ereditati, ma non può
rimuovere la definizione di un membro ereditato. Nell'esempio precedente Point3D eredita i campi x e y da
Point e ogni istanza di Point3D contiene tre campi: x , y e z .

Un tipo di classe viene implicitamente convertito in uno dei relativi tipi di classe di base. Una variabile di un tipo
di classe, quindi, può fare riferimento a un'istanza della classe o a un'istanza di una classe derivata. Nel caso
delle dichiarazioni di classe precedenti, ad esempio, una variabile di tipo Point può fare riferimento a Point o
Point3D :

Point a = new Point(10, 20);


Point b = new Point3D(10, 20, 30);

Campi
Un campo è una variabile associata a una classe o a un'istanza di una classe.
Un campo dichiarato con il static modificatore definisce un campo statico . che identifica esattamente una
posizione di memoria. Indipendentemente dal numero di istanze di una classe create, esiste una sola copia di un
campo statico.
Un campo dichiarato senza il static modificatore definisce un campo di istanza . Ogni istanza di una classe
contiene una copia separata di tutti i campi di istanza della classe.
Nell'esempio seguente ogni istanza della classe Color include una copia distinta dei campi di istanza r , g e
b , mentre esiste una sola copia dei campi statici Black , White , Red , Green e Blue .

public class Color


{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte r, g, b;

public Color(byte r, byte g, byte b) {


this.r = r;
this.g = g;
this.b = b;
}
}

Come illustrato nell'esempio precedente, i campi di sola lettura possono essere dichiarati con un
modificatore readonly . L'assegnazione a readonly un campo può essere eseguita solo come parte della
dichiarazione del campo o in un costruttore della stessa classe.
Metodi
Un metodo è un membro che implementa un calcolo o un'azione che può essere eseguita da un oggetto o una
classe. I metodi statici sono accessibili tramite la classe, mentre i metodi di istanza sono accessibili tramite
istanze della classe.
I metodi includono un elenco di parametri (possibilmente vuoto), che rappresentano valori o riferimenti a
variabili passati al metodo, e un tipo restituito , che specifica il tipo di valore calcolato e restituito dal metodo. Il
tipo restituito di un metodo void è se non restituisce alcun valore.
Come i tipi, anche i metodi possono avere un set di parametri di tipo per i quali è necessario specificare
argomenti di tipo quando vengono chiamati. A differenza dei tipi, gli argomenti di tipo possono essere spesso
dedotti dagli argomenti di una chiamata al metodo e non devono essere assegnati in modo esplicito.
La firma di un metodo deve essere univoca nell'ambito della classe in cui viene dichiarato il metodo. La firma di
un metodo è costituita dal nome del metodo, dal numero di parametri di tipo e dal numero, dai modificatori e
dai tipi dei rispettivi parametri. Nella firma di un metodo non è incluso il tipo restituito.
Parametri
I parametri consentono di passare ai metodi valori o riferimenti a variabili. I parametri di un metodo ottengono i
valori effettivi dagli argomenti specificati quando viene richiamato il metodo. Esistono quattro tipi di parametri:
parametri di valore, parametri di riferimento, i parametri di output e matrici di parametri.
Un parametro di valore consente di passare parametri di input. Corrisponde a una variabile locale che ottiene
il valore iniziale dall'argomento passato per il parametro. Eventuali modifiche a un parametro di valore non
interessano l'argomento passato per il parametro.
I parametri di valore possono essere facoltativi specificando un valore predefinito. In questo caso gli argomenti
corrispondenti possono essere omessi.
Un parametro di riferimento consente di passare parametri di input e di output. L'argomento passato per un
parametro di riferimento deve essere una variabile e, durante l'esecuzione del metodo, il parametro di
riferimento rappresenta la stessa posizione di memoria della variabile di argomento. Un parametro di
riferimento viene dichiarato con il modificatore ref . Nell'esempio seguente viene illustrato l'uso di parametri
ref .

using System;

class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}

static void Main() {


int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); // Outputs "2 1"
}
}

Un parametro di output consente di passare parametri di input. Un parametro di output è simile a un


parametro di riferimento, da cui differisce perché il valore iniziale dell'argomento fornito dal chiamante non è
importante. Un parametro di output viene dichiarato con il modificatore out . Nell'esempio seguente viene
illustrato l'uso di parametri out .

using System;

class Test
{
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
}

static void Main() {


int res, rem;
Divide(10, 3, out res, out rem);
Console.WriteLine("{0} {1}", res, rem); // Outputs "3 1"
}
}
Una matrice di parametri consente di passare un numero variabile di argomenti a un metodo. Una matrice di
parametri viene dichiarata con il modificatore params . Solo l'ultimo parametro di un metodo può essere
costituito da una matrice di parametri, che deve essere sempre di tipo unidimensionale. I Write metodi
WriteLine e della System.Console classe sono ottimi esempi di utilizzo delle matrici di parametri. Vengono
dichiarati come illustrato di seguito.

public class Console


{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
...
}

All'interno di un metodo, una matrice di parametri si comporta esattamente come un normale parametro di tipo
matrice. In una chiamata di un metodo con una matrice di parametri, tuttavia, è possibile passare un singolo
argomento di tipo matrice di parametri oppure un qualsiasi numero di argomenti di tipo elemento della matrice
di parametri. Nel secondo caso, un'istanza di matrice viene automaticamente creata e inizializzata con gli
argomenti specificati. Questo esempio

Console.WriteLine("x={0} y={1} z={2}", x, y, z);

è equivalente alla sintassi seguente.

string s = "x={0} y={1} z={2}";


object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

Corpo del metodo e variabili locali


Il corpo di un metodo specifica le istruzioni da eseguire quando viene richiamato il metodo.
Il corpo di un metodo può dichiarare variabili specifiche per la chiamata del metodo. Queste variabili prendono
il nome di variabili locali . Una dichiarazione di variabile locale specifica un nome di tipo, un nome di variabile
e possibilmente un valore iniziale. Nell'esempio seguente viene dichiarata una variabile locale i con un valore
iniziale pari a zero e una variabile locale j senza valore iniziale.

using System;

class Squares
{
static void Main() {
int i = 0;
int j;
while (i < 10) {
j = i * i;
Console.WriteLine("{0} x {0} = {1}", i, j);
i = i + 1;
}
}
}

In C# è necessario assegnare esplicitamente una variabile locale prima di poterne ottenere il valore. Se ad
esempio nella dichiarazione della variabile locale i precedente non fosse stato incluso un valore iniziale, il
compilatore avrebbe segnalato un errore ogni volta che la variabile i veniva usata, perché i non era
assegnata esplicitamente in quei punti del programma.
Un metodo può usare istruzioni return per restituire il controllo al chiamante. In un metodo che restituisce
void , le istruzioni return non possono specificare un'espressione. In un metodo che restituisce non void -
return , le istruzioni devono includere un'espressione che calcola il valore restituito.

Metodi statici e di istanza


Un metodo dichiarato con un static modificatore è un metodo statico . Questo metodo non agisce su
un'istanza specifica e può accedere direttamente solo a membri statici.
Un metodo dichiarato senza un static modificatore è un metodo di istanza . Questo metodo agisce su
un'istanza specifica e può accedere a membri statici e di istanza. L'istanza in cui è stato richiamato un metodo di
istanza è accessibile in modo esplicito come this . È un errore fare riferimento a this in un metodo statico.
La classe Entity seguente contiene sia membri statici sia membri di istanza.

class Entity
{
static int nextSerialNo;
int serialNo;

public Entity() {
serialNo = nextSerialNo++;
}

public int GetSerialNo() {


return serialNo;
}

public static int GetNextSerialNo() {


return nextSerialNo;
}

public static void SetNextSerialNo(int value) {


nextSerialNo = value;
}
}

Ogni istanza Entity contiene un numero di serie (e probabilmente anche altre informazioni non illustrate qui).
Il costruttore Entity (simile a un metodo di istanza) inizializza la nuova istanza con il successivo numero di
serie disponibile. Poiché il costruttore è un membro di istanza, è consentito accedere sia al campo di istanza
serialNo sia al campo statico nextSerialNo .

I metodi statici GetNextSerialNo e SetNextSerialNo possono accedere al campo statico nextSerialNo , ma si


verificherebbe un errore se accedessero direttamente al campo di istanza serialNo .
Nell'esempio seguente viene illustrato l'utilizzo della Entity classe.

using System;

class Test
{
static void Main() {
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
}
}

Osservare come, mentre i metodi statici SetNextSerialNo e GetNextSerialNo vengono richiamati sulla classe, il
metodo di istanza GetSerialNo viene richiamato su istanze della classe.
Metodi virtuali, di override e astratti
Se una dichiarazione di metodo di istanza include un modificatore virtual , il metodo viene definito metodo
vir tuale . Quando non virtual è presente alcun modificatore, il metodo viene definito come metodo non
vir tuale .
Quando viene richiamato un metodo virtuale, il tipo in fase di esecuzione dell'istanza per cui viene eseguita
la chiamata determina l'implementazione effettiva del metodo da richiamare. In una chiamata a un metodo non
virtuale, il fattore determinante è il tipo in fase di compilazione dell'istanza.
Un metodo virtuale può essere sottoposto a override in una classe derivata. Quando una dichiarazione di
metodo di istanza override include un modificatore, il metodo esegue l'override di un metodo virtuale
ereditato con la stessa firma. Mentre una dichiarazione di metodo virtuale introduce un nuovo metodo, una
dichiarazione di metodo di override specializza un metodo virtuale ereditato esistente specificando una nuova
implementazione del metodo.
Un metodo astratto è un metodo virtuale senza implementazione. Un metodo astratto viene dichiarato con il
abstract modificatore ed è consentito solo in una classe che è anche abstract dichiarata. Un metodo astratto
deve essere sottoposto a override in ogni classe derivata non astratta.
Nell'esempio seguente viene dichiarata una classe astratta, Expression , che rappresenta un nodo dell'albero
delle espressioni, e tre classi derivate, Constant , VariableReference e Operation , che implementano i nodi
dell'albero delle espressioni relativi a costanti, riferimenti a variabili e operazioni aritmetiche. (Questo è simile a,
ma non deve essere confuso con i tipi di albero delle espressioni introdotti nei tipi di albero delle espressioni).

using System;
using System.Collections;

public abstract class Expression


{
public abstract double Evaluate(Hashtable vars);
}

public class Constant: Expression


{
double value;

public Constant(double value) {


this.value = value;
}

public override double Evaluate(Hashtable vars) {


return value;
}
}

public class VariableReference: Expression


{
string name;

public VariableReference(string name) {


this.name = name;
}

public override double Evaluate(Hashtable vars) {


object value = vars[name];
if (value == null) {
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}

public class Operation: Expression


{
Expression left;
char op;
Expression right;

public Operation(Expression left, char op, Expression right) {


this.left = left;
this.op = op;
this.right = right;
}

public override double Evaluate(Hashtable vars) {


double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Unknown operator");
}
}

Le quattro classi precedenti possono essere usate per modellare espressioni aritmetiche. Usando istanze di
queste classi, l'espressione x + 3 , ad esempio, può essere rappresentata come illustrato di seguito.

Expression e = new Operation(


new VariableReference("x"),
'+',
new Constant(3));

Il metodo Evaluate di un'istanza Expression viene richiamato per valutare l'espressione specificata e generare
un valore double . Il metodo accetta come argomento un oggetto Hashtable che contiene i nomi delle variabili
(come chiavi delle voci) e valori (come valori delle voci). Il Evaluate metodo è un metodo astratto virtuale,
ovvero le classi derivate non astratte devono eseguirne l'override per fornire un'implementazione effettiva.
L'implementazione di un valore Constant del metodo Evaluate restituisce semplicemente la costante
memorizzata. L' VariableReference implementazione di un oggetto cerca il nome della variabile in Hashtable e
restituisce il valore risultante. L'implementazione di un valore Operation valuta prima gli operandi sinistro e
destro (richiamando in modo ricorsivo i metodi Evaluate ) e quindi esegue l'operazione aritmetica specificata.
Il programma seguente usa le classi Expression per valutare l'espressione x * (y + 2) per valori diversi di x
e y.

using System;
using System.Collections;

class Test
{
static void Main() {
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Hashtable vars = new Hashtable();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // Outputs "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // Outputs "16.5"
}
}

Overload di un metodo
L'overload di un metodo consente a più metodi della stessa classe di avere lo stesso nome, purché abbiano
firme univoche. Quando si compila una chiamata di un metodo di overload, il compilatore usa la risoluzione
dell'overload per determinare il metodo specifico da richiamare. La risoluzione dell'overload trova il metodo
che meglio corrisponde agli argomenti o segnala un errore se non riesce a trovare alcuna corrispondenza.
Nell'esempio seguente viene illustrato il funzionamento effettivo della risoluzione dell'overload. Il commento
relativo a ogni chiamata del metodo Main mostra il metodo effettivamente richiamato.

class Test
{
static void F() {
Console.WriteLine("F()");
}

static void F(object x) {


Console.WriteLine("F(object)");
}

static void F(int x) {


Console.WriteLine("F(int)");
}

static void F(double x) {


Console.WriteLine("F(double)");
}

static void F<T>(T x) {


Console.WriteLine("F<T>(T)");
}

static void F(double x, double y) {


Console.WriteLine("F(double, double)");
}

static void Main() {


F(); // Invokes F()
F(1); // Invokes F(int)
F(1.0); // Invokes F(double)
F("abc"); // Invokes F(object)
F((double)1); // Invokes F(double)
F((object)1); // Invokes F(object)
F<int>(1); // Invokes F<T>(T)
F(1, 1); // Invokes F(double, double)
}
}
Come illustrato nell'esempio, è sempre possibile selezionare un metodo specifico eseguendo in modo esplicito il
cast degli argomenti ai tipi di parametro corretti e/o specificando in modo esplicito gli argomenti di tipo.
Altri membri di funzione
I membri che contengono codice eseguibile sono noti come membri funzione di una classe. Nella sezione
precedente sono stati descritti i metodi, che costituiscono i membri di funzione principali. In questa sezione
vengono descritti gli altri tipi di membri di C#funzione supportati da: costruttori, proprietà, indicizzatori, eventi,
operatori e distruttori.
Nel codice seguente viene illustrata una classe List<T> generica denominata, che implementa un elenco di
oggetti espandibile. Nella classe sono contenuti alcuni esempi di membri di funzione più comuni.
public class List<T> {
// Constant...
const int defaultCapacity = 4;

// Fields...
T[] items;
int count;

// Constructors...
public List(int capacity = defaultCapacity) {
items = new T[capacity];
}

// Properties...
public int Count {
get { return count; }
}
public int Capacity {
get {
return items.Length;
}
set {
if (value < count) value = count;
if (value != items.Length) {
T[] newItems = new T[value];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
}
}

// Indexer...
public T this[int index] {
get {
return items[index];
}
set {
items[index] = value;
OnChanged();
}
}

// Methods...
public void Add(T item) {
if (count == Capacity) Capacity = count * 2;
items[count] = item;
count++;
OnChanged();
}
protected virtual void OnChanged() {
if (Changed != null) Changed(this, EventArgs.Empty);
}
public override bool Equals(object other) {
return Equals(this, other as List<T>);
}
static bool Equals(List<T> a, List<T> b) {
if (a == null) return b == null;
if (b == null || a.count != b.count) return false;
for (int i = 0; i < a.count; i++) {
if (!object.Equals(a.items[i], b.items[i])) {
return false;
}
}
return true;
}

// Event...
public event EventHandler Changed;

// Operators...
public static bool operator ==(List<T> a, List<T> b) {
return Equals(a, b);
}
public static bool operator !=(List<T> a, List<T> b) {
return !Equals(a, b);
}
}

Costruttori
C# supporta sia costruttori di istanza sia costruttori statici. Un costruttore di istanza è un membro che
implementa le azioni necessarie per inizializzare un'istanza di una classe, mentre un costruttore statico è un
membro che implementa le azioni necessarie per inizializzare una classe nel momento in cui viene caricata per
la prima volta.
Un costruttore viene dichiarato come un metodo, senza tipo restituito e con lo stesso nome della classe in cui è
contenuto. Se una dichiarazione di costruttore include static un modificatore, dichiara un costruttore statico.
In caso contrario, dichiara un costruttore di istanza.
È possibile eseguire l'overload di costruttori di istanze. La classe List<T> , ad esempio, dichiara due costruttori
di istanza, uno senza parametri e uno che accetta un parametro int . I costruttori di istanza vengono richiamati
con l'operatore new . Nelle istruzioni seguenti vengono allocate due List<string> istanze di utilizzando
ciascuno dei costruttori List della classe.

List<string> list1 = new List<string>();


List<string> list2 = new List<string>(10);

A differenza di altri membri, i costruttori di istanza non vengono ereditati e una classe non può contenere
costruttori di istanza diversi da quelli effettivamente dichiarati nella classe. Se per una classe non è specificato
alcun costruttore di istanza, ne viene automaticamente fornito uno vuoto senza parametri.
Proprietà
Le proprietà sono una naturale estensione dei campi. Entrambi sono membri denominati con tipi associati e la
sintassi per accedere ai campi e alle proprietà è identica. A differenza dei campi, tuttavia, le proprietà non
denotano posizioni di memoria, ma hanno funzioni di accesso che specificano le istruzioni da eseguire nel
momento in cui ne vengono letti o scritti i valori.
Una proprietà viene dichiarata come un campo, ad eccezione del fatto che get la dichiarazione termina con una
set funzione di accesso e/o una { funzione } di accesso scritta tra i delimitatori e anziché terminare con un
punto e virgola. get Una proprietà con una funzione di accesso e una set funzione di accesso è una proprietà
di lettura/scrittura , una proprietà con get una sola funzione di accesso è una proprietà di sola lettura e una
proprietà con set solo una funzione di accesso è un proprietà di sola scrittura .
Una get funzione di accesso corrisponde a un metodo senza parametri con un valore restituito del tipo di
proprietà. Fatta eccezione per la destinazione di un'assegnazione, quando si fa riferimento a una proprietà in
un'espressione get , viene richiamata la funzione di accesso della proprietà per calcolare il valore della
proprietà.
Una set funzione di accesso corrisponde a un metodo con un solo value parametro denominato e nessun
tipo restituito. Quando si fa riferimento a una proprietà come destinazione di un'assegnazione o come operando
di ++ o -- , la set funzione di accesso viene richiamata con un argomento che fornisce il nuovo valore.
La classe List<T> dichiara due proprietà, Count e Capacity , che sono rispettivamente di sola lettura e di
lettura/scrittura. Di seguito è riportato un esempio d'uso di queste proprietà.

List<string> names = new List<string>();


names.Capacity = 100; // Invokes set accessor
int i = names.Count; // Invokes get accessor
int j = names.Capacity; // Invokes get accessor

Come per i campi e i metodi, C# supporta sia proprietà di istanza sia proprietà statiche. Le proprietà statiche
vengono dichiarate con il modificatore e le proprietà dell' static istanza vengono dichiarate senza di essa.
Le funzioni di accesso di una proprietà possono essere virtuali. Se una dichiarazione di proprietà contiene un
modificatore virtual , abstract o override , questo viene applicato anche alle funzioni di accesso della
proprietà.
Indicizzatori
Un indicizzatore è un membro che consente di indicizzare gli oggetti esattamente come una matrice. Un
indicizzatore viene dichiarato come una proprietà, ma a differenza di questa il nome del membro è this
seguito da un elenco di parametri scritti tra i delimitatori [ e ] . I parametri sono disponibili nelle funzioni di
accesso dell'indicizzatore. Analogamente alle proprietà, gli indicizzatori possono essere di lettura/scrittura, di
sola lettura o di sola scrittura e le funzioni di accesso di un indicizzatore possono essere virtuali.
La classe List dichiara un indicizzatore di lettura/scrittura che accetta un parametro int e consente di
indicizzare istanze List con valori int . Esempio:
List<string> names = new List<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = names[i];
names[i] = s.ToUpper();
}

Gli indicizzatori possono essere sottoposti a overload, ovvero una classe può dichiarare più indicizzatori purché
includano un numero o tipi diversi di parametri.
Eventi
Un evento è un membro che consente a una classe o a un oggetto di inviare notifiche. Un evento viene
dichiarato come un campo, ad eccezione del fatto che event la dichiarazione include una parola chiave e il tipo
deve essere un tipo delegato.
In una classe che dichiara un membro di evento, l'evento si comporta esattamente come un campo di un tipo
delegato, purché l'evento non sia astratto e non dichiari funzioni di accesso. Il campo archivia un riferimento a
un delegato che rappresenta i gestori eventi aggiunti all'evento. Se non sono presenti handle di evento, il campo
null è.

La classe List<T> dichiara un singolo membro di evento denominato Changed , con cui si indica che un nuovo
elemento è stato aggiunto all'elenco. L' Changed evento viene generato OnChanged dal metodo virtuale, che
verifica prima di tutto se l'evento null è (ovvero non è presente alcun gestore). Generare un evento equivale a
richiamare il delegato rappresentato dall'evento. Non sono quindi necessari speciali costrutti di linguaggio per
generare eventi.
I client rispondono agli eventi tramite gestori eventi , che possono essere aggiunti con l'operatore += e
rimossi con l'operatore -= . Nell'esempio seguente un gestore eventi viene aggiunto all'evento Changed di
List<string> .

using System;

class Test
{
static int changeCount;

static void ListChanged(object sender, EventArgs e) {


changeCount++;
}

static void Main() {


List<string> names = new List<string>();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); // Outputs "3"
}
}

Negli scenari avanzati in cui è auspicabile controllare l'archiviazione sottostante di un evento, una dichiarazione
di evento può fornire in modo esplicito le funzioni di accesso add e remove , simili alla funzione di accesso set
di una proprietà.
Operatori
Un operatore è un membro che definisce il significato dell'applicazione di un particolare operatore di
espressione alle istanze di una classe. È possibile definire tre tipi di operatori: unari, binari e di conversione. Tutti
gli operatori devono essere dichiarati come public e static .
La classe List<T> dichiara due operatori, operator== e operator!= , attribuendo così un nuovo significato alle
espressioni che applicano questi operatori alle istanze di List . In particolare, gli operatori definiscono
l'uguaglianza List<T> di due istanze di confrontando ognuno degli oggetti contenuti Equals usando i relativi
metodi. Nell'esempio seguente viene usato l'operatore == per confrontare due istanze di List<int> .
using System;

class Test
{
static void Main() {
List<int> a = new List<int>();
a.Add(1);
a.Add(2);
List<int> b = new List<int>();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Outputs "True"
b.Add(3);
Console.WriteLine(a == b); // Outputs "False"
}
}

Il primo Console.WriteLine restituisce True perché i due elenchi contengono lo stesso numero di oggetti con
gli stessi valori e nello stesso ordine. Se in List<T> non fosse stato definito l'operatore operator== , il primo
Console.WriteLine avrebbe restituito False perché a e b fanno riferimento a istanze di List<int> diverse.

Distruttori
Un distruttore è un membro che implementa le azioni necessarie per distruggere un'istanza di una classe. I
distruttori non possono avere parametri, non possono avere modificatori di accessibilità e non possono essere
richiamati in modo esplicito. Il distruttore di un'istanza viene richiamato automaticamente durante Garbage
Collection.
Per la Garbage Collector è consentita una latitudine ampia per decidere quando raccogliere oggetti ed eseguire
distruttori. In particolare, la tempistica delle chiamate del distruttore non è deterministica e i distruttori possono
essere eseguiti su qualsiasi thread. Per questi e altri motivi, le classi devono implementare i distruttori solo
quando non sono realizzabili altre soluzioni.
L'istruzione using offre una soluzione più efficace per l'eliminazione di oggetti.

Struct
Analogamente alle classi, i tipi struct sono strutture dati che possono contenere membri dati e membri
funzione. A differenza delle classi, tuttavia, i tipi struct sono tipi valore e non richiedono l'allocazione dell'heap.
Una variabile di un tipo struct archivia direttamente i relativi dati, mentre una variabile di un tipo classe archivia
un riferimento a un oggetto allocato in modo dinamico. I tipi struct non supportano l'ereditarietà specificata
dall'utente. Tutti i tipi struct ereditano implicitamente dal tipo object .
I tipi struct sono particolarmente utili per strutture dati di piccole dimensioni che hanno una semantica di valori.
I numeri complessi, i punti di un sistema di coordinate o le coppie chiave-valore di un dizionario sono buoni
esempi di struct. L'uso dei tipi struct al posto delle classi può determinare una notevole differenza riguardo al
numero di allocazioni di memoria eseguite da un'applicazione. Il programma seguente, ad esempio, crea e
inizializza una matrice di 100 punti. Con Point implementato come classe, vengono create istanze di 101
oggetti separati, uno per la matrice e uno per ciascuno dei 100 elementi.

class Point
{
public int x, y;

public Point(int x, int y) {


this.x = x;
this.y = y;
}
}

class Test
{
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
}
}

In alternativa, è possibile Point creare uno struct.


struct Point
{
public int x, y;

public Point(int x, int y) {


this.x = x;
this.y = y;
}
}

Viene ora creata un'istanza di un solo oggetto, quello relativo alla matrice, e le istanze di Point vengono
memorizzate inline nella matrice.
Con l'operatore new vengono chiamati i costruttori di struct, ma questo non implica l'allocazione di memoria.
Anziché allocare dinamicamente un oggetto e restituire un riferimento a quest'ultimo, un costruttore di struct
restituisce semplicemente il valore del tipo struct (in genere una posizione temporanea nello stack), che viene
quindi copiato in base alle esigenze.
Con le classi, due variabili possono fare riferimento allo stesso oggetto e pertanto le operazioni su una variabile
possono influire sull'oggetto a cui fa riferimento l'altra. Con i tipi struct, ogni variabile ha una propria copia dei
dati e le operazioni su una variabile non possono influire sull'altra. L'output prodotto dal frammento di codice
seguente, ad esempio, dipende dalla Point presenza di una classe o di uno struct.

Point a = new Point(10, 10);


Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Se Point è una classe, l'output è 20 perché a e b fanno riferimento allo stesso oggetto. Se Point è uno
struct, l'output è 10 perché l'assegnazione di a a b crea una copia del valore e questa copia non è interessata
dall'assegnazione successiva a a.x .
L'esempio precedente evidenzia due delle limitazioni dei tipi struct. In primo luogo, la copia di un intero tipo
struct è in genere meno efficiente della copia di un riferimento all'oggetto. Di conseguenza, il passaggio dei
parametri di assegnazione e valore può risultare molto più costoso con i tipi struct che con i tipi riferimento. In
secondo luogo, ad eccezione dei parametri ref e out , non è possibile creare riferimenti ai tipi struct e questa
condizione che ne limita l'uso in varie situazioni.

Matrici
Una matrice è una struttura di dati contenente una serie di variabili accessibili tramite indici calcolati. Le
variabili contenute in una matrice, chiamate anche elementi della matrice, sono tutte dello stesso tipo, definito
tipo di elemento della matrice.
Poiché i tipi di matrice sono tipi di riferimento, la dichiarazione di una variabile di matrice si limita a riservare
spazio per un riferimento a un'istanza di matrice. Le istanze di matrice effettive vengono create dinamicamente
in fase di new esecuzione usando l'operatore. L' new operazione specifica la lunghezza della nuova istanza
della matrice, che viene quindi corretta per la durata dell'istanza. Gli indici degli elementi di una matrice sono
compresi tra 0 e Length - 1 . L'operatore new inizializza automaticamente gli elementi di una matrice sul
rispettivo valore predefinito che, ad esempio, equivale a zero per tutti i tipi numerici e a null per tutti i tipi di
riferimento.
Nell'esempio seguente viene creata una matrice di elementi int , viene inizializzata la matrice e ne viene
stampato il contenuto.

using System;

class Test
{
static void Main() {
int[] a = new int[10];
for (int i = 0; i < a.Length; i++) {
a[i] = i * i;
}
for (int i = 0; i < a.Length; i++) {
Console.WriteLine("a[{0}] = {1}", i, a[i]);
}
}
}
In questo esempio viene creata e usata una matrice unidimensionale . C# supporta anche matrici
multidimensionali . Il numero di dimensioni di un tipo di matrice, chiamato anche rango del tipo di matrice, è
pari a uno più il numero di virgole tra parentesi quadre del tipo di matrice. Nell'esempio seguente vengono
allocate una matrice unidimensionale, bidimensionale e tridimensionale.

int[] a1 = new int[10];


int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

La matrice a1 contiene 10 elementi, la matrice a2 contiene 50 (10 × 5) elementi e la a3 matrice contiene 100
(10 × 5 × 2) elementi.
L'elemento di una matrice può essere di qualsiasi tipo, anche di tipo matrice. Una matrice con elementi di tipo
matrice viene chiamata talvolta matrice di matrici , poiché le lunghezze delle matrici elemento non devono
essere tutte uguali. Nell'esempio seguente viene allocata una matrice di matrici di int :

int[][] a = new int[3][];


a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

La prima riga crea una matrice con tre elementi, ognuno di tipo int[] e con un valore iniziale di null . Le righe
successive inizializzano quindi i tre elementi con riferimenti a singole istanze di matrice di lunghezza variabile.
L' new operatore consente di specificare i valori iniziali degli elementi della matrice utilizzando un
inizializzatore di matrice , ovvero un elenco di espressioni scritte tra { i delimitatori e. } Nell'esempio
seguente viene allocata e inizializzata una matrice int[] con tre elementi.

int[] a = new int[] {1, 2, 3};

Si noti che la lunghezza della matrice viene dedotta dal numero di espressioni tra { e. } Per evitare di
ridefinire il tipo di matrice, è possibile abbreviare le dichiarazioni di campo e variabile locale.

int[] a = {1, 2, 3};

Entrambi gli esempi precedenti equivalgono a quello seguente:

int[] t = new int[3];


t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;

Interfacce
Un'interfaccia definisce un contratto che può essere implementato da classi e struct. Può contenere metodi,
proprietà, eventi e indicizzatori. Un'interfaccia non fornisce le implementazioni dei membri che definisce, ma
specifica semplicemente i membri che devono essere forniti dalle classi o dai tipi struct che la implementano.
Le interfacce possono usare l'ereditarietà multipla . Nell'esempio seguente l'interfaccia IComboBox eredita da
ITextBox e IListBox .
interface IControl
{
void Paint();
}

interface ITextBox: IControl


{
void SetText(string text);
}

interface IListBox: IControl


{
void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

Classi e struct possono implementare più interfacce. Nell'esempio seguente la classe EditBox implementa
IControl e IDataBound .

interface IDataBound
{
void Bind(Binder b);
}

public class EditBox: IControl, IDataBound


{
public void Paint() {...}
public void Bind(Binder b) {...}
}

Quando una classe o un tipo struct implementa un'interfaccia specifica, le istanze di tale classe o struct possono
essere convertite in modo implicito in quel tipo di interfaccia. Esempio:

EditBox editBox = new EditBox();


IControl control = editBox;
IDataBound dataBound = editBox;

Nei casi in cui un'istanza non implementa staticamente un'interfaccia specifica è possibile usare cast di tipo
dinamico. Ad esempio, le istruzioni seguenti usano i cast di tipo dinamico per ottenere le implementazioni
IDataBound dell' IControl interfaccia e dell'oggetto. Poiché il tipo effettivo dell'oggetto è, EditBox il cast ha
esito positivo.

object obj = new EditBox();


IControl control = (IControl)obj;
IDataBound dataBound = (IDataBound)obj;

Nella classe precedente EditBox , il Paint metodo IControl dell'interfaccia e il Bind metodo IDataBound
dell'interfaccia vengono implementati usando public i membri. C#supporta anche implementazioni
esplicite di membri di interfaccia , usando le quali la classe o lo struct public può evitare di creare membri.
Un'implementazione esplicita di un membro dell'interfaccia viene scritta usando il nome completo del membro
dell'interfaccia. La classe EditBox può ad esempio implementare i metodi IControl.Paint e IDataBound.Bind
usando implementazioni esplicite di un membro dell'interfaccia, come indicato di seguito.

public class EditBox: IControl, IDataBound


{
void IControl.Paint() {...}
void IDataBound.Bind(Binder b) {...}
}

È possibile accedere ai membri dell'interfaccia esplicita solo tramite il tipo di interfaccia. Ad esempio,
l'implementazione di IControl.Paint fornita dalla classe precedente EditBox può essere richiamata solo
IControl convertendo prima il EditBox riferimento al tipo di interfaccia.

EditBox editBox = new EditBox();


editBox.Paint(); // Error, no such method
IControl control = editBox;
control.Paint(); // Ok
Enumerazioni
Un tipo enum è un tipo valore distinto con un set di costanti denominate. Nell'esempio seguente viene
dichiarato e usato un tipo enum denominato Color con tre valori costanti Green , Red , e Blue .

using System;

enum Color
{
Red,
Green,
Blue
}

class Test
{
static void PrintColor(Color color) {
switch (color) {
case Color.Red:
Console.WriteLine("Red");
break;
case Color.Green:
Console.WriteLine("Green");
break;
case Color.Blue:
Console.WriteLine("Blue");
break;
default:
Console.WriteLine("Unknown color");
break;
}
}

static void Main() {


Color c = Color.Red;
PrintColor(c);
PrintColor(Color.Blue);
}
}

Ogni tipo di enumerazione ha un tipo integrale corrispondente denominato tipo sottostante del tipo enum.
Un tipo enum che non dichiara in modo esplicito un tipo sottostante ha un tipo sottostante int di. Il formato di
archiviazione e l'intervallo di valori possibili di un tipo enum sono determinati dal tipo sottostante. Il set di valori
che un tipo enum può assumere non è limitato dai relativi membri enum. In particolare, è possibile eseguire il
cast di qualsiasi valore del tipo sottostante di un'enumerazione al tipo enum ed è un valore valido distinto di tale
tipo enum.
Nell'esempio seguente viene dichiarato un tipo enum denominato Alignment con un tipo sottostante di sbyte .

enum Alignment: sbyte


{
Left = -1,
Center = 0,
Right = 1
}

Come illustrato nell'esempio precedente, una dichiarazione di membro enum può includere un'espressione
costante che specifica il valore del membro. Il valore costante per ogni membro enum deve essere compreso
nell'intervallo del tipo sottostante dell'enumerazione. Quando una dichiarazione di un membro enum non
specifica in modo esplicito un valore, al membro viene assegnato il valore zero (se è il primo membro del tipo
enum) o il valore del membro enum testuale precedente, più uno.
I valori enum possono essere convertiti in valori integrali e viceversa usando i cast di tipo. Esempio:

int i = (int)Color.Blue; // int i = 2;


Color c = (Color)2; // Color c = Color.Blue;

Il valore predefinito di qualsiasi tipo enum è il valore integrale zero convertito nel tipo enum. Nei casi in cui le
variabili vengono inizializzate automaticamente su un valore predefinito, si tratta del valore assegnato alle
variabili dei tipi enum. Affinché il valore predefinito di un tipo enum sia facilmente disponibile, il valore letterale
0 viene convertito in modo implicito in qualsiasi tipo enum. Di conseguenza, quanto riportato di seguito è
consentito.
Color c = 0;

Delegati
Un tipo delegato rappresenta riferimenti ai metodi con un elenco di parametri e un tipo restituito particolari. I
delegati consentono di trattare i metodi come entità che è possibile assegnare a variabili e passare come
parametri. I delegati sono simili al concetto di puntatori a funzione disponibili in altri linguaggi. A differenza dei
puntatori a funzione, tuttavia, i delegati sono orientati agli oggetti e indipendenti dai tipi.
Nell'esempio seguente viene dichiarato e usato un tipo delegato denominato Function .

using System;

delegate double Function(double x);

class Multiplier
{
double factor;

public Multiplier(double factor) {


this.factor = factor;
}

public double Multiply(double x) {


return x * factor;
}
}

class Test
{
static double Square(double x) {
return x * x;
}

static double[] Apply(double[] a, Function f) {


double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}

static void Main() {


double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, Square);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new Multiplier(2.0);
double[] doubles = Apply(a, m.Multiply);
}
}

Un'istanza del tipo delegato Function può fare riferimento a qualsiasi metodo che accetta un argomento
double e restituisce un valore double . Il Apply metodo applica un oggetto Function specificato agli elementi
di un double[] oggetto, restituendo un oggetto double[] con i risultati. Nel metodo Main , Apply viene usato
per applicare a double[] tre funzioni differenti.
Un delegato può fare riferimento a un metodo statico, come Square o Math.Sin nell'esempio precedente, o a
un metodo di istanza, come m.Multiply nell'esempio precedente. Un delegato che fa riferimento a un metodo
di istanza fa riferimento anche a un oggetto particolare. Quando il metodo di istanza viene richiamato tramite il
delegato, l'oggetto diventa this nella chiamata.
I delegati possono essere creati anche mediante funzioni anonime, ovvero "metodi inline" creati in tempo reale.
Le funzioni anonime possono vedere le variabili locali dei metodi circostanti. Quindi, l'esempio precedente può
essere scritto più facilmente senza usare una Multiplier classe:

double[] doubles = Apply(a, (double x) => x * 2.0);

Una proprietà interessante e utile di un delegato consiste nel fatto che non conosce o non prende in
considerazione la classe del metodo a cui fa riferimento. Ciò che conta è che il metodo a cui un delegato fa
riferimento abbia gli stessi parametri e restituisca lo stesso tipo del delegato in questione.

Attributi
Tipi, membri e altre entità di un programma C# supportano modificatori che controllano alcuni aspetti del loro
comportamento. L'accessibilità di un metodo, ad esempio, è controllata con i modificatori public , protected ,
internal e private . Il linguaggio C# generalizza questa funzionalità in modo che i tipi di informazioni
dichiarative definiti dall'utente possano essere associati a entità di programma e recuperati in fase di
esecuzione. I programmi specificano queste informazioni dichiarative aggiuntive definendo e usando attributi .
Nell'esempio seguente viene dichiarato un attributo HelpAttribute che può essere inserito in entità di
programma per fornire collegamenti alla documentazione correlata.

using System;

public class HelpAttribute: Attribute


{
string url;
string topic;

public HelpAttribute(string url) {


this.url = url;
}

public string Url {


get { return url; }
}

public string Topic {


get { return topic; }
set { topic = value; }
}
}

Tutte le classi di attributi derivano dalla System.Attribute classe di base fornita dal .NET Framework. Gli attributi
possono essere applicati specificandone il nome (con eventuali argomenti) tra parentesi quadre appena prima
della dichiarazione associata. Se il nome di un attributo termina Attribute in, tale parte del nome può essere
omessa quando si fa riferimento all'attributo. L'attributo HelpAttribute , ad esempio, può essere usato come
illustrato di seguito.

[Help("http://msdn.microsoft.com/.../MyClass.htm")]
public class Widget
{
[Help("http://msdn.microsoft.com/.../MyClass.htm", Topic = "Display")]
public void Display(string text) {}
}

Questo esempio connette un oggetto HelpAttribute Widget HelpAttribute alla Display classe e un altro al
metodo nella classe. I costruttori pubblici di una classe di attributo controllano le informazioni che devono
essere specificate quando l'attributo è associato a un'entità di programma. È possibile specificare informazioni
aggiuntive facendo riferimento a proprietà di lettura/scrittura pubbliche della classe di attributo, ad esempio il
riferimento alla proprietà Topic precedente.
Nell'esempio seguente viene illustrato come recuperare le informazioni sugli attributi per un'entità programma
specificata in fase di esecuzione tramite reflection.

using System;
using System.Reflection;

class Test
{
static void ShowHelp(MemberInfo member) {
HelpAttribute a = Attribute.GetCustomAttribute(member,
typeof(HelpAttribute)) as HelpAttribute;
if (a == null) {
Console.WriteLine("No help for {0}", member);
}
else {
Console.WriteLine("Help for {0}:", member);
Console.WriteLine(" Url={0}, Topic={1}", a.Url, a.Topic);
}
}

static void Main() {


ShowHelp(typeof(Widget));
ShowHelp(typeof(Widget).GetMethod("Display"));
}
}
Se un attributo viene richiesto tramite reflection, il costruttore della classe di attributo viene richiamato con le
informazioni specificate nell'origine del programma e viene restituita l'istanza dell'attributo risultante. Se sono
state fornite informazioni aggiuntive tramite proprietà, queste vengono impostate sui valori specificati prima
che venga restituita l'istanza dell'attributo.
Criteri di ricerca per C# 7
02/11/2020 • 29 minutes to read • Edit Online

Le estensioni di criteri di ricerca per C# consentono molti dei vantaggi dei tipi di dati algebrici e dei criteri di
ricerca dai linguaggi funzionali, ma in modo da integrarsi perfettamente con l'aspetto del linguaggio sottostante.
Le funzionalità di base sono: i tipi di record, ovvero i tipi il cui significato semantico è descritto dalla forma dei
dati; e criteri di ricerca, ovvero un nuovo form di espressione che consente la scomposizione multilivello
estremamente concisa di questi tipi di dati. Gli elementi di questo approccio sono ispirati dalle funzionalità
correlate nei linguaggi di programmazione F # e scala.

Espressione is
L' is operatore viene esteso per testare un'espressione rispetto a un criterio.

relational_expression
: relational_expression 'is' pattern
;

Questa forma di relational_expression è aggiunta ai moduli esistenti nella specifica C#. Si tratta di un errore in
fase di compilazione se il relational_expression a sinistra del is token non designa un valore o non ha un tipo.
Ogni identificatore del modello introduce una nuova variabile locale assegnata definitivamente dopo l' is
operatore true (ovvero assegnata definitivamente quando è true).

Nota: esiste tecnicamente un'ambiguità tra il tipo in is-expression e constant_pattern, una delle quali
potrebbe essere un'analisi valida di un identificatore qualificato. Si tenta di associarlo come tipo per la
compatibilità con le versioni precedenti del linguaggio; solo se l'operazione ha esito negativo, viene risolta
come in altri contesti, fino alla prima cosa trovata (che deve essere una costante o un tipo). Questa
ambiguità è presente solo sul lato destro di un' is espressione.

Modelli
Gli schemi vengono usati nell' is operatore e in una switch_Statement per esprimere la forma dei dati in base
ai quali confrontare i dati in ingresso. I modelli possono essere ricorsivi in modo che sia possibile trovare una
corrispondenza tra parti dei dati e modelli secondari.
pattern
: declaration_pattern
| constant_pattern
| var_pattern
;

declaration_pattern
: type simple_designation
;

constant_pattern
: shift_expression
;

var_pattern
: 'var' simple_designation
;

Nota: esiste tecnicamente un'ambiguità tra il tipo in is-expression e constant_pattern, una delle quali
potrebbe essere un'analisi valida di un identificatore qualificato. Si tenta di associarlo come tipo per la
compatibilità con le versioni precedenti del linguaggio; solo se l'operazione ha esito negativo, viene risolta
come in altri contesti, fino alla prima cosa trovata (che deve essere una costante o un tipo). Questa
ambiguità è presente solo sul lato destro di un' is espressione.

Modello di dichiarazione
Il declaration_pattern verifica che un'espressione sia di un tipo specificato e ne esegue il cast a tale tipo se il test
ha esito positivo. Se il simple_designation è un identificatore, introduce una variabile locale del tipo specificato
denominato dall'identificatore specificato. La variabile locale viene assegnata definitivamente quando il risultato
dell'operazione di corrispondenza dei modelli è true.

declaration_pattern
: type simple_designation
;

La semantica di runtime di questa espressione è che testa il tipo di runtime dell'operando di sinistra
relational_expression sul tipo nel modello. Se è del tipo di runtime (o di un sottotipo), il risultato di is operator
è true . Dichiara una nuova variabile locale denominata dall' identificatore a cui viene assegnato il valore
dell'operando sinistro quando il risultato è true .
Alcune combinazioni di tipo statico del lato sinistro e del tipo specificato vengono considerate incompatibili e
generano un errore in fase di compilazione. Un valore di tipo statico E viene definito modello compatibile con
il tipo T se esiste una conversione di identità, una conversione di un riferimento implicito, una conversione
boxing, una conversione esplicita di un riferimento o una conversione unboxing da E a T . Si tratta di un
errore in fase di compilazione se un'espressione di tipo E non è compatibile con il tipo in un modello di tipo
con cui corrisponde.

Nota: in C# 7,1 questa operazione viene estesa per consentire un'operazione di corrispondenza dei modelli
se il tipo di input o il tipo T è un tipo aperto. Questo paragrafo viene sostituito da quanto segue:
Alcune combinazioni di tipo statico del lato sinistro e del tipo specificato vengono considerate incompatibili
e generano un errore in fase di compilazione. Un valore di tipo statico E viene definito modello compatibile
con il tipo T se esiste una conversione di identità, una conversione di un riferimento implicito, una
conversione boxing, una conversione di riferimento esplicita o una conversione unboxing E da T a
oppure se E o T è un tipo aper to . Si tratta di un errore in fase di compilazione se un'espressione di
tipo E non è compatibile con il tipo in un modello di tipo con cui corrisponde.
Il modello di dichiarazione è utile per l'esecuzione di test dei tipi di riferimento in fase di esecuzione e sostituisce
l'idioma

var v = expr as Type;


if (v != null) { // code using v }

Con l'aspetto leggermente più conciso

if (expr is Type v) { // code using v }

Si tratta di un errore se il tipo è un tipo di valore Nullable.


Il modello di dichiarazione può essere usato per testare i valori dei tipi nullable: un valore di tipo Nullable<T> (o
un oggetto boxed T ) corrisponde a un modello di tipo T2 id se il valore è non null e il tipo di T2 è T o di un
tipo o di un'interfaccia di base di T . Ad esempio, nel frammento di codice

int? x = 3;
if (x is int v) { // code using v }

La condizione dell' if istruzione è in fase di true esecuzione e la variabile v include il valore 3 di tipo int
all'interno del blocco.
Criterio costante

constant_pattern
: shift_expression
;

Un criterio costante verifica il valore di un'espressione rispetto a un valore costante. La costante può essere
qualsiasi espressione costante, ad esempio un valore letterale, il nome di una variabile dichiarata const o una
costante di enumerazione o un' typeof espressione.
Se e e c sono di tipo integrale, il modello viene considerato corrispondente se il risultato dell'espressione
e == c è true .

In caso contrario, il modello viene considerato corrispondente se object.Equals(e, c) restituisce true . In


questo caso si tratta di un errore in fase di compilazione se il tipo statico di e non è compatibile con il tipo della
costante.
Modello var

var_pattern
: 'var' simple_designation
;

Un'espressione e corrisponde a un var_pattern sempre. In altre parole, una corrispondenza con un modello var
ha sempre esito positivo. Se il simple_designation è un identificatore, in fase di esecuzione il valore di e viene
associato a una variabile locale appena introdotta. Il tipo della variabile locale è il tipo statico di e.
Si tratta di un errore se il nome viene var associato a un tipo.

Istruzioni switch
L' switch istruzione viene estesa in modo da selezionare per l'esecuzione il primo blocco a cui è associato un
modello corrispondente all' espressione switch.

switch_label
: 'case' complex_pattern case_guard? ':'
| 'case' constant_expression case_guard? ':'
| 'default' ':'
;

case_guard
: 'when' expression
;

L'ordine in cui vengono confrontati i modelli non è definito. Un compilatore è autorizzato a trovare una
corrispondenza con i modelli non ordinati e a riutilizzare i risultati dei modelli già corrispondenti per calcolare il
risultato della corrispondenza di altri criteri.
Se è presente un case-Guard , l'espressione è di tipo bool . Viene valutato come una condizione aggiuntiva che
deve essere soddisfatta affinché il case venga considerato soddisfatto.
Se un switch_label non può avere alcun effetto in fase di esecuzione, non è un errore perché il relativo modello
viene sostituito dai case precedenti. [TODO: è necessario essere più precisi sulle tecniche che il compilatore deve
usare per raggiungere questo giudizio.]
Una variabile di pattern dichiarata in un switch_label viene assegnata in modo sicuro nel blocco case solo se il
blocco case contiene esattamente una switch_label.
[TODO: è necessario specificare quando un blocco switch è raggiungibile.]
Ambito delle variabili del modello
L'ambito di una variabile dichiarata in un modello è il seguente:
Se il modello è un'etichetta case, l'ambito della variabile è il blocco case.
In caso contrario, la variabile viene dichiarata in un'espressione is_pattern e il relativo ambito è basato sul
costrutto che racchiude immediatamente l'espressione contenente l'espressione is_pattern , come indicato di
seguito:
Se l'espressione è in un'espressione lambda con corpo di espressione, il relativo ambito è il corpo
dell'espressione lambda.
Se l'espressione si trova in una proprietà o in un metodo con corpo di espressione, il relativo ambito è il
corpo del metodo o della proprietà.
Se l'espressione si trova in una when clausola di una catch clausola, l'ambito è tale catch clausola.
Se l'espressione si trova in un iteration_statement, il suo ambito è semplicemente tale istruzione.
In caso contrario, se l'espressione è in un altro formato di istruzione, il relativo ambito è l'ambito che
contiene l'istruzione.
Ai fini della determinazione dell'ambito, un embedded_statement viene considerato nel proprio ambito. Ad
esempio, la grammatica per un if_Statement è

if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement 'else' embedded_statement
;

Quindi, se l'istruzione controllata di un if_Statement dichiara una variabile di modello, il suo ambito è limitato a
tale embedded_statement:
if (x) M(y is var z);

In questo caso l'ambito di z è l'istruzione incorporata M(y is var z); .


Altri casi sono errori per altri motivi, ad esempio nel caso di un valore predefinito di un parametro o di un
attributo, che sono entrambi un errore, perché tali contesti richiedono un'espressione costante.

In C# 7,3 sono stati aggiunti i seguenti contesti in cui è possibile dichiarare una variabile di modello:
Se l'espressione si trova in un inizializzatore di Costruttore, il relativo ambito è l' inizializzatore del
costruttore e il corpo del costruttore.
Se l'espressione si trova in un inizializzatore di campo, il relativo ambito è il equals_value_clause in cui
viene visualizzato.
Se l'espressione si trova in una clausola di query specificata per essere convertita nel corpo di
un'espressione lambda, il suo ambito è semplicemente tale espressione.

Modifiche alla disambiguazione sintattica


Esistono situazioni che coinvolgono generics in cui la grammatica C# è ambigua e la specifica del linguaggio
spiega come risolvere le ambiguità seguenti:
ambiguità della grammatica 7.6.5.2
Le produzioni per il nome semplice (§ 7.6.3) e l' accesso ai membri (§ 7.6.5) possono dare luogo a ambiguità
nella grammatica per le espressioni. Ad esempio, l'istruzione:

F(G<A,B>(7));

può essere interpretato come una chiamata a F con due argomenti, G < A e B > (7) . In alternativa, può
essere interpretato come una chiamata a F con un argomento, che è una chiamata a un metodo generico
G con due argomenti di tipo e un argomento normale.

Se una sequenza di token può essere analizzata (nel contesto) come nome semplice (§ 7.6.3), accesso ai
membri (§ 7.6.5) o puntatore-membro-Access (§ 18.5.2) che termina con un tipo-argument-list (§ 4.4.1),
viene esaminato il token immediatamente successivo al token di chiusura > . Se è uno dei

( ) ] } : ; , . ? == != | ^

quindi, l' elenco di argomenti di tipo viene mantenuto come parte del nome semplice, l' accesso ai membri o
l'accesso ai membri del puntatore e qualsiasi altra possibile analisi della sequenza di token viene scartata. In
caso contrario, l' elenco di argomenti di tipo non viene considerato parte dell'accesso con nome semplice,
accesso ai membri o > puntatore al membro, anche se non è possibile analizzare la sequenza di token. Si
noti che queste regole non vengono applicate durante l'analisi di un elenco di argomenti di tipo in uno
spazio dei nomi o un nome di tipo (§ 3,8). L'istruzione

F(G<A,B>(7));

in base a questa regola, sarà interpretato come una chiamata a F con un argomento, che è una chiamata a
un metodo generico G con due argomenti di tipo e un argomento normale. Istruzioni
F(G < A, B > 7);
F(G < A, B >> 7);

ogni oggetto verrà interpretato come una chiamata a F con due argomenti. L'istruzione

x = F < A > +y;

verrà interpretato come un operatore minore di, un operatore maggiore di e un operatore unario più, come
se l'istruzione fosse stata scritta x = (F < A) > (+y) , anziché come nome semplice con un tipo-argument-
list seguito da un operatore Binary Plus. Nell'istruzione

x = y is C<T> + z;

i token C<T> vengono interpretati come uno spazio dei nomi o un nome di tipo con un elenco di argomenti
di tipo.

In C# 7 sono state introdotte alcune modifiche che rendono le regole di risoluzione dell'ambiguità non più
sufficienti per gestire la complessità del linguaggio.
Dichiarazioni di variabili out
È ora possibile dichiarare una variabile in un argomento out:

M(out Type name);

Tuttavia, il tipo può essere generico:

M(out A<B> name);

Poiché la grammatica del linguaggio per l'argomento utilizza Expression, questo contesto è soggetto alla regola
di risoluzione dell'ambiguità. In questo caso, la chiusura > è seguita da un identificatore, che non è uno dei
token che consente di considerarlo come un elenco di argomenti di tipo. Viene pertanto proposto di
aggiungere identificatore al set di token che attiva la risoluzione dell'ambiguità in un tipo-
argument-list .
Tuple e dichiarazioni di decostruzione
Un valore letterale di tupla viene eseguito esattamente nello stesso problema. Si consideri l'espressione di tupla

(A < B, C > D, E < F, G > H)

Con le precedenti regole C# 6 per l'analisi di un elenco di argomenti, questo verrebbe analizzato come una tupla
con quattro elementi, a partire dal A < B primo. Tuttavia, quando viene visualizzato a sinistra di una
decostruzione, si vuole che la risoluzione dell'ambiguità venga attivata dal token dell' identificatore , come
descritto in precedenza:

(A<B,C> D, E<F,G> H) = e;

Si tratta di una dichiarazione di decostruzione che dichiara due variabili, la prima delle quali è di tipo A<B,C> e
denominata D . In altre parole, il valore letterale di tupla contiene due espressioni, ciascuna delle quali è
un'espressione di dichiarazione.
Per semplicità della specifica e del compilatore, propongo che questo valore letterale di tupla venga analizzato
come tupla con due elementi ovunque venga visualizzato (indipendentemente dal fatto che venga visualizzato
sul lato sinistro di un'assegnazione). Si tratta di un risultato naturale della risoluzione dell'ambiguità descritta
nella sezione precedente.
Criteri di ricerca
Criteri di ricerca introduce un nuovo contesto in cui si verifica l'ambiguità del tipo di espressione. In precedenza,
il lato destro di un is operatore era un tipo. A questo punto può essere un tipo o un'espressione e, se è un tipo,
può essere seguito da un identificatore. Questo può, tecnicamente, modificare il significato del codice esistente:

var x = e is T < A > B;

Questa operazione può essere analizzata in base alle regole di C# 6 come

var x = ((e is T) < A) > B;

Tuttavia, in base alle regole di C# 7 (con la risoluzione dell'ambiguità proposta in precedenza) verrebbe
analizzata come

var x = e is T<A> B;

che dichiara una variabile B di tipo T<A> . Fortunatamente, i compilatori nativi e Roslyn hanno un bug in cui
forniscono un errore di sintassi sul codice C# 6. Questa particolare modifica di rilievo non costituisce pertanto
un problema.
La corrispondenza dei modelli introduce token aggiuntivi che dovrebbero guidare la risoluzione dell'ambiguità
verso la selezione di un tipo. Gli esempi seguenti di codice C# 6 valido esistente verrebbero interrotti senza
regole di risoluzione dell'ambiguità aggiuntive:

var x = e is A<B> && f; // &&


var x = e is A<B> || f; // ||
var x = e is A<B> & f; // &
var x = e is A<B>[]; // [

Modifica proposta alla regola di risoluzione dell'ambiguità


Suggerisco di rivedere la specifica per modificare l'elenco di token distinguere da

( ) ] } : ; , . ? == != | ^

su

( ) ] } : ; , . ? == != | ^ && || & [

E, in determinati contesti, consideriamo l' identificatore come token distinguere. Questi contesti sono i casi in cui
la sequenza di token da ambiguità è immediatamente preceduta da una delle parole chiave is , o o si case
out verifica durante l'analisi del primo elemento di un valore letterale di tupla (nel qual caso i token sono
preceduti da ( o : e l'identificatore è seguito da un , ) o da un elemento successivo di un valore letterale di
tupla.
Regola di risoluzione dell'ambiguità modificata
La regola di risoluzione dell'ambiguità riveduta sarà simile alla seguente

Se una sequenza di token può essere analizzata (nel contesto) come nome semplice (§ 7.6.3), accesso ai
membri (§ 7.6.5) o puntatore-membro-Access (§ 18.5.2) che termina con un tipo-argument-list (§ 4.4.1), il
token immediatamente dopo il > token di chiusura viene esaminato, per verificare se è
Uno tra ( ) ] } : ; , . ? == != | ^ && || & [ ; o
Uno degli operatori relazionali < > <= >= is as ; oppure
Parola chiave di query contestuale visualizzata all'interno di un'espressione di query. o
In determinati contesti consideriamo l' identificatore come token distinguere. Questi contesti sono i casi
in cui la sequenza di token ambiguità è immediatamente preceduta da una delle parole chiave is ,
case o out , o si verifica durante l'analisi del primo elemento di un valore letterale di tupla (nel qual
caso i token sono preceduti da ( o : e l'identificatore è seguito da un , ) o da un elemento
successivo di un valore letterale di tupla.
Se il token seguente è incluso in questo elenco oppure un identificatore in un contesto di questo tipo, il tipo-
argument-list viene mantenuto come parte del nome semplice, l' accesso ai membri o l' accesso ai membri
del puntatore e qualsiasi altra possibile analisi della sequenza di token viene scartata. In caso contrario, l'
elenco di argomenti di tipo non viene considerato parte dell'accesso con nome semplice, accesso ai membri
o puntatore-membro, anche se non è possibile analizzare la sequenza di token. Si noti che queste regole non
vengono applicate durante l'analisi di un elenco di argomenti di tipo in uno spazio dei nomi o un nome di
tipo (§ 3,8).

Modifiche di rilievo dovute a questa proposta


Nessuna modifica di rilievo è nota a causa di questa regola di risoluzione dell'ambiguità proposta.
Esempi interessanti
Ecco alcuni risultati interessanti di queste regole di risoluzione dell'ambiguità:
L'espressione (A < B, C > D) è una tupla con due elementi, ognuno dei quali è un confronto.
L'espressione (A<B,C> D, E) è una tupla con due elementi, il primo dei quali è un'espressione di dichiarazione.
La chiamata M(A < B, C > D, E) ha tre argomenti.
La chiamata M(out A<B,C> D, E) ha due argomenti, il primo dei quali è una out dichiarazione.
L'espressione e is A<B> C utilizza un'espressione di dichiarazione.
L'etichetta case case A<B> C: Usa un'espressione di dichiarazione.

Alcuni esempi di criteri di ricerca


Is-As
È possibile sostituire l'idioma

var v = expr as Type;


if (v != null) {
// code using v
}

Con la forma leggermente più concisa e diretta


if (expr is Type v) {
// code using v
}

Test Nullable
È possibile sostituire l'idioma

Type? v = x?.y?.z;
if (v.HasValue) {
var value = v.GetValueOrDefault();
// code using value
}

Con la forma leggermente più concisa e diretta

if (x?.y?.z is Type value) {


// code using value
}

Semplificazione aritmetica
Si supponga di definire un set di tipi ricorsivi per rappresentare le espressioni (per una proposta separata):

abstract class Expr;


class X() : Expr;
class Const(double Value) : Expr;
class Add(Expr Left, Expr Right) : Expr;
class Mult(Expr Left, Expr Right) : Expr;
class Neg(Expr Value) : Expr;

A questo punto è possibile definire una funzione per calcolare la derivata (non ridotta) di un'espressione:

Expr Deriv(Expr e)
{
switch (e) {
case X(): return Const(1);
case Const(*): return Const(0);
case Add(var Left, var Right):
return Add(Deriv(Left), Deriv(Right));
case Mult(var Left, var Right):
return Add(Mult(Deriv(Left), Right), Mult(Left, Deriv(Right)));
case Neg(var Value):
return Neg(Deriv(Value));
}
}

Un semplificatore di espressioni illustra i modelli posizionali:


Expr Simplify(Expr e)
{
switch (e) {
case Mult(Const(0), *): return Const(0);
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
default: return e;
}
}
Procedure dettagliate di C#
02/11/2020 • 4 minutes to read • Edit Online

Le procedure dettagliate forniscono istruzioni specifiche per scenari comuni, che li rende un ottimo strumento
per apprendere l'utilizzo del prodotto o di una determinata area funzionale.
In questa sezione vengono forniti collegamenti a procedure dettagliate di programmazione di C#.

Contenuto della sezione


Elabora le attività asincrone al termine
Viene illustrato come creare una soluzione asincrona usando async e await.
Creazione di un componente Windows Runtime in C# o Visual Basic e chiamata da JavaScript
Viene illustrato come creare un tipo di Windows Runtime, comprimerlo in un componente Windows
Runtime e quindi chiamare il componente da un'app di Windows 8. x Store compilata per Windows
usando JavaScript.
Programmazione di Office (C# e Visual Basic)
Viene illustrato come creare una cartella di lavoro di Excel e un documento di Word usando C# e Visual
Basic.
Creazione e utilizzo di oggetti dinamici (C# e Visual Basic)
Viene illustrato come creare un oggetto personalizzato che espone dinamicamente il contenuto di un file
di testo e come creare un progetto che usi la libreria IronPython .
Creazione di un controllo composito con Visual C #
Illustra la creazione di un semplice controllo composito e l'estensione delle sue funzionalità mediante
ereditarietà.
Creazione di un controllo Windows Forms che sfrutta le funzionalità della fase di progettazione di Visual
Studio
Viene illustrato come creare una finestra di progettazione personalizzata per un controllo personalizzato.
Eredità da un controllo Windows Forms con Visual C #
Viene illustrata la creazione di un semplice pulsante ereditato. Questo pulsante eredita le funzionalità dal
pulsante Windows Form standard ed espone un membro personalizzato.
Debug di controlli di Windows Forms personalizzati in fase di progettazione
Viene descritto come eseguire il debug del comportamento in fase di progettazione del controllo
personalizzato.
Procedura dettagliata: eseguire attività comuni utilizzando le azioni della finestra di progettazione
Vengono illustrate alcune delle attività comunemente svolte quali l'aggiunta o la rimozione di una scheda
in un TabControl , l'ancoraggio di un controllo al relativo elemento padre e la modifica dell'orientamento
di un controllo SplitContainer .
Scrittura di query in C# (LINQ)
Illustra le funzionalità del linguaggio C# usate per scrivere espressioni di query LINQ.
Manipolazione dei dati (C#) (LINQ to SQL)
Descrive uno scenario LINQ to SQL per l'aggiunta, la modifica e l'eliminazione di dati in un database.
Modello a oggetti e query semplici (C#) (LINQ to SQL)
Viene illustrato come creare una classe di entità e una semplice query per filtrare la classe di entità.
Utilizzo solo di stored procedure (C#) (LINQ to SQL)
Viene illustrato come usare LINQ to SQL per accedere ai dati eseguendo solo stored procedure.
Esecuzione di query tra relazioni (C#) (LINQ to SQL)
In questa procedura dettagliata viene descritto l'utilizzo delle associazioni LINQ to SQL per rappresentare
relazioni di chiave esterna nel database.
Scrittura di un visualizzatore in C #
Viene illustrato come scrivere un visualizzatore semplice usando C#.

Sezioni correlate
Esempi e procedure dettagliate per la distribuzione
Fornisce esempi dettagliati di scenari di distribuzione comuni.

Vedere anche
Guida per programmatori C#
Visual Studio Samples

Potrebbero piacerti anche