Condividi tramite


Sistema di tipi C#

Suggerimento

Novità dello sviluppo di software? Iniziare prima con le esercitazioni introduttive . Ti guidano attraverso la scrittura di programmi e introducono i tipi man mano che procedi.

Esperienza in un'altra lingua? Se si conoscono già i sistemi di tipi, esaminare la distinzione tra valore e riferimento e la guida su quale tipo scegliere, quindi passare agli articoli relativi a tipi specifici.

C# è un linguaggio fortemente tipizzato. Ogni variabile, costante ed espressione ha un tipo . Il compilatore applica la sicurezza dei tipi controllando che ogni operazione nel codice sia valida per i tipi coinvolti. Ad esempio, è possibile aggiungere due valori int, ma non è possibile aggiungere un int e un bool.

int a = 5;
int b = a + 2; // OK

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
// int c = a + test;

Annotazioni

A differenza di C e C++, in C#, bool non è convertibile in int.

La sicurezza dei tipi rileva gli errori in fase di compilazione, prima dell'esecuzione del codice. Il compilatore incorpora anche le informazioni sul tipo nel file eseguibile come metadati, usato da Common Language Runtime (CLR) per controlli di sicurezza aggiuntivi in fase di esecuzione.

Dichiarare variabili con tipi

Quando si dichiara una variabile, specificarne il tipo in modo esplicito o usare var per consentire al compilatore di dedurre il tipo dal valore assegnato:

// Explicit type:
int count = 10;
double temperature = 36.6;

// Compiler-inferred type:
var name = "C#";
var items = new List<string> { "one", "two", "three" };

I parametri del metodo e i valori restituiti hanno anche tipi. Il metodo seguente accetta un string e un int, e restituisce un string:

static string GetGreeting(string name, int visitCount)
{
    return visitCount switch
    {
        1 => $"Welcome, {name}!",
        _ => $"Welcome back, {name}! Visit #{visitCount}."
    };
}

Dopo aver dichiarato una variabile, non è possibile modificarne il tipo o assegnare un valore non compatibile con il tipo dichiarato. È possibile convertire i valori in altri tipi. Il compilatore esegue conversioni implicite che non perdono automaticamente i dati. Le conversioni esplicite (cast) richiedono di indicare la conversione nel codice. Per altre informazioni, vedere Cast e conversioni dei tipi.

Tipi predefiniti e tipi personalizzati

C# offre tipi predefiniti per i dati comuni: numeri interi, numeri a virgola mobile, bool, chare string. Ogni programma C# può usare questi tipi predefiniti senza riferimenti aggiuntivi.

Oltre ai tipi predefiniti, è possibile creare tipi personalizzati usando diversi costrutti:

  • Classi : tipi di riferimento per il comportamento di modellazione e oggetti complessi. Supportare l'ereditarietà e il polimorfismo.
  • Structs: tipi valore per dati di piccole dimensioni e leggeri. Ogni variabile contiene la propria copia.
  • Record : classi o struct con uguaglianza generata dal compilatore, ToStringe mutazione non distruttiva tramite with espressioni.
  • Interfacce : contratti che definiscono membri che qualsiasi classe o struct può implementare.
  • Enumerazioni : set denominati di costanti integrali, ad esempio giorni della settimana o modalità di accesso ai file.
  • Tuple: tipi strutturali leggeri che raggruppano i valori correlati senza definire un tipo denominato.
  • Generics : costrutti con parametri di tipo come List<T> e Dictionary<TKey, TValue> che forniscono sicurezza dei tipi riutilizzando la stessa logica per tipi diversi.

Tipi di valore e tipi di riferimento

Ogni tipo in C# è un tipo valore o un tipo riferimento. Questa distinzione determina il funzionamento delle variabili di archiviazione dei dati e del funzionamento dell'assegnazione.

I tipi valore contengono direttamente i dati. Quando si assegna un tipo di valore a una nuova variabile, il runtime copia i dati. Le modifiche apportate a una variabile non influiscono sull'altra. Gli struct, le enumerazioni e i tipi numerici predefiniti sono tutti tipi valore.

I tipi di riferimento contengono un riferimento a un oggetto nell'heap gestito. Quando si assegna un tipo riferimento a una nuova variabile, entrambe le variabili puntano allo stesso oggetto. Le modifiche apportate a una variabile sono visibili tramite l'altra. Classi, matrici, delegati e stringhe sono tipi di riferimento.

L'esempio seguente mostra la differenza. Il primo blocco mostra la definizione per lo struct di Coords record, ovvero un tipo di valore. Il secondo blocco mostra il diverso comportamento per i tipi valore e i tipi riferimento.

public readonly record struct Coords(int X, int Y);
// Value type: each variable holds its own copy
var point1 = new Coords(3, 4);
var point2 = point1;
Console.WriteLine($"point1: ({point1.X}, {point1.Y})");
Console.WriteLine($"point2: ({point2.X}, {point2.Y})");
// point1 and point2 are independent copies

// Reference type: both variables refer to the same object
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine($"list1 count: {list1.Count}"); // 4 — same object

Tutti i tipi derivano in ultima analisi da System.Object. I tipi valore derivano da System.ValueType, che deriva da object. Questa gerarchia unificata è denominata Common Type System (CTS). Per altre informazioni sull'ereditarietà, vedere Ereditarietà.

Scegliere il tipo di tipo

Quando definisci un nuovo tipo, la tua scelta influenza il comportamento del codice. Usare le linee guida seguenti per prendere una decisione iniziale:

  • Tupla : raggruppamento temporaneo di valori che non richiedono un tipo o un comportamento denominato.
  • struct oppure record struct : dati di piccole dimensioni (circa 64 byte o meno), semantica dei valori o immutabilità. Gli struct di record aggiungono uguaglianza basata su valore ed espressioni.
  • record class — Principalmente dati, con uguaglianza basata su valore, ToStringe mutazione non distruttiva. Supporta l'ereditarietà.
  • class — Comportamento complesso, polimorfismo o stato modificabile. La maggior parte dei tipi personalizzati sono classi.
  • interface — Un contratto che i tipi non correlati possono implementare. Definisce le funzionalità anziché l'identità.
  • enum — Set fisso di costanti denominate, ad esempio codici di stato o opzioni.

Più di un'opzione è spesso ragionevole.

Tipo in fase di compilazione e tipo di runtime

Una variabile può avere tipi diversi in fase di compilazione e runtime. Il tipo in fase di compilazione è il tipo dichiarato o dedotto nel codice sorgente. Il tipo di runtime è il tipo effettivo dell'istanza a cui fa riferimento la variabile. Il tipo di runtime deve essere uguale al tipo in fase di compilazione oppure un tipo che ne deriva o lo implementa. Un'assegnazione è valida solo quando esiste una conversione implicita dal tipo di runtime al tipo in fase di compilazione, ad esempio un'identità, un riferimento, una conversione boxing o numerica.

// Compile-time and run-time types match:
string message = "Hello, world!";

// Compile-time type differs from run-time type:
object boxed = "This is a string at run time";
IEnumerable<char> characters = "abcdefghijklmnopqrstuvwxyz";

Nell'esempio precedente, boxed ha un tipo in fase di compilazione di object e un tipo di runtime di string. L'assegnazione funziona perché string deriva da object. Analogamente, characters ha un tipo in fase di compilazione di IEnumerable<char>e l'assegnazione funziona perché string implementa tale interfaccia. Il tipo in fase di compilazione controlla la risoluzione dell'overload e le conversioni disponibili. Il tipo di runtime controlla l'invio dei metodi virtuali, is le espressioni, e switch le espressioni.

Vedere anche

Specificazione del linguaggio C#

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