Tipi (Guida per programmatori C#)

Tipi, variabili e valori

C# è un linguaggio fortemente tipizzato. Ogni variabile e costante dispone di 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 Framework definisce un set di tipi numerici incorporati e di tipi più complessi che rappresentano un'ampia varietà di costrutti logici, ad esempio il file system, le connessioni di rete, gli insiemi e le matrici di oggetti e le date. Un tipico programma C# utilizza tipi della libreria di classi e tipi definiti dall'utente che modellano i concetti specifici del dominio del problema del programma.

Le informazioni archiviate in un tipo possono includere:

  • Spazio di archiviazione eventualmente richiesto da una variabile del tipo.

  • Valori massimo e minimo che può rappresentare.

  • I membri (metodi, campi, eventi e così via) che contiene.

  • Tipo di base da cui eredita.

  • Posizione in cui verrà allocata la memoria per le variabili in fase di esecuzione.

  • Tipi di operazioni consentite.

Il compilatore utilizza le informazioni sui tipi per garantire che tutte le operazioni eseguite nel codice siano indipendenti dai tipi. Ad esempio, se si dichiara una variabile di tipo int, il compilatore consente di utilizzare la variabile in operazioni di addizione e sottrazione. Se si tenta di eseguire quelle stesse operazioni su una variabile di tipo bool, il compilatore genera un errore, come mostrato nell'esempio seguente:

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

bool test = true;

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

Nota

Gli sviluppatori che utilizzano C e C++ devono tenere presente che in C#, bool non è convertibile in int.

Il compilatore incorpora le informazioni sui tipi nel file eseguibile in forma di metadati. Common Language Runtime (CLR) utilizza tali metadati in fase di esecuzione a ulteriore garanzia dell'indipendenza dai tipi quando alloca e recupera memoria.

Specifica di tipi nelle dichiarazioni di variabile

Quando si dichiara una variabile o una costante in un programma, è necessario specificarne il tipo o utilizzare la parola chiave var per consentire al compilatore di dedurre il tipo. Nell'esempio seguente vengono mostrate alcune dichiarazioni di variabile che utilizzano sia tipi numerici incorporati che tipi complessi definiti dall'utente:

// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
            where item <= limit
            select item;

I tipi dei parametri e dei valori restituiti di un metodo vengono specificati nella firma del metodo. Nella firma seguente viene mostrato un metodo che richiede int come argomento di input e restituisce una stringa:

public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Una volta dichiarata, una variabile non può essere dichiarata nuovamente con un nuovo tipo e non le può venire assegnato un valore non compatibile con il tipo dichiarato. Ad esempio, non è possibile dichiarare int e assegnare ad esso il valore booleano true. È tuttavia possibile convertire i valori 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. Una conversione che potrebbe causare la perdita di dati richiede un cast nel codice sorgente.

Per ulteriori informazioni, vedere Cast e conversioni di tipi (Guida per programmatori C#).

Tipi incorporati

C# fornisce un insieme standard di tipi numerici incorporati per rappresentare numeri interi, valori in virgola mobile, espressioni booleane, caratteri di testo, valori decimali e altri tipi di dati. Sono inoltre disponibili tipi string e object, utilizzabili in qualsiasi programma C#. Per ulteriori informazioni sui tipi incorporati, vedere Tabelle di riferimento dei tipi (Riferimenti per C#).

Tipi personalizzati

Per creare tipi personalizzati si utilizzano i costrutti struct, class, interface ed enum. La stessa libreria di classi .NET Framework è un insieme di tipi personalizzati forniti da Microsoft che è possibile utilizzare nelle applicazioni. Per impostazione predefinita, i tipi utilizzati con maggiore frequenza nella libreria di classi sono disponibili in qualsiasi programma C#. Altri diventano disponibili solo quando si aggiunge in modo esplicito un riferimento al progetto all'assembly in cui sono definiti. Quando il compilatore dispone di un riferimento all'assembly, è possibile dichiarare variabili (e costanti) dei tipi dichiarati in quell'assembly nel codice sorgente. Per ulteriori informazioni, vedere Libreria di classi di .NET Framework.

Common Type System

È importante comprendere due punti fondamentali sul sistema dei tipi in .NET Framework:

  • Esso supporta il principio di ereditarietà. I tipi possono derivare da altri tipi, i cosiddetti tipi di base. Il tipo derivato eredita, con alcune restrizioni, i metodi, le proprietà e altri membri del tipo di base. Il tipo di base può a sua volta derivare da un altro tipo, nel qual caso il tipo derivato eredita i membri di entrambi i tipi di base nella gerarchia di ereditarietà. Tutti i tipi, inclusi i tipi numerici incorporati come System.Int32 (parola chiave C#: int), derivano in definitiva da un solo tipo di base che è System.Object (parola chiave C#: object). Questa gerarchia di tipi unificata viene chiamata Common Type System (CTS). Per ulteriori informazioni sull'ereditarietà in C#, vedere Ereditarietà (Guida per programmatori C#).

  • Ogni tipo in CTS è definito come tipo valore o tipo riferimento. Sono inclusi tutti i tipi personalizzati nella libreria di classi .NET Framework, nonché i tipi definiti dall'utente. I tipi definiti tramite la parola chiave struct sono tipi valore; tutti i tipi numerici incorporati sono structs. I tipi definiti tramite la parola chiave class sono tipi riferimento. I tipi riferimento e i tipi valore hanno regole della fase di compilazione diverse e comportamento in fase di esecuzione diverso.

Nell'illustrazione seguente è mostrata la relazione tra tipi valore e tipi riferimento in CTS.

Tipi valore e tipi riferimento in CTS

Tipi valore e tipi riferimento

Nota

È possibile vedere che i tipi utilizzati con maggiore frequenza sono tutti organizzati nello spazio dei nomi System. Tuttavia, l'inclusione di un tipo in uno spazio dei nomi è indipendente dal fatto che il tipo sia un tipo valore o un tipo riferimento.

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 relativi 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 dei tipi valore.

Esistono due categorie di tipi valore: struct e enum.

I tipi numerici incorporati sono struct i cui metodi e proprietà sono accessibili:

// Static method on type Byte.
byte b = Byte.MaxValue;

Tuttavia, si dichiarano e si assegnano ad essi i valori come se si trattasse di tipi non aggregati semplici:

byte num = 0xA;
int i = 5;
char c = 'Z';

I tipi valore sono sealed, ciò significa, ad esempio, che non è possibile derivare un tipo da System.Int32 e non è possibile definire una struttura da ereditare da qualsiasi struttura o classe definita dall'utente perché una struttura può ereditare solo da System.ValueType. Una struttura può tuttavia implementare una o più interfacce. È possibile eseguire il cast di un tipo struct in un tipo interface, ma in tal caso è necessaria un'operazione di conversione boxing per eseguire il wrapping della struttura in un oggetto tipo riferimento sull'heap gestito. Le operazioni di conversione boxing si verificano anche quando si passa un tipo valore a un metodo che accetta System.Object come parametro di input. Per ulteriori informazioni, vedere Boxing e unboxing (Guida per programmatori C#).

Per creare tipi valore personalizzati si utilizza la parola chiave struct. In genere, una struttura viene utilizzata come contenitore per un piccolo insieme di variabili correlate, come mostrato nell'esempio seguente:

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

Per ulteriori informazioni sulle strutture, vedere Struct (Guida per programmatori C#). Per ulteriori informazioni sui tipi valore in .NET Framework, vedere Common Type System.

L'altra categoria di tipi valore è enum. Un tipo enum definisce un insieme di costanti integrali denominate. Ad esempio, l'enumerazione System.IO.FileMode nella libreria di classi .NET Framework contiene un insieme di interi costanti denominati che specificano come deve essere aperto un file. La relativa definizione viene mostrata nell'esempio seguente:

public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

Il valore della costante System.IO.FileMode.Create è 2. Tuttavia, un nome è molto più significativo nella lettura del codice sorgente; per questa ragione è preferibile utilizzare enumerazioni anziché valori letterali numerici costanti. Per ulteriori informazioni, vedere System.IO.FileMode.

Tutte le enumerazioni ereditano da System.Enum, che eredita da System.ValueType. Tutte le regole che si applicano alle strutture si applicano anche alle enumerazioni. Per ulteriori informazioni sulle enumerazioni, vedere Tipi di enumerazione (Guida per programmatori C#).

Tipi di riferimento

Un tipo definito come classe, delegato, matrice o interfaccia è un tipo riferimento. Quando si dichiara una variabile di un tipo riferimento in fase di esecuzione, la variabile contiene il valore null finché non si crea in modo esplicito un'istanza dell'oggetto tramite l'operatore new o lo si assegna a un oggetto creato altrove tramite new, as shown in the following example:

MyClass mc = new MyClass();
MyClass mc2 = mc;

Un'interfaccia deve essere inizializzata insieme a un oggetto classe che la implementa. Se MyClass implementa IMyInterface, si crea un'istanza di IMyInterface come illustrato nell'esempio riportato di seguito:

IMyInterface iface = new MyClass();

Quando l'oggetto viene creato, la memoria viene allocata sull'heap gestito e la variabile contiene solo un riferimento alla posizione dell'oggetto. I tipi sull'heap gestito richiedono un overhead sia quando vengono allocati sia quando vengono recuperati dalla funzionalità di gestione automatica della memoria di CLR, nota come Garbage Collection. Tuttavia, anche la funzionalità Garbage Collection è altamente ottimizzata e nella maggior parte degli scenari non crea problemi di prestazioni. Per ulteriori informazioni su Garbage Collection, vedere Gestione automatica della memoria.

Anche 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 utilizzate con la sintassi semplificata fornita da C#, come illustrato nell'esempio seguente:

// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.
int len = nums.Length;

I tipi riferimento supportano completamente l'ereditarietà. Quando si crea una classe, è possibile ereditare da qualsiasi altra interfaccia o classe che non è definita come sealed e le altre classi possono ereditare dalla classe e possono eseguire l'override dei metodi virtuali. Per ulteriori informazioni sulla creazione di classi personalizzate, vedere Classi e struct (Guida per programmatori C#). Per ulteriori informazioni sull'ereditarietà e sui metodi virtuali, vedere Ereditarietà (Guida per programmatori C#).

Tipi di valori letterali

In C#, i valori letterali ricevono un tipo dal compilatore. È possibile specificare come un valore letterale numerico deve essere tipizzato aggiungendo una lettera alla fine del numero. Ad esempio, per specificare che il valore 4.56 deve essere considerato come un tipo float, aggiungere una "f" o una "F" dopo il numero: 4.56f. Se non viene aggiunta alcuna lettera, il compilatore dedurrà un tipo per il valore letterale. Per ulteriori informazioni su quali tipi possono essere specificati con suffissi letterali, vedere le pagine di riferimento per i tipi singoli in Tipi di valore (Riferimenti per C#).

Poiché i valori letterali sono tipizzati e tutti i tipi derivano da System.Object, è possibile scrivere e compilare codice come il seguente:

string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);

Tipi generici

Un tipo può essere dichiarato con uno o più parametri di tipo che fungono da segnaposto del tipo vero o proprio (tipo concreto) che il codice client fornirà alla creazione di un'istanza del tipo. Tali tipi sono definiti tipi generici. Ad esempio, il tipo .NET Framework System.Collections.Generic.List<T> dispone di un parametro di tipo che per convenzione gli viene assegnato il nome T. Quando si crea un'istanza del tipo, si specifica il tipo degli oggetti che conterrà l'elenco, ad esempio, stringa:

List<string> strings = new List<string>();

L'utilizzo del parametro di tipo rende possibile il riutilizzo della stessa classe per contenere qualsiasi tipo di elemento, senza dover convertire ogni elemento in oggetto. Le classi di insiemi generici sono definite insiemi fortemente tipizzati perché il compilatore conosce il tipo specifico degli elementi dell'insieme e può generare un errore in fase di compilazione se, ad esempio, si tenta di aggiungere un intero all'oggetto strings nell'esempio precedente. Per ulteriori informazioni, vedere Generics (Guida per programmatori C#).

Tipi impliciti, tipi anonimi e tipi nullable

Come indicato precedentemente, è possibile tipizzare in modo implicito una variabile locale (ma non membri di classe) tramite la parola chiave var. La variabile riceve comunque un tipo in fase di compilazione, fornito dal compilatore. Per ulteriori informazioni, vedere Variabili locali tipizzate in modo implicito (Guida per programmatori C#).

In alcuni casi è difficile o impossibile creare un tipo denominato per insiemi semplici di valori correlati che non si intende archiviare o passare all'esterno dei limiti del metodo. A tale scopo è possibile creare tipi anonimi. Per ulteriori informazioni, vedere Tipi anonimi (Guida per programmatori C#).

Tipi valore comuni non possono avere un valore null. Tuttavia, è possibile creare tipi valore nullable aggiungendo ? dopo il tipo. Ad esempio, int? è un tipo int che può avere anche il valore null. In CTS i tipi nullable sono istanze del tipo di struttura generico System.Nullable<T>. I tipi nullable sono particolarmente utili quando si passano dati da e verso database nei quali i valori numerici potrebbero essere null. Per ulteriori informazioni, vedere Tipi nullable (Guida per programmatori C#).

Sezioni correlate

Per ulteriori informazioni, vedere i seguenti argomenti:

Specifiche del linguaggio C#

Per ulteriori informazioni, vedere la Specifiche del linguaggio C#. La specifica del linguaggio è la fonte ufficiale per la sintassi e l'utilizzo di C#.

Vedere anche

Riferimenti

Confronto tra tipi di dati in diversi linguaggi

Tabella dei tipi integrali (Riferimenti per C#)

Concetti

Guida per programmatori C#

Conversione dei tipi di dati XML

Altre risorse

Riferimenti per C#