Libro C#
Libro C#
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
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
namespace Acme.Collections
{
public class Stack<T>
{
Entry _top;
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; }
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 :
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:
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; }
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:
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 :
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 :
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.
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();
}
Classi e struct possono implementare più interfacce. Nell'esempio seguente la classe EditBox implementa
IControl e IDataBound .
interface IDataBound
{
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:
È 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
}
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:
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 byte R;
public byte G;
public byte 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:
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 .
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;
}
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.
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);
int x = 3, y = 4, z = 5;
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++;
}
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);
}
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.
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 .
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)");
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.
T[] _items;
int _count;
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à:
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;
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> .
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.
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
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.
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 :
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.
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 };
L' foreach istruzione può essere utilizzata per enumerare gli elementi di qualsiasi raccolta. Il codice seguente
enumera la matrice dall'esempio precedente:
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:
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.
class Multiplier
{
double _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;
}
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:
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:
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;
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);
if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}
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.
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.
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.
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.
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.
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.
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);
// 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);
}
//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);
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);
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);
}
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);
}
OrderPrecedence();
}
}
}
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:
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:
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 .
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:
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:
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.
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.
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");
}
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");
}
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.
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);
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.
È 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.
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.
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.
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:
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:
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()}!");
}
namespace list_tutorial
{
class Program
{
static void Main(string[] args)
{
WorkingWithStrings();
}
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
}
}
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:
fibonacciNumbers.Add(previous + previous2);
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.
namespace classes
{
public class BankAccount
{
public string Number { get; }
public string Owner { get; set; }
public decimal Balance { get; }
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.
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):
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:
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++;
namespace classes
{
public class Transaction
{
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }
A questo punto, aggiungere un List<T> di oggetti Transaction alla classe BankAccount . Aggiungere la
dichiarazione seguente dopo il costruttore nel file BankAccount.cs :
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:
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:
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:
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 :
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:
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());
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.
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:
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 :
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:
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 :
Aggiungere il codice seguente a LineOfCreditAccount . Il codice nega il saldo per calcolare un importo di
interesse positivo che viene prelevato dall'account:
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:
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 :
Verificare i risultati. A questo punto, aggiungere un set di codice di test simile al seguente LineOfCreditAccount :
Quando si aggiunge il codice precedente ed Esegui il programma, verrà visualizzato un errore simile al
seguente:
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:
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:
Dopo l'estensione della BankAccount classe, è possibile modificare il LineOfCreditAccount costruttore per
chiamare il nuovo costruttore di base, come illustrato nel codice seguente:
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:
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:
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.
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:
È 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:
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):
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.
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);
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 :
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.
È 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:
È 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à:
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
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.
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 :
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.
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:
Per visualizzare i risultati, è possibile eseguire nuovamente l'applicazione. Verrà visualizzato un output simile al
seguente:
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:
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> .
using System;
using System.Threading.Tasks;
Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
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.",
};
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();
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.
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();
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
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}");
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}");
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:
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:
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.
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"):
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:
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:
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:
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:
Sono disponibili 16 bracci di cambio totali per il riempimento. Quindi, test e semplificazione.
È stato fatto un metodo simile al seguente?
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.
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:
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:
Viene quindi creata Vegetable un'istanza della classe denominata item usando l' new operatore e
specificando un nome per il costruttore: Vegetable
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;
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.
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.
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 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.
{<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:
// 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.
{<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:
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.
// 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 illustrato nell'esempio, è possibile usare un'istanza di FormattableString per generare più stringhe di
risultato per diverse impostazioni cultura.
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.
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.
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
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;
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:
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":
È 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.
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:
Una fixture di base di overhead può implementare questa interfaccia come illustrato nel codice seguente:
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:
È 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:
Un tipo di luce diverso può supportare un protocollo più sofisticato. Può fornire la propria implementazione per
TurnOnFor , come illustrato nel codice seguente:
A differenza dei metodi della classe virtuale che eseguono l'override, la dichiarazione di TurnOnFor nella
HalogenLight classe non usa la override parola chiave.
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 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 override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
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.
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.
È possibile recuperare l'ultima parola con l'indice ^1 . Aggiungere il codice seguente sotto l'inizializzazione:
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:
Gli esempi seguenti creano intervalli aperti alle estremità, per l'inizio, la fine o entrambe:
È anche possibile dichiarare gli intervalli o gli indici come variabili. In seguito la variabile può essere usata
all'interno dei caratteri [ e ] :
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;
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:
In tutti i casi, l'operatore di intervallo per Array alloca una matrice per archiviare gli elementi restituiti.
(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);
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.
<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.
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
}
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
}
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>();
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:
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);
namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }
Quindi aggiungere un metodo static per la creazione di nuovi partecipanti tramite la generazione di un ID
casuale:
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 :
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 :
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);
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 :
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:
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.
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.
#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:
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:
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);
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:
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; }
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.
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 :
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:
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:
// Something else
default:
break;
}
}
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}
// 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.
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.
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;
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.
Il codice iniziale elabora ogni pagina quando viene recuperata, come illustrato nel codice seguente:
finalResults.Merge(issues(results)["nodes"]);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
È 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;
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:
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");
}
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;
È possibile ottenere il codice per l'esercitazione completata dal repository DotNet/docs nella cartella
CSharp/Tutorials/AsyncStreams .
È 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.
namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}
namespace LiveryRegistration
{
public class Taxi
{
public int Fares { 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 .
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();
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.
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
{
// ...
// ...
};
vehicle switch
{
// ...
// ...
};
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
{
// ...
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,
{ } => 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
},
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.
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
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:
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:
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:
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:
È 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:
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
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:
Eseguire il programma, usando dotnet run in modo da poter visualizzare ogni riga visualizzata nella console.
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;
}
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):
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à:
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;
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.
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:
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();
}
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:
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.
È 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:
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();
È 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.
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:
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:
Compilare l'applicazione ed eseguirla. Verranno stampati i nomi dei repository che fanno parte di .NET
Foundation.
[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 :
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();
[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:
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; }
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);
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.
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.
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 A
{
public void Method1()
{
// Method implementation.
}
}
public class B : A
{ }
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();
}
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;
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:
È 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;
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;
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.
classe Object
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;
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;
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;
È 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;
if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;
Type = type;
}
Un costruttore
Poiché la classe Publication è abstract , non è possibile crearne un'istanza direttamente dal codice,
come nell'esempio seguente:
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 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;
}
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-character string.");
Currency = currency;
return oldValue;
}
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;
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)}");
}
È 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;
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;
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.
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
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 };
// 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
}
}
}
È 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:
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 };
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:
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
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));
} 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.
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;
// 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");
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");
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();
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();
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".
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.
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.
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.
[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()
{ }
}
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] ).
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 .
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
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 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:
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:
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:
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:
Infine, registra le with espressionidi supporto. Un * with expression_ _ indica al compilatore di creare una
copia di un record, ma _WITH proprietà specificate modificate:
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:
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 .
I chiamanti possono usare la sintassi dell'inizializzatore di proprietà per impostare i valori, mantenendo al
tempo stesso l'immutabilità:
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.
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> .
In alternativa, con le parentesi facoltative per renderlo chiaro con and precedenza maggiore di 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.
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:
Un altro ottimo utilizzo di questa funzionalità consiste nel combinarlo con proprietà solo init per inizializzare un
nuovo oggetto:
È 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.
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() :
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:
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:
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 .
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 :
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:
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:
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:
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.
int M()
{
int y;
LocalFunction();
return y;
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);
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:
È 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:
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.
Il codice seguente crea un intervallo secondario con "lazy" e "dog". Include words[^2] e words[^1] . L'indice
finale words[^0] non è incluso:
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"
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.
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 }
};
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 .
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:
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:
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 :
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"
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
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:
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à:
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();
}
Se il programma non restituisce un codice di uscita, è possibile dichiarare un metodo Main che restituisce una
classe Task:
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:
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:
return longRunningWorkImplementation();
[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.
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
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.
Per altre informazioni, vedere la sezione Valore letterale predefinito dell'articolo Operatore default.
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 _ :
Il separatore di cifre può essere usato anche con i tipi decimal , float e double :
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:
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:
public class D : B
{
public D(int i) : base(i, out var j)
{
Console.WriteLine($"The value of 'j' is {j}");
}
}
È possibile dichiarare il valore restituito come ref e modificare tale valore nella matrice, come illustrato nel
codice seguente:
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:
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();
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();
Ora la stessa sintassi può essere applicata alle matrici dichiarate con stackalloc :
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.
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.
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 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:
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:
Nuovo codice:
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
int a = 5;
int b = a + 2; //OK
// 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;
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:
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.
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:
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:
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:
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:
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:
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:
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à.
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.
In altri casi, il tipo in fase di compilazione è diverso, come illustrato nei due esempi seguenti:
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
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;
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.
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
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
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
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:
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.
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.
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.
class Student
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { 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; }
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; }
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:
[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!;
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!");
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.
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
// 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.
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:
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.
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 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:
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:
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.
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;
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;
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.
È 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 .
È anche possibile usare la parola chiave var individualmente con una o con tutte le dichiarazioni di
variabili all'interno delle parentesi.
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;
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
È quindi possibile decostruire un'istanza della classe Person denominata p con un'assegnazione simile alla
seguente:
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 void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
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 void Deconstruct(out string fname, out string mname, out string lname, out int age)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
public void Deconstruct(out string lname, out string fname, out string mname, out decimal income)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
income = AnnualIncome;
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
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";
}
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; }
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.
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.
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;
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.
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;
}
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;
È 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.
using System;
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;
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;
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;
using System;
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;
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}'");
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;
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;
}
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 .
Il chiamante può quindi usare la tupla restituita con codice simile al seguente:
È 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:
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;
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();
Console.WriteLine($"Result: {result}");
}
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.
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à.
La definizione di una proprietà contiene le dichiarazioni di una funzione di accesso get e set che recupera e
assegna il valore della proprietà:
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:
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:
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à:
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 :
È 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:
Questa funzionalità viene in genere usata per l'inizializzazione delle raccolte esposte come proprietà di sola
lettura:
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:
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; }
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:
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;
}
}
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; }
[field:NonSerialized]
public int Id { get; set; }
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.
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.
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:
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.
page[index] = value;
}
}
È 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 ArgsActions
{
readonly private Dictionary<string, Action> argsActions = new Dictionary<string, Action>();
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.
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>>;
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:
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:
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.
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
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 void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
// <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;
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:
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:
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;
}
// constructor
public GenericList()
{
head = null;
}
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>();
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
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.
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.
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:
Non è necessario scegliere l'uno o l'altro. È possibile specificare qualsiasi numero di istruzioni yield return
necessarie per soddisfare le esigenze del metodo:
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:
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:
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:
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:
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:
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.
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.
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.
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:
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.
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:
Quando il metodo usato come destinazione di delegato è un metodo piccolo spesso si usa la sintassi
dell'espressione lambda per eseguire l'assegnazione:
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.
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:
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:
È possibile notare che per ogni tipo Predicate , esiste un tipo Func strutturalmente equivalente, ad esempio:
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:
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 è:
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.
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.
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:
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
}
Usare quindi l'argomento Severity per filtrare i messaggi inviati all'output del log.
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.
Dopo aver creato questa classe, è possibile crearne un'istanza che associa il relativo metodo LogMessage al
componente Logger:
Le due operazioni non si escludono a vicenda. È possibile associare entrambi i metodi di log e generare
messaggi nella console e in un file:
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.
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.
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.
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:
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 += :
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.
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:
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.
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:
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 :
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:
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:
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.
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:
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.
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:
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:
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.
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()
{
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#.
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}";
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;
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 .
// 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
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();
List<City> largeCitiesList =
(from country in countries
from city in country.Cities
where city.Population > 10000
select city)
.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:
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;
var queryCountryGroups =
from country in countries
group country by country.Name[0];
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 .
IEnumerable<City> queryCityPop =
from city in cities
where city.Population < 200000 && city.Population > 100000
select city;
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;
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.
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.
// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// 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.
// 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:
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:
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.
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;
}
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
IEnumerable<string> myQuery2;
// QueryMethod2 returns a query as the value of its out parameter.
app.QueryMethod2(ref nums, out myQuery2);
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;
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.
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:");
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];
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.
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;
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;
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;
/* 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;
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.
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()
});
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 !
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);
}
// 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;
}
// Flag to indicate the source iterator has reached the end of the source sequence.
internal bool isLastSourceElement = false;
// The end and beginning are the same until the list contains > 1 elements.
tail = head;
// 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;
// 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();
}
}
}
}
// 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;
// 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;
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.
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
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.
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" };
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 .
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 } };
// 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;
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; }
}
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 };
// 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 };
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" };
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.
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" };
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" };
// 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);
}
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; }
}
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; }
}
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
};
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:
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:
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; }
}
void CrossJoin()
{
var crossJoinQuery =
from c in categories
from p in products
select new { c.ID, p.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");
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? :
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;
}
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" };
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à).
Il codice esprime l'intento (scaricando i dati in modo asincrono) senza rimanere impantanati nell'interazione con
Task gli oggetti.
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.
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.
[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");
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();
// 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;
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
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.
}
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() .
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. . .
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)
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; }
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:
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.
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:
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 .
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:
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.
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:
case "square":
return new Square(4);
case "large-circle":
return new Circle(12);
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;
}
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.
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.
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);
Per fare in modo che i chiamanti non modifichino l'origine, restituire il valore per ref readonly :
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:
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.
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.
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.
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:
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:
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.
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:
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.
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:
if (addFive.NodeType == ExpressionType.Lambda)
{
var lambdaExp = (LambdaExpression)addFive;
Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
}
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#.
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 :
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");
}
}
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
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:
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:
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:
// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node)
{
this.node = node;
}
// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
this.node = node;
}
// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node)
{
this.node = node;
}
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:
Ora che è stata creata un'implementazione del visitatore più generale, è possibile visitare ed elaborare più tipi
diversi di espressioni.
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:
Creare un visitatore per questa somma ed eseguire il visitatore per visualizzare l'output seguente:
È 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):
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.
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:
Creazione di nodi
Iniziamo con un esempio relativamente semplicemente, usando l'espressione di addizione già impiegata nelle
sezioni seguenti:
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 :
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:
Infine, inserire la chiamata al metodo in un'espressione lambda e verificare che gli argomenti nell'espressione
lambda siano stati definiti:
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.
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.
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.
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);
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.
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:
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 :
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
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).
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>
<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;
}
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.
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;
}
}
<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;
}
}
<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;
}
}
<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
{
}
/*
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;
}
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;
}
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.
<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");
}
}
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";
}
}
Output
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.
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.
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(' ');
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(' ');
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' };
Istanze consecutive di un separatore generano una stringa vuota nella matrice di output:
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).
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.
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:
In alcune espressioni risulta più semplice concatenare le stringhe usando l'interpolazione di stringhe, come
illustrato nel codice seguente:
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.
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." };
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." };
È 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.
string factMessage = "Extension methods have all the capabilities of regular static methods.";
// 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)");
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.
string factMessage = "Extension methods have all the capabilities of regular static methods.";
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.
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.
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 '_':
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.
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:
Il metodo StringBuilder.ToString restituisce una stringa non modificabile con il contenuto dell'oggetto
StringBuilder.
string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);
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.
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.
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.";
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:
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.
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:
I confronti linguistici dipendono dalle impostazioni cultura correnti e dal sistema operativo. Prendere in
considerazione questo quando si lavora con i confronti tra stringhe.
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
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);
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}.");
}
}
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
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));
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.
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
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);
}
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);
double d = 9.78654;
UseAsWithNullable(d);
}
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.
TIP
Prima di creare un analizzatore personalizzato, consultare le interfacce predefinite. Per altre informazioni, vedere regole di
stile del codice.
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 :
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.
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.
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.
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à π .
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.
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.
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.
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.
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 :
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.
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 :
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 :
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:
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 :
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:
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 :
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.
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:
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:
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.
È 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:
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):
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:
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 :
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 :
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 :
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:
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:
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 :
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 :
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:
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 :
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:
Aggiungere poi un altro filtro per restituire solo i metodi pubblici che restituiscono string :
Selezionare solo la proprietà del nome e solo i nomi distinti rimuovendo qualsiasi overload:
È anche possibile creare la query completa usando la sintassi di query LINQ e quindi visualizzare tutti i nomi di
metodo nella console:
È 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.
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 :
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 :
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:
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 :
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:
Eseguire di nuovo il programma. Questa volta l'albero importa correttamente lo spazio dei nomi
System.Collections.Generic .
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;
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:
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:
Se si vuole esplorare in autonomia, considerare di estendere l'esempio completato per questi tipi di
dichiarazioni di variabili:
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:
A questo punto, aggiungere l'istruzione seguente per eseguire il binding dell'espressione dell'inizializzatore:
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());
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);
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:
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
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:
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.
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:
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
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:
int x = 0;
Console.WriteLine(x);
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:
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.
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":
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:
Si noteranno delle sottolineature rosse ondulate nel codice appena aggiunto sul simbolo MakeConstAsync .
Aggiungere una dichiarazione per MakeConstAsync , come il codice seguente:
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 :
// 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);
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.
[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);
}
}
}";
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 .
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:
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:
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:
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:
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.
// 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 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.
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:
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:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
string s = ""abc"";
}
}
}";
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;
}
}
}";
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:
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());
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#.
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.
Sezioni correlate
Introduzione a C#
Guida per programmatori C#
Riferimenti per C#
Esempi ed esercitazioni
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.
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!
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.
È 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.
Può restituire anche un valore intero. Il valore integer è il codice di uscita per l'applicazione.
-oppure-
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!");
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
{
}
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
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
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.
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
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.
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.
}
Per accodare stringhe nei cicli, specialmente quando si lavora con grandi quantità di testo, usare un
oggetto StringBuilder.
// 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.
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.
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.
// 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.
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();
}
}
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.
Operatore New
Usare il modulo conciso della creazione dell'istanza di oggetto, con la tipizzazione implicita, come
illustrato nella dichiarazione seguente.
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());
};
}
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.
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 };
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.
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:
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 .
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:
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:
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.
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;
}
// 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
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:
executable.exe a b c "a"
"b"
"c"
"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}");
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
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:
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
Il vantaggio della nuova sintassi è che il compilatore genera sempre il codice corretto.
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
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.
Raccolte (C#) Vengono descritti alcuni dei tipi di raccolte disponibili in .NET.
Viene mostrato come usare raccolte semplici e raccolte di
coppie chiave/valore.
Alberi delle espressioni (C#) Viene illustrato come è possibile usare gli alberi delle
espressioni per abilitare la modifica dinamica del codice
eseguibile.
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.
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");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
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.
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.
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");
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:
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:
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.
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 .
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");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
return toast;
}
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.
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
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.
Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");
DoIndependentWork();
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 .
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.
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 .
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
return hours;
}
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 .
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)
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.
Vedere anche
async
await
Programmazione asincrona
Panoramica asincrona
Tipi restituiti asincroni (C#)
02/11/2020 • 14 minutes to read • Edit Online
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.");
}
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:
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);
Console.WriteLine(message);
}
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
È 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.
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;
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
await secondHandlerFinished;
}
class Program
{
static readonly Random s_rnd = new Random();
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
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();
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.
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
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;
}
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
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();
Console.WriteLine("Application ending.");
}
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
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
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
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
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();
try
{
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}
Console.WriteLine("Application ending.");
}
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
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à.
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
};
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.
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 .
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
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à.
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.
downloadTasks.Remove(finishedTask);
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
};
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
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
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.
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";
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
Console.WriteLine(text);
}
return sb.ToString();
}
writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}
await Task.WhenAll(writeTaskList);
}
try
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();
var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
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.
}
[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;
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.
[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]
VA LO RE DI DEST IN A Z IO N E SI A P P L IC A A
event Evento
VA LO RE DI DEST IN A Z IO N E SI A P P L IC A A
property Proprietà
È 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#.
// applies to method
[method: ValidatedContract]
int Method2() { 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#).
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;
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:
[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:
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;
// Default value.
version = 1.0;
}
class TestAuthorAttribute
{
static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
// 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.
[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
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.
È 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 .
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" };
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.
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}
};
// Output:
// Tadpole 400
// Pinwheel 25
// Milky Way 0
// Andromeda 3
}
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:
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 :
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.
L'esempio seguente crea una raccolta Dictionary ed esegue l'iterazione nel dizionario usando un'istruzione
foreach .
return elements;
}
theElement.Symbol = symbol;
theElement.Name = name;
theElement.AtomicNumber = atomicNumber;
Per usare invece un inizializzatore di raccolta per compilare la raccolta Dictionary , è possibile sostituire i
metodi BuildDictionary e AddToDictionary con il metodo seguente.
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.
// LINQ Query.
var subset = from theElement in elements
where theElement.AtomicNumber < 22
orderby theElement.Name
select theElement;
// Output:
// Calcium 20
// Potassium 19
// Scandium 21
}
// Output:
// blue 50 car4
// blue 30 car5
// blue 20 car1
// green 50 car7
// green 10 car3
// red 60 car6
// red 50 car2
}
return compare;
}
}
// Collection class.
public class AllColors : System.Collections.IEnumerable
{
Color[] _colors =
{
new Color() { Name = "red" },
new Color() { Name = "blue" },
new Color() { Name = "green" }
};
// Custom enumerator.
private class ColorEnumerator : System.Collections.IEnumerator
{
private Color[] _colors;
private int _position = -1;
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.
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.
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) { }
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.
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.
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();
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.
È 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.
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#).
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.
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#).
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.
È 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.
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;
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.
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 { }
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#).
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);
}
}
PrintFullName(employees);
}
}
class Program
{
IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());
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.
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(); }
L'esempio di codice seguente viene illustra la conversione implicita tra la firma del metodo e il tipo di delegato.
Per altri esempi, vedere Uso della varianza nei delegati (C#) e Uso della varianza per i delegati generici Func e
Action (C#).
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 .
È 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.
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.
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
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();
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)
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
public Form1()
{
InitializeComponent();
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#).
}
}
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.
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.
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 .
// Prints True.
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.
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.
return base.VisitBinary(b);
}
}
Console.WriteLine(modifiedExpr);
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:
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.
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" };
// 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);
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.
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:
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
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.
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
*/
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
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
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
Operatori checked
Gli operatori checked vengono visualizzati con il simbolo # davanti all'operatore. Ad esempio, l'operatore di
addizione checked viene visualizzato come #+ .
Esempi
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.
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();
}
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");
Console.ReadKey();
}
// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
}
// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}
// 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 }
Console.ReadKey();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
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#.
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()
{
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.
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:
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>.
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;
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.
IEnumerable<Customer> customerQuery =
from cust in customers
where cust.City == "London"
select cust;
Per altre informazioni, vedere relazioni tra i tipi nelle operazioni di query LINQ.
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.
//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:
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;
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.
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":
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.
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; }
}
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 :
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}},
};
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 query syntax.
IEnumerable<string> output =
from rad in radii
select $"Area for a circle with a radius of '{rad}' = {rad * rad * Math.PI:F2}";
*/
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.
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.
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.
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.
//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);
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.
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 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.
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:
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 });
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:
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.
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.
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 .
// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael
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 :
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.
3. Modificare la stringa di formato WriteLine in modo che sia possibile vedere i punteggi:
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 :
// 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
var studentQuery3 =
from student in students
group student by student.Last[0];
// 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;
// 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;
// Output:
// Omelchenko Svetlana
// O'Donnell Claire
// Mortensen Sven
// Garcia Cesar
// Fakhouri Fadi
// Feng Hanying
// Garcia Hugo
// Adams Terry
// Zabokritski Eugene
// Tucker Michael
var studentQuery6 =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
select totalScore;
// Output:
// Class average score = 334.166666666667
// 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 };
// 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(' ');
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.
GroupBy group … by
-oppure-
group … by … into …
OrderBy<TSource,TKey>(IEnumerable<TSource>, orderby
Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.
Select select
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>)
Per ulteriori informazioni, vedere clausola OrderBy.
Where 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
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
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
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>
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
Queryable.OrderByDescendi
ng
Queryable.ThenByDescendi
ng
the
fox
quick
brown
jumps
*/
the
quick
jumps
fox
brown
*/
fox
the
brown
jumps
quick
*/
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
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.
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" };
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.
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
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
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; }
}
// 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;
class Market
{
public string Name { get; set; }
public string[] Items { get; set; }
}
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
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" };
an
apple
a
day
the
quick
brown
fox
*/
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; }
}
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
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
class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}
class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
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; }
}
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
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
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
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
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
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
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
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.
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
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
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.
È 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:
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.";
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.";
// 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;
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.
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";
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\";
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.
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);
Alcuni tipi di operazioni di query in C#, ad esempio Except, Distinct, Union e Concat, possono essere espressi
solo nella sintassi basata su metodo.
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");
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.
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");
// 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());
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");
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}\":");
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.
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).
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()
};
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");
// 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.
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.
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.
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.
Esempio
class SumColumns
{
static void Main(string[] args)
{
string[] lines = System.IO.File.ReadAllLines(@"../../../scores.csv");
// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60
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 .
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();
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();
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\";
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";
// 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;
// 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);
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.
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#";
// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();
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.
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
{
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.");
}
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();
}
Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes",
startFolder, longestFile.FullName, longestFile.Length);
Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes",
startFolder, smallestFile.FullName, smallestFile.Length);
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.
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();
int i = queryDupFiles.Count();
PageOutput<PortableKey, string>(queryDupFiles);
}
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;
// 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());
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.
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\";
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:
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 }
});
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.
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 };
//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:
int[] numbers2 = { 1, 2, 3, 4, 5 };
È 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);
// With the generic overload, you can also use numeric properties of objects.
/*
This code produces the following output:
Integer: Median = 3
String: Median = 4
*/
È 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:
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.
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:
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 .
L'output è: System.Int32 .
L'esempio seguente usa la reflection per ottenere il nome completo dell'assembly caricato.
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.
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.
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
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.
Questo esempio scrive l'oggetto da una classe in un file XML usando la classe XmlSerializer.
Esempio
public class XMLWrite
{
writer.Serialize(file, overview);
file.Close();
}
}
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;
}
Console.WriteLine(overview.title);
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.
[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }
[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
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:
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.
[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; }
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:
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 :
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
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
// Assignment statement.
counter = 1;
C AT EGO RY PA RO L E C H IAVE C #/ N OT E
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.
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:
// 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;
if (b == true)
{
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
}
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
Metodo 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;
class Example
{
static void Main()
{
Person p = new Person("Mandy", "Dejesus");
Console.WriteLine(p);
p.DisplayName();
}
}
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:
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;
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 .
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;
using System;
using System.Collections.Generic;
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.
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 };
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()) };
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; }
// Assign b to a.
b = a;
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();
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.
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; }
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;
/* 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; }
class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;
pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);
/* 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; }
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
// Demonstrate that two value type instances never have reference equality.
#region ValueTypes
#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
#endregion
/* 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
int a = 5;
int b = a + 2; //OK
// 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;
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:
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.
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:
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:
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:
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:
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:
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:
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à.
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.
In altri casi, il tipo in fase di compilazione è diverso, come illustrato nei due esempi seguenti:
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
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;
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.
// 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:
class Animal
{
public void Eat() => System.Console.WriteLine("Eating.");
class UnSafeCast
{
static void Main()
{
Test(new Mammal());
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.
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
/*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;
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;
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);
}
}
}
Se si modifica l'istruzione:
int j = (short) o;
in:
int j = (int) o;
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.
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 };
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.
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.
try
{
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: -105
try
{
int m = Int32.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.
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;
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;
while (repeat)
{
Console.Write("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive): ");
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
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 .
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.
Esempio
Questo esempio illustra un altro modo per convertire un valore esadecimale string in un Integer, chiamando il
metodo Parse(String, NumberStyles).
Esempio
L'esempio seguente illustra come convertire un tipo string esadecimale in float usando la classe
System.BitConverter e il metodo UInt32.Parse.
// Output: 200.0056
Esempio
L'esempio seguente illustra come convertire una matrice byte in una stringa esadecimale tramite la classe
System.BitConverter.
/*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.
/*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.
class ExampleClass
{
public ExampleClass() { }
public ExampleClass(int v) { }
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);
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;
// 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);
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 .
// 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
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.
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.
7. Aggiornare la dichiarazione di classe per ereditare la classe DynamicObject , come illustrato nell'esempio
di codice seguente.
class ReadOnlyFile : 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
try
{
sr = new StreamReader(p_filePath);
while (!sr.EndOfStream)
{
line = sr.ReadLine();
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)
Try
sr = New StreamReader(p_filePath)
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.
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.");
}
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
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.
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.
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();
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.
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 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:
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:
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.
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;
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.
class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);
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;
}
}
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.
if (p2.Equals(p1))
Console.WriteLine("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:
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// 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;
}
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.
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");
}
}
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()
};
In C# ogni tipo è polimorfico perché tutti i tipi, incluso i tipi definiti dall'utente, ereditano da Object.
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:
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:
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
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();
}
}
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.
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 :
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:
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:
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:
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");
}
}
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 .
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 .
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 .
Aggiungere il modificatore virtual alla definizione di Method1 in BaseClass . Il modificatore virtual può
essere aggiunto prima o dopo l'oggetto public .
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
class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}
// 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();
}
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("----------");
// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
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 .
// 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.
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.
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();
// 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("----------");
// 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();
}
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:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
È 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
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.
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:
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 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.
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.
// 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:
return fahrenheit;
}
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(":");
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;
}
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;
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.
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:
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.
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;
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
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;
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:
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.
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.
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;
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:
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
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;
}
}
I modificatori della proprietà vengono inseriti nella dichiarazione della proprietà stessa. ad esempio:
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;
Esempio
Il codice seguente mostra un programma di test che crea diversi oggetti derivati da Shape e stampa le relative
aree.
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;
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.
class TimePeriod
{
private double _seconds;
class Program
{
static void Main()
{
TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;
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;
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
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
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:
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:
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 .
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 .
Quando si assegna un valore alla proprietà, viene richiamata la funzione di accesso set tramite un argomento
che fornisce il nuovo valore. ad esempio:
È 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 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";
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;
}
}
class TestHiding
{
static void Main()
{
Manager m1 = new Manager();
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:
Il cast (Employee) viene usato per accedere alla proprietà nascosta nella classe di base:
((Employee)m1).Name = "Mary";
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.
//constructor
public Square(double s) => side = s;
//constructor
public Cube(double s) => side = s;
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
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:
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 { }
}
interface IEmployee
{
string Name
{
get;
set;
}
int Counter
{
get;
}
}
// constructor
public Employee() => _counter = ++numberOfEmployees;
}
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:
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.
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:
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 string Id
{
get { return _id; }
set { }
}
}
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.
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
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;
set
{
_age = value;
}
}
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);
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:
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;
Se per modellare le proprietà venissero usati metodi set e get distinti, il codice equivalente sarebbe simile al
seguente:
person.SetAge(person.GetAge() + 1);
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
Esempio
L'esempio seguente mostra una classe semplice con alcune proprietà implementate automaticamente:
// 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"; }
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:
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; }
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.
// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}
// Read-only property.
public string Address { get; }
// Private constructor.
private Contact2(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
/* 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.
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:
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
{
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
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:
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 :
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.
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;
}
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:
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:
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
}
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();
Console.WriteLine($"Result: {result}");
}
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.
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.
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.
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 :
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...
}
}
Nell'esempio precedente viene usato un attributo speciale per supportare il compilatore nell'analisi statica in un
contesto Nullable.
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;
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;
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;
return GetValueAsync();
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;
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:
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 .
return longRunningWorkImplementation();
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.
Un'assegnazione per valore legge il valore di una variabile e la assegna a una nuova variabile:
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 .
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.
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:
Le variabili locali di riferimento devono ancora essere inizializzate quando vengono dichiarate.
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
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 };
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
}
// Passing by reference
static void squareRef(ref int refParameter)
{
refParameter *= refParameter;
}
}
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 .
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);
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 .
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);
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 .
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);
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 .
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]);
}
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]);
}
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 .
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";
}
ClassTaker(testClass);
StructTaker(testStruct);
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 };
È 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.
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.
class ImplicitlyTypedLocals2
{
static void Main()
{
string[] words = { "aPPLE", "BlUeBeRrY", "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:
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:
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.
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;
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
{
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;
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.
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 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();
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.
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;
}
}
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.
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.
Se non si ricorda l'ordine dei parametri ma si conoscono i nomi, è possibile inviare gli argomenti in qualsiasi
ordine.
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
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");
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.
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à.
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);
class ExampleClass
{
private string _name;
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.
var myFormat =
Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting1;
È 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.
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.
2. Aggiungere il codice seguente alla fine del metodo per definire dove visualizzare il testo nel documento e
il testo da visualizzare:
DisplayInWord();
2. Premere CTRL + F5 per eseguire il progetto. Verrà visualizzato un documento di Word contenente il testo
specificato.
// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");
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.
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();
}
// 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: ",");
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.
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 .
static Adult()
{
minimumAge = 18;
}
È anche possibile definire un costruttore statico con una definizione di corpo dell'espressione, come illustrato
nell'esempio seguente.
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 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() { }
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:
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 Employee() { }
Questa classe può essere creata usando una delle istruzioni seguenti:
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:
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 :
L'uso della parola chiave this nell'esempio precedente causa la chiamata di questo costruttore:
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.
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:
Ciò consente di creare oggetti Coords con valori iniziali predefiniti o specifici, come illustrato di seguito:
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:
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;
}
class MainClass
{
static void Main()
{
var p1 = new Coords();
var p2 = new Coords(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;
class TestPerson
{
static void Main()
{
var person = new Person();
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;
class TestShapes
{
static void Main()
{
double radius = 2.5;
double height = 3.0;
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() { }
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:
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;
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.
// 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;
class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);
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;
}
// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}
class TestPerson
{
static void Main()
{
// Create a Person object by using the instance constructor.
Person person1 = new Person("George", 40);
// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());
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;
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:
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.
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 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.
*/
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
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 Cat()
{
}
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];
[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:
Perché la compilazione del codice precedente riesca, il tipo IndexersExample deve avere i membri seguenti:
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 .
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:
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.
È 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
};
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:
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() { }
// Display results.
System.Console.WriteLine(cat.Name);
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();
Console.WriteLine("Address Entries:");
/*
* 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 void Add(TKey key, params TValue[] values) => Add(key, (IEnumerable<TValue>)values);
storedValues.AddRange(values);
}
}
/*
* Prints:
Using second multi-valued dictionary created with a collection initializer using indexing:
Using third multi-valued dictionary created with a collection initializer using indexing:
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 :
Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
}
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116
// Properties.
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
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"
};
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; }
}
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:
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:
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:
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:
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() { }
}
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 { }
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }
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:
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
class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();
Esempio 2
Descrizione
L'esempio seguente dimostra che è anche possibile sviluppare struct e interfacce parziali.
Codice
partial interface ITest
{
void Interface_Test();
}
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.
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 .
// 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 };
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.
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:
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; }
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.
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:
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");
}
}
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();
}
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.
}
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();
}
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:
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
Esempio
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}
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:
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:
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.
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:
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:
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:
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:
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:
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;
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:
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:
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);
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();
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.");
}
class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();
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}!");
}
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);
In C# 2.0 è disponibile un metodo più semplice per scrivere la dichiarazione precedente, come illustrato
nell'esempio seguente.
In C# 2.0 e versioni successive, è anche possibile usare un metodo anonimo per dichiarare e inizializzare un
delegato, come illustrato nell'esempio seguente.
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.
Programmazione efficiente
Dichiarazione di un delegato.
L'istruzione seguente dichiara un nuovo tipo di delegato.
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);
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];
// Alternative syntax.
int[] array3 = { 1, 2, 3, 4, 5, 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:
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:
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
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.
// 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 } } };
// Output:
// 1
// 2
// 3
// 4
// 7
// three
// 8
// 12
// 12 equals 12
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
array5[2, 1] = 25;
Analogamente, nell'esempio seguente viene ottenuto il valore di un elemento specifico della matrice, che viene
assegnato alla variabile elementValue .
Nell'esempio di codice seguente, gli elementi della matrice vengono inizializzati in base ai valori predefiniti (ad
eccezione delle matrici di matrici).
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:
Prima di poter usare jaggedArray , è necessario che siano stati inizializzati i relativi elementi. È possibile
inizializzare gli elementi nel modo seguente:
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:
È inoltre possibile inizializzare la matrice al momento della dichiarazione, come nell'esempio seguente:
È 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:
È 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 ):
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][];
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 :
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:
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.
int[] theArray = { 1, 3, 5, 7, 9 };
PrintArray(theArray);
Nel codice seguente viene illustrata un'implementazione parziale del metodo di stampa.
È possibile inizializzare e passare una nuova matrice in un passaggio, come mostrato nell'esempio seguente.
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));
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:
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 } });
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[]
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.
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.
// Initialize to null.
string message2 = null;
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.
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:
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.
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:
\0 Null 0x0000
\a Avviso 0x0007
\b Backspace 0x0008
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.
// 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.
System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"
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);
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;
// 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);
}
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();
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.
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 di data e ora in .NET Illustra come convertire una stringa come "24/01/2008" in
un oggetto System.DateTime.
Uso della classe StringBuilder Descrive come creare e modificare oggetti stringa dinamici
tramite la classe StringBuilder.
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 .
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];
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.
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
int nextIndex = 0;
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];
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
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] .
// 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();
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;
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
};
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:
// 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;
}
}
/* 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:
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]
{
}
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.
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.
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.
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
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 .
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:
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 :
È anche possibile usare un' espressione lambda per specificare un gestore eventi:
public Form1()
{
InitializeComponent();
this.Click += (s,e) =>
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
}
È 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.
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:
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.
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.
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;
}
class Program
{
static void Main()
{
var pub = new Publisher();
var sub1 = new Subscriber("sub1", pub);
var sub2 = new Subscriber("sub2", pub);
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;
}
// 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 ShapeContainer()
{
_list = new List<Shape>();
}
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();
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à.
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*/));
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;
Console.WriteLine("Drawing a shape.");
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.
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:
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;
}
// constructor
public GenericList()
{
head = null;
}
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>();
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
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:
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.
Provare a usare T come nome del parametro di tipo per i tipi con parametro di tipo di una sola lettera.
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:
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 : <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.
public T FindFirstOccurrence(string s)
{
Node current = head;
T t = null;
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 == .
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.
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
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:
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.
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() .
Per combinare delegati dello stesso tipo, è possibile usare il metodo riportato sopra:
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.
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
}
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 { }
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:
//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:
I tipi generici possono usare più parametri di tipo e vincoli, in questo modo:
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
}
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> .
do
{
Node previous = null;
Node current = head;
swapped = false;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
È possibile specificare più interfacce come vincoli su un unico tipo, in questo modo:
interface IMonth<T> { }
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> { }
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> { }
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:
L'esempio di codice seguente mostra un modo per chiamare il metodo usando int per l'argomento tipo:
È anche possibile omettere l'argomento tipo perché venga dedotto dal compilatore. La chiamata seguente a
Swap equivale alla chiamata precedente:
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>.
È 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>() { }
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>();
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
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:
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;
Il codice che fa riferimento al delegato deve specificare l'argomento tipo della classe che lo contiene, in questo
modo:
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;
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
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> :
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 :
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.
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.
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.
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:
[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:
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
Un attributo che fa riferimento a un parametro di tipo generico provoca un errore in fase di compilazione:
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!");
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.
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.
using System;
Console.WriteLine("Hello, World!");
Anziché:
System.Console.WriteLine("Hello, World!");
namespace AliasExample
{
class TestClass
{
static void Main()
{
generics::Dictionary<string, int> dict = new generics::Dictionary<string, int>()
{
["A"] = 1,
["B"] = 2,
["C"] = 3
};
class Program
{
static void Main(string[] args)
{
// Displays "SampleMethod inside SampleNamespace."
SampleClass outer = new SampleClass();
outer.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
{
}
}
}
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:
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:
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:
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
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");
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.
Sezioni correlate
Per altre informazioni, vedere:
Tipi puntatore
Buffer a dimensione fissa
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 .
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:
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];
}
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);
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 .
[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:
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:
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;
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
[] Indicizza un puntatore.
Per altre informazioni sugli operatori correlati ai puntatori, vedere Operatori correlati ai puntatori.
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.
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.
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.
unsafe
{
// Convert to byte:
byte* p = (byte*)&number;
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 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];
}
}
}
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<T>" , 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.
Sezioni correlate
Per altre informazioni, vedere:
-DOC (elabora i commenti relativi alla documentazione)
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
/// <summary>
/// This property always returns a value < 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
F campo
E evento
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.
/** */
/**
<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
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;
}
/// <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
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
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
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.
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>
/// <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
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>
/// <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
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
<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>
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
{
}
/// <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
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
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
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
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
// 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)
{
}
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
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
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()
{
}
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
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
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
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.
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
Esempio
Nell'esempio riportato di seguito viene illustrato come effettuare un riferimento cref a un tipo generico.
// the following cref shows how to specify the reference, such that,
// the compiler will resolve the reference
/// <summary cref="C{T}">
/// </summary>
class A { }
class Program
{
static void Main() { }
}
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
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
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
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
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
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 .
try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}
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:
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.
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.
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:
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.
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:
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:
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.
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.
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
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.
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:
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.
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)] .
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.
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.
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.
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);
}
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.
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
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;
}
}
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à:
È 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("*.*");
System.IO.Directory.SetCurrentDirectory(@"C:\Users\Public\TestFolder\");
currentDirName = System.IO.Directory.GetCurrentDirectory();
Console.WriteLine(currentDirName);
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";
// 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);
// This example uses a random string for the name, but you also can specify
// a particular name.
//string fileName = "MyNewFile.txt";
// 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;
}
//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.
Sostituire l'istruzione if - else con l'istruzione using riportata nel codice seguente.
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";
// 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!");
}
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";
Esempio
L'esempio seguente illustra come eliminare file e directory.
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.
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.
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()
{
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");
// 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");
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;
file.Close();
System.Console.WriteLine("There were {0} lines.", counter);
// Suspend the screen.
System.Console.ReadLine();
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();
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).
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 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.
2. Aggiungere il seguente codice al metodo Main per creare un elenco bankAccounts contenente due conti.
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.
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();
2. Premere CTRL+F5.
Viene visualizzato un foglio di lavoro di Excel che contiene i dati dei due conti.
// 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();
// 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'.
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.
((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.
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.
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.
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
}
};
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();
// 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();
}
// 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();
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# 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";
// 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#.
namespace IndexedProperties
{
class Program
{
static void Main(string[] args)
{
CSharp2010();
//CSharp2008();
}
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:
namespace WinSound
{
public partial class Form1 : Form
{
private TextBox textBox1;
private Button button1;
[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
}
if (dialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog1.FileName;
PlaySound(dialog1.FileName, new System.IntPtr(), PlaySoundFlags.SND_SYNC);
}
}
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.
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
Imports Microsoft.Office.Interop
class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}
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
}
};
With Me.Application
' Add a new Excel workbook.
.Workbooks.Add()
.Visible = True
.Range("A1").Value = "ID"
.Range("B1").Value = "Balance"
.Range("A2").Select()
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) .
// 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 .
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.
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.
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.
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.
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#.
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
Valori predefiniti
Il compilatore determina un'impostazione predefinita in base a queste regole:
.NET 5. x C# 9.0
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.
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.
<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
csc -langversion:?
Se si sta interrogando l'opzione di compilazione -langversion , verrà stampato un valore simile al seguente:
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;
MutateAndDisplay(p2);
Console.WriteLine($"{nameof(p2)} after passing to a method: {p2}");
}
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 TaggedInteger(int n)
{
Number = n;
tags = new List<string>();
}
var n2 = n1;
n2.Number = 7;
n2.AddTag("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.
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.
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.
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.
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:
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.
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 .
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
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.
double d = 3D;
d = 4d;
d = 3.934_001;
float f = 3_000.5F;
f = 5.4f;
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.
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 .
DA TO
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.
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 .
DA TO
byte sbyte
NOTE
Una conversione numerica esplicita potrebbe causare la perdita di dati o generare un'eccezione, in genere un
OverflowException .
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:
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.
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
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.
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 .
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:
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.
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.
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
}
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.
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:
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;
}
È anche possibile applicare il readonly modificatore ai metodi che eseguono l'override dei metodi
dichiarati in System.Object :
Proprietà e indicizzatori:
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à.
Nel caso dei tipi valore predefiniti, usare i valori letterali corrispondenti per specificare un valore del tipo.
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:
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):
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.
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:
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:
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.
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
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
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 .
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:
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.
È 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:
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:
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
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:
int Display(int s)
{
Console.WriteLine(s);
return s;
}
// Output:
// 1
// 2
// 3
// 4
// False
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;
Il valore predefinito di un tipo di valore Nullable rappresenta null , ovvero un'istanza la cui
Nullable<T>.HasValue proprietà restituisce false .
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
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;
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}");
// 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.
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 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:
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 ("):
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.
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:
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:
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"));
class ExampleClass
{
static dynamic field;
dynamic prop { get; set; }
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:
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);
// 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.
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 .
Esempio
interface ISampleInterface
{
void 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:
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;
}
}
// Property implementation:
public int X { 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);
}
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:
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
{
}
Il frammento di codice seguente mostra dove il compilatore emette avvisi quando si usa questa classe:
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.
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.
È 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:
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:
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;
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
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
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
using System;
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:
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
bool false
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:
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.
mentre
async await by
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
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.
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.
protected
internal
private
protected internal
private protected
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.
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;
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;
// Access is unlimited.
T1.M1.publicInt = 1;
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 :
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
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();
public B MyMethod()
{
// Error: The type B is less accessible
// than the method A.MyMethod.
return new B();
}
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:
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
}
}
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
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;
class PrivateTest
{
static void Main()
{
var e = new Employee2();
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
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();
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;
}
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.
}
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.
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();
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:
// 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.
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
Esempio
In questo esempio, la classe Square deve eseguire un'implementazione di GetArea poiché deriva da Shape :
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:
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; }
}
Nell'esempio precedente, se si prova a creare un'istanza della classe astratta tramite un'istruzione simile alla
seguente:
si ottiene un messaggio di errore che informa che il compilatore non può creare un'istanza della classe astratta
"BaseClass".
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 :
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:
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:
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.
}
}
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:
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;
Esempio
In questo esempio viene illustrato come usare le costanti come variabili locali.
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.
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.
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.
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
[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);
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);
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)
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.
// Contravariant interface.
interface IContravariant<in A> { }
class Program
{
static void Test()
{
IContravariant<Object> iobj = new Sample<Object>();
IContravariant<String> istr = new Sample<String>();
// Contravariant delegate.
public delegate void DContravariant<in A>(A argument);
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:
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;
}
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;
}
}
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'.
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.
// Covariant interface.
interface ICovariant<out R> { }
class Program
{
static void Test()
{
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();
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.
// Covariant delegate.
public delegate R DCovariant<out R>();
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.
Vedere anche
Varianza nelle interfacce generiche
in
Modificatori
override (riferimenti per C#)
02/11/2020 • 4 minutes to read • Edit Online
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;
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
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 SamplePoint()
{
// Initialize a readonly instance field
z = 24;
}
Il tipo restituito può non essere readonly struct . Qualsiasi tipo che può essere restituito da ref può essere
restituito anche da ref readonly .
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
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.
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 :
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:
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.
public Employee4()
{
}
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;
Test.x = 99;
Console.WriteLine(Test.x);
}
}
/*
Output:
0
5
99
*/
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 :
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;
}
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:
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; }
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.
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()
{
}
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;
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;
}
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.
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 #
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. .
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.
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 .
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.
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.");
}
//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.");
}
// 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.
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;
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;
using System;
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;
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;
È 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 .
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 ).
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;
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);
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);
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);
using System;
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
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);
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 .
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:
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++
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++
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.
}
for ( ; ; )
{
// Body of the loop.
}
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:
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:
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:
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.
È anche possibile specificare in modo esplicito il tipo di una variabile di iterazione, come illustrato nel codice
seguente:
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++;
}
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);
}
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;
}
Sample Output:
Enter your selection (1, 2, or 3): 1
Current value is 1
*/
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();
}
/*
* 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;
}
}
/*
* Output:
Current value is 1
Current value is 2
Current value is 3
*/
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);
}
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.");
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.
// 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;
}
}
}
Found:
Console.WriteLine($"The number {myNumber} is found.");
Finish:
Console.WriteLine("End of search.");
Sample Output
Enter the number to search for: 44
The number 44 is found.
End of search.
*/
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;
}
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
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;
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;
using System;
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 .
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.
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.
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 .
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.
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();
}
}
try
{
ProcessString(s);
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
}
}
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
at TryFinallyTest.Main() Exception caught.
* */
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);
}
}
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");
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);
}
}
}
// 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
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;
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());
try
{
// Invalid conversion; obj contains a string, not a numeric type.
i = (int)obj;
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...
}
}
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, which includes variable ten, does not cause
// a compiler error.
int ten = 10;
int i2 = 2147483647 + ten;
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.
// 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;
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);
// 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);
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;
}
È 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:
// The following two assignments are equivalent. Each assigns the address
// of the first element in array arr to pointer p.
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:
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:
Per inizializzare puntatori di tipi diversi, annidare semplicemente le istruzioni fixed come illustrato
nell'esempio seguente.
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:
È possibile allocare memoria nello stack, dove non è soggetta a Garbage Collection e pertanto non deve essere
bloccata. A tale scopo, utilizzare un' stackalloc espressione.
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...
}
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;
decimal appliedAmount = 0;
lock (balanceLock)
{
if (balance >= amount)
{
balance -= amount;
appliedAmount = amount;
}
}
return appliedAmount;
}
lock (balanceLock)
{
balance += amount;
}
}
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
}
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();
}
// 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[]
*/
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.
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) { }
}
Si supponga a questo punto che sia disponibile un altro metodo che usa argomenti per valore. I risultati
cambiano, come illustrato nel codice seguente:
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.
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 .
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.
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.
Per altre informazioni su come passare i tipi di riferimento per valore e per riferimento, vedere Passaggio di
parametri di tipi di riferimento.
Tra il token return e la variabile restituita in un'istruzione return nel metodo. Ad esempio:
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.
È 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.
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.
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.
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
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 .
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);
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.
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.
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 }
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
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
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
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.
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.
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;
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);
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:
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;
using System;
using static System.Math;
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);
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 .
A partire da C# 8,0, è possibile usare la sintassi alternativa seguente per l' using istruzione che non richiede
parentesi graffe:
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):
{
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.";
È 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.";
È 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.
Per altre informazioni sull'eliminazione degli oggetti IDisposable , vedere Uso di oggetti che implementano
IDisposable.
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;
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 .
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;
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
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;
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;
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;
Il codice equivalente senza criteri di ricerca richiede un'assegnazione separata che include un cast esplicito.
using System;
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 ).
using System;
class Program
{
static void Main(string[] args)
{
var d1 = new Dice();
ShowValue(d1);
}
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
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}");
}
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:
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 };
Nell'esempio precedente, la variabile temporanea viene utilizzata per archiviare il risultato di un'operazione
costosa. La variabile può quindi essere usata più volte.
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:
Quando il vincolo new() viene usato con altri vincoli, è necessario specificarlo per ultimo:
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>:
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:
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 :
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:
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:
Si noti che la sintassi usata per descrivere i vincoli dei parametri di tipo per i delegati è uguale a quella dei
metodi:
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";
class TestClass
{
static void Main()
{
Employee E = new Employee();
E.GetInfo();
}
}
/*
Output
Name: John L. Malgraine
SSN: 444-55-6666
Employee ID: ABC567EFG
*/
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)");
}
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.
CalcTax(this);
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));
}
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
*/
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() { }
}
// Returns true.
Console.WriteLine("null == null is {0}", null == null);
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:
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.
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
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:
globale Alias dello spazio dei nomi globale, che altrimenti non è
provvisto di nome.
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.
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;
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;
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; }
}
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.
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);
}
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.
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;
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;
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; }
}
È 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.
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);
}
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;
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; }
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 .
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.
Implementazione tecnica
Il codice seguente restituisce IEnumerable<string> da un metodo iteratore e quindi scorre i relativi elementi.
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);
}
}
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.
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.
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 };
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#).
// 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 };
class CompoundFrom2
{
static void Main()
{
char[] upperCase = { 'A', 'B', 'C' };
char[] lowerCase = { 'x', 'y', 'z' };
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);
}
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 };
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 };
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 };
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 };
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;
}
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:
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:
Esempi di utilizzo di più completi di group con e senza into sono disponibili nella sezione Esempio di questo
articolo.
// 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;
return students;
}
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;
}
return students;
}
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" };
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" };
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()
{
// 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);
}
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
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" };
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();
// 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;
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
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;
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 };
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.
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; }
}
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;
Console.WriteLine("Simple GroupJoin:");
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);
}
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 });
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 };
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
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."
};
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.
Vedere anche
LINQ in C#
in (Riferimenti per C#)
02/11/2020 • 2 minutes to read • Edit Online
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);
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!");
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
È 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.
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.
x.. y Range
x * y, x / y, x % y Moltiplicazione
x + y, x – y Additive
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 OR condizionale
x ?? y Operatore null-coalescing
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.
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
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
double a = 1.5;
Console.WriteLine(a); // output: 1.5
Console.WriteLine(--a); // output: 0.5
Console.WriteLine(a); // output: 0.5
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
Operatore di moltiplicazione *
L'operatore di moltiplicazione * calcola il prodotto degli operandi:
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 :
int a = 13;
int b = 5;
Console.WriteLine((double)a / b); // output: 2.6
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:
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:
Operatore di addizione +
L'operatore di addizione + calcola la somma degli operandi:
È 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:
È 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
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.
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.
Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .
int a = int.MaxValue;
int b = 3;
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:
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:
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 .
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
A partire da C# 8,0, l'operatore unario di suffisso ! è l' operatore che perdona i valori null.
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;
}
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;
}
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.
bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}
L' operatore logico and & Calcola anche l'and logico degli operandi, ma valuta sempre entrambi gli operandi.
bool SecondOperand()
{
Console.WriteLine("Second operand is evaluated.");
return true;
}
L' operatore OR logico | Calcola anche l'OR logico degli operandi, ma valuta sempre entrambi gli operandi.
X Y X& Y X|Y
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:
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
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.
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
Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .
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.
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.
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.
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
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.
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.
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
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
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
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
Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione precedenza
degli operatori dell'articolo operatori c# .
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 ).
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.
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.
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;
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.
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 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:
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
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.
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
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:
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:
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:
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:
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() {}
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 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(…)
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:
numbers.Clear();
display(numbers.Count); // output: 0
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 margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner); // output: 10 20 30 40
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
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:
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
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
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:
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;
byte number = d;
Console.WriteLine(number); // 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.
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
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;
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.
}
}
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
x->y
equivale a
(*x).y
unsafe
{
char* pointerToChars = stackalloc char[123];
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.
unsafe
{
const int Count = 3;
int[] numbers = new int[Count] { 10, 20, 30 };
fixed (int* pointerToFirst = &numbers[0])
{
int* pointerToLast = pointerToFirst + (Count - 1);
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) .
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
}
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
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# .
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
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:
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
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
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:
Espressione lambda con istruzioni, che contiene un blocco di istruzioni come corpo:
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:
Le espressioni lambda dell'espressione possono anche essere convertite nei tipi di albero delle espressioni ,
come illustrato nell'esempio seguente:
È 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:
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).
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.
Non è possibile usare le espressioni lambda dell'istruzione per creare alberi delle espressioni.
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:
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:
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.
È 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";
};
}
Per altre informazioni su come creare e usare i metodi asincroni, vedere programmazione asincrona con Async e
await.
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}");
È 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:
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
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.
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
int gameInput = 5;
game.Run(gameInput);
int anotherJ = 3;
game.updateCapturedLocalVariable(anotherJ);
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.
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):
A partire da C# 6, l' interpolazione di stringhe rappresenta un modo più pratico per formattare le stringhe:
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 + :
x += y
equivale a
x = x + y
int i = 5;
i += 9;
Console.WriteLine(i);
// Output: 14
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.
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 .
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 abbaab = a + b + b + a + a + b;
var aba = a + b + a;
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.
x -= y
equivale a
x = x - y
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.
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 .
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:
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:
L'operatore condizionale si associa all'operando a destra, che significa che un'espressione nel formato
a ? b : c ? d : e
a ? b : (c ? d : e)
TIP
Un modo per ricordarsi che cosa restituisce questo operatore è il seguente:
Console.WriteLine(sinc(0.1));
Console.WriteLine(sinc(0.0));
// Output:
// 0.998334166468282
// 1
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:
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;
string classify;
if (input >= 0)
{
classify = "nonnegative";
}
else
{
classify = "negative";
}
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));
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}");
}
}
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 :
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.
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.
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:
Gli operatori che uniscono i valori null sono associativi a destra. Ovvero espressioni nel formato
a ?? b ?? c
d ??= e ??= f
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;
}
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
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;
}
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:
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:
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 :
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.
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:
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;
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.
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.
Display(InitializeArray<int>(3)); // output: [ 0, 0, 0 ]
Display(InitializeArray<bool>(4, default)); // output: [ False, False, False, False ]
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:
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:
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:
// 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:
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:
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:
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:
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
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 :
È 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:
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 };
creazione di matrici
È possibile usare l'operatore new per creare un'istanza di matrice, come illustrato nell'esempio seguente:
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:
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:
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;
unsafe
{
Console.WriteLine(sizeof(Point*)); // output: 8
}
}
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.
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.
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:
NOTE
In presenza di memoria allocata nello stack, è consigliabile usare il tipo Span<T> o ReadOnlySpan<T> ogni
qualvolta sia possibile.
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 :
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.
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.
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:
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.
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:
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:
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:
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.
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#.
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;
if (x == Yellow || y == Yellow)
{
return Yellow;
}
return Green;
}
public override bool Equals(object obj) => obj is LaunchStatus other && this == other;
public override int GetHashCode() => status;
}
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;
var p3 = p1 with
{
Name = "C",
Y = 4
};
Console.WriteLine($"{nameof(p3)}: {p3}"); // output: p3: NamedPoint { Name = C, X = 0, Y = 4 }
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;
}
}
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;
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;
original.Tags.Add("C");
Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
// output: Tags of copy: A, B
}
}
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;
È 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.
+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.
+=, -= , *= , /= , %= , &= , |=, ^= , <<=, >>= 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 >=
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:
// 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.
{<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
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:
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.
System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("nl-NL");
string messageInCurrentCulture = message.ToString();
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.
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 .
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.
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.
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;
[AttributeUsage(AttributeTargets.Method)]
public class InfoAttribute : Attribute
{
private string information;
[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
AT T RIB UTO SC O P O
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
AT T RIB UTO SC O P O
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);
}
}
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()
{
// ...
}
[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
string 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() { }
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 { }
[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; }
Se l'argomento AllowMultiple è true , l'attributo restituito può essere applicato più volte a una singola entità,
come illustrato nell'esempio seguente:
[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 { }
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:
// 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 .
Metodo, proprietà o evento Nome del metodo, della proprietà o dell'evento da cui la
chiamata ha avuto origine.
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:
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.
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:
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.
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:
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:
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:
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.
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 .
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:
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:
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:
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:
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.
// 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:
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
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:
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;
#if (TRACE)
Console.WriteLine("Tracing is enabled.");
#endif
}
}
// Output:
// Debugging is enabled.
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
}
}
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:
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:
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;
}
}
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:
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
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
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;
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.
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++
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 .
csc File.cs
Compila tutti i file C# della directory corrente con le ottimizzazioni attivate e definisce il simbolo DEBUG.
L'output è File2.exe:
Compila tutti i file C# della directory corrente generando una versione di debug di File2.dll. Non viene
visualizzato nessun logo e nessun avviso:
Compila tutti i file C# della directory corrente con something. xyz (una dll):
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
File di output
O P Z IO N E SC O P O
-addmodule Specifica uno o più moduli che devono fare parte di questo
assembly.
Debug/Controllo errori
O P Z IO N E SC O P O
Preprocessore
O P Z IO N E SC O P O
Risorse
O P Z IO N E SC O P O
Varie
O P Z IO N E SC O P O
-codepage Specifica la tabella codici da usare per tutti i file del codice
sorgente nella compilazione.
Opzioni obsolete
O P Z IO N E SC O P O
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
-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.
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:
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 :
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.
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 :
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.
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
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
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
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
VA LO RE SIGN IF IC ATO
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.
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.
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 .
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;
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();
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 .
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.
Esempio
In questo esempio viene eseguita la stessa operazione descritta in precedenza, ma vengono usate le opzioni di
Assembly Linker.
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 :
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.
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
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
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.
<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:
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
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 :
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:
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:
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
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.
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:
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 :
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
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.
Windows XP 5,01
Windows 7 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.
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 :
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
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.
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 :
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 :
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
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 :
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:
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.
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:
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
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
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:
4 (impostazione predefinita) Visualizza tutti gli avvisi di livello 3 oltre ad avvisi informativi.
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:
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
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:
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 :
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.
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
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 :
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#.
using System;
namespace Acme.Collections
{
public class Stack
{
Entry top;
class Entry
{
public Entry next;
public object 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:
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
Booleano: bool
Tipi riferimento Tipi classe Classe di base principale di tutti gli altri
tipi: object
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.
64 long -
9.223.372.036.854.775.808
... 9, 223, 372, 036, 854,
775, 807
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.
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
x++ Post-incremento
x-- Post-decremento
Unario +x identità
-x Negazione
!x Negazione logica
++x Pre-incremento
--x Pre-decremento
Moltiplicazione x * y Moltiplicazione
x / y Divisione
x % y Resto
x > y Maggiore di
Uguaglianza x == y Uguale
x != y Diverso
AND logico x & y AND Integer bit per bit, AND logico
booleano
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
Istruzione Expression
Istruzione if
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
Istruzione do
Istruzione for
Istruzione foreach
Istruzione break
Istruzione continue
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
Istruzione yield
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
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 :
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:
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
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.
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 :
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:
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 :
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 :
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 .
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;
}
using System;
class Test
{
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
}
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
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.
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
}
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 .
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;
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.
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()");
}
// 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.
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à.
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;
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;
class Test
{
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
}
}
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.
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.
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 :
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.
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.
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();
}
Classi e struct possono implementare più interfacce. Nell'esempio seguente la classe EditBox implementa
IControl e IDataBound .
interface IDataBound
{
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:
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.
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.
È 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.
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;
}
}
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 .
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:
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;
class Multiplier
{
double factor;
class Test
{
static double Square(double x) {
return x * x;
}
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:
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;
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);
}
}
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
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 .
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 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.
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
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:
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
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:
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:
( ) ] } : ; , . ? == != | ^
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).
Test Nullable
È possibile sostituire l'idioma
Type? v = x?.y?.z;
if (v.HasValue) {
var value = v.GetValueOrDefault();
// code using value
}
Semplificazione aritmetica
Si supponga di definire un set di tipi ricorsivi per rappresentare le espressioni (per una proposta separata):
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));
}
}
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#.
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