Share via


Tipi di dati (confronto tra C# e Java)

Aggiornamento: novembre 2007

In questo argomento vengono descritte alcune delle principali analogie e differenze tra Java e C# nella modalità con cui i dati vengono rappresentati, allocati e sottoposti a Garbage Collection.

Tipi di dati composti

Il concetto di classe intesa come un tipo di dati compositi con campi, metodi ed eventi è simile in Java e C#. L'ereditarietà delle classi viene descritta in un altro argomento, Ereditarietà e classi derivate (confronto tra C# e Java). In C# viene introdotto il concetto di struttura, ossia un tipo di dati compositi allocato su uno stack che non supporta l'ereditarietà. Per la maggior parte degli altri aspetti, le strutture sono molto simili alle classi. Consentono di raggruppare campi e metodi correlati da utilizzare in cicli rigidi e in altri scenari in cui le prestazioni sono prioritarie.

In C# è possibile creare un metodo distruttore da chiamare prima che le istanze di una classe vengano sottoposte a Garbage Collection. In Java è possibile utilizzare un metodo finalize che contenga il codice per pulire le risorse prima che l'oggetto venga sottoposto a Garbage Collection. In C# questa funzione viene eseguita dal distruttore della classe. Il distruttore è simile a un costruttore, con la differenza che è privo di argomenti ed è preceduto da un carattere tilde (~).

Tipi di dati incorporati

In C# sono supportati tutti i tipi di dati disponibili in Java e in più i numeri senza segno e un nuovo tipo a virgola mobile ad alta precisione a 128 bit.

Per ogni tipo di dati primitivo di Java, la libreria delle classi di base fornisce una classe wrapper che lo rappresenta come oggetto Java. La classe Int32, ad esempio, esegue il wrapping del tipo di dati int, mentre la classe Double esegue il wrapping del tipo di dati double.

Tutti i tipi di dati primitivi di C# sono invece oggetti nello spazio dei nomi System. Per ogni tipo di dati, viene fornito un nome breve, o alias. Ad esempio, int è il nome breve di System.Int32 e double è il nome breve di System.Double.

Nella tabella riportata di seguito è incluso l'elenco dei tipi di dati di C# e dei relativi alias. È possibile notare che i primi otto tipi di dati corrispondono ai tipi primitivi disponibili in Java. La parola chiave boolean di Java è denominata bool in C#.

Nome breve

Classe .NET

Tipo

Larghezza

Intervallo (bit)

byte

Byte

Valore integer senza segno

8

Da 0 a 255

sbyte

SByte

Valore integer con segno

8

Da -128 a 127

int

Int32

Valore integer con segno

32

Da -2.147.483.648 a 2.147.483.647

uint

UInt32

Valore integer senza segno

32

Da 0 a 4294967295

short

Int16

Valore integer con segno

16

Da -32.768 a 32.767

ushort

UInt16

Valore integer senza segno

16

Da 0 a 65535

long

Int64

Valore integer con segno

64

Da -922337203685477508 a 922337203685477507

ulong

UInt64

Valore integer senza segno

64

Da 0 a 18446744073709551615

float

Single

Tipo a virgola mobile e precisione singola

32

Da -3,402823e38 a 3,402823e38

double

Double

Tipo a virgola mobile e precisione doppia

64

Da -1,79769313486232e308 a 1,79769313486232e308

char

Char

Singolo carattere Unicode

16

Simboli Unicode utilizzati nel testo

bool

Boolean

Tipo booleano logico

8

True o false

object

Object

Tipo di base per tutti gli altri tipi

string

String

Sequenza di caratteri

decimal

Decimal

Tipo frazionario o integrale esatto che può rappresentare numeri decimali con 29 cifre significative

128

Da ±1.0 × 10e−28 a ±7.9 × 10e28

Poiché C# rappresenta tutti i tipi di dati primitivi come oggetti, è possibile chiamare un metodo di oggetto su un tipo di dati primitivo. Ad esempio:

static void Main()
{
    int i = 10;
    object o = i;
    System.Console.WriteLine(o.ToString());
}    

Questo risultato viene ottenuto grazie alle conversioni boxing e unboxing automatiche. Per ulteriori informazioni, vedere la classe Boxing e unboxing (Guida per programmatori C#).

Costanti

Sia in Java che in C# è possibile dichiarare una variabile il cui valore viene specificato in fase di compilazione e non può essere modificato in fase di esecuzione. Per dichiarare tale variabile, in Java viene utilizzato il modificatore di campo final mentre in C# la parola chiave const. Oltre a const, in C# è possibile utilizzare la parola chiave readonly per dichiarare le variabili cui è possibile assegnare un valore una sola volta in fase di esecuzione, ossia nell'istruzione di dichiarazione oppure in un altro punto del costruttore. Dopo l'inizializzazione, il valore di una variabile readonly non può essere modificato. Le variabili readonly risultano ad esempio utili quando sono stati compilati separatamente due moduli che devono condividere dati come un numero di versione. Se il modulo A viene aggiornato e ricompilato con un nuovo numero di versione, il modulo B può essere inizializzato con il nuovo valore costante senza dover essere ricompilato.

Enumerazioni

Le enumerazioni, o enum, vengono utilizzate per raggruppare costanti denominate, in modo analogo a come avviene in C e C++. Non sono disponibili in Java. Nell'esempio riportato di seguito viene definita un'enumerazione Color semplice.

public enum Color
{
    Green,   //defaults to 0
    Orange,  //defaults to 1
    Red,     //defaults to 2
    Blue     //defaults to 3
}  

Alle enumerazioni è anche possibile assegnare valori integrali, come illustrato nella seguente dichiarazione enum:

public enum Color2
{
    Green = 10,
    Orange = 20,
    Red = 30,
    Blue = 40
}

Nell'esempio di codice riportato di seguito viene chiamato il metodo GetNames del tipo Enum per visualizzare le costanti disponibili per un'enumerazione. Viene quindi assegnato un valore a un'enumerazione e viene visualizzato il valore.

class TestEnums
{
    static void Main()
    {
        System.Console.WriteLine("Possible color choices: ");

        //Enum.GetNames returns a string array of named constants for the enum.
        foreach(string s in System.Enum.GetNames(typeof(Color)))
        {
            System.Console.WriteLine(s);
        }

        Color favorite = Color.Blue;

        System.Console.WriteLine("Favorite Color is {0}", favorite);
        System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
    }
}

Output

Possible color choices:

Green

Orange

Red

Blue

Favorite Color is Blue

Favorite Color value is 3

Stringhe

I tipi stringa Java e C# hanno un comportamento simile, con alcune piccole differenze. Entrambi i tipi non sono modificabili, ossia non è possibile modificare i valori delle stringhe dopo che sono state create. In entrambi i casi, i metodi che sembrano modificare l'effettivo contenuto di una stringa creano in realtà una nuova stringa da restituire, lasciando invariata quella originaria. Il processo di confronto dei valori delle stringhe è diverso in C# e in Java. Per confrontare i valori delle stringhe in Java, gli sviluppatori devono chiamare il metodo equals su un tipo stringa, dal momento che per impostazione predefinita l'operatore == confronta i tipi di riferimento. In C# gli sviluppatori possono invece utilizzare l'operatore == o != per confrontare direttamente i valori delle stringhe. Anche se una stringa è un tipo di riferimento in C#, per impostazione predefinita gli operatori == e != confrontano i valori delle stringhe anziché i riferimenti.

Come in Java, gli sviluppatori C# non devono utilizzare il tipo stringa per concatenare le stringhe. Ciò consente di evitare che vengano create nuove classi di stringhe ogni volta che la stringa viene concatenata. Gli sviluppatori possono invece utilizzare la classe StringBuilder, che equivale da un punto di vista funzionale alla classe StringBuffer di Java.

Stringhe letterali

In C# è possibile evitare di utilizzare sequenze di escape, quali "\t" per il carattere di tabulazione o "\" per le barre rovesciate, all'interno delle costanti di stringa. A tale scopo, è sufficiente dichiarare la stringa verbatim anteponendo il simbolo @ all'assegnazione del valore di stringa. Negli esempi riportati di seguito viene illustrato come utilizzare i caratteri di escape e come assegnare stringhe letterali:

static void Main()
{
    //Using escaped characters:
    string path1 = "\\\\FileShare\\Directory\\file.txt";
    System.Console.WriteLine(path1);

    //Using String Literals:
    string path2 = @"\\FileShare\Directory\file.txt";
    System.Console.WriteLine(path2);
}

Conversione e cast

In Java e C# vengono seguite regole simili per la conversione e il cast automatici dei tipi di dati.

Analogamente a Java, in C# sono supportate sia le conversioni dei tipi implicite che quelle esplicite. Se effettuate verso un tipo di dati più grande, le conversioni sono implicite. Ad esempio, la seguente conversione da int a long è implicita, come in Java:

int int1 = 5;
long long1 = int1;  //implicit conversion

Di seguito è riportato l'elenco delle conversioni implicite tra tipi di dati .NET Framework.

Tipo di origine

Tipo di destinazione

Byte

short, ushort, int, uint, long, ulong, float, double o decimal

Sbyte

short, int, long, float, double o decimal

Int

long, float, double o decimal

Uint

long, ulong, float, double o decimal

Short

int, long, float, double o decimal

Ushort

int, uint, long, ulong, float, double o decimal

Long

float, double o decimal

Ulong

float, double o decimal

Float

double

Char

ushort, int, uint, long, ulong, float, double o decimal

Per eseguire il cast delle espressioni che si desidera convertire in modo esplicito, utilizzare la stessa sintassi di Java:

long long2 = 5483;
int int2 = (int)long2;  //explicit conversion

Di seguito è riportato l'elenco delle conversioni esplicite.

Tipo di origine

Tipo di destinazione

Byte

sbyte o char

Sbyte

byte, ushort, uint, ulong o char

Int

sbyte, byte, short, ushort, uint, ulong o char

Uint

sbyte, byte, short, ushort, int o char

Short

sbyte, byte, ushort, uint, ulong o char

Ushort

sbyte, byte, short o char

Long

sbyte, byte, short, ushort, int, uint, ulong o char

Ulong

sbyte, byte, short, ushort, int, uint, long o char

Float

sbyte, byte, short, ushort, int, uint, long, ulong, char o decimal

Double

sbyte, byte, short, ushort, int, uint, long, ulong, char, float o decimal

Char

sbyte, byte o short

Decimal

sbyte, byte, short, ushort, int, uint, long, ulong, char, float o double

Tipi di valore e tipi di riferimento

C# supporta due tipi di variabile:

  • Tipi di valore

    Tipi di dati primitivi incorporati, ad esempio char, int e float, e tipi definiti dall'utente dichiarati con struct.

  • Tipi di riferimento

    Classi e altri tipi di dati complessi che vengono costruiti a partire dai tipi primitivi. Le variabili di questi tipi non contengono un'istanza del tipo, bensì un riferimento a un'istanza.

Se si creano due variabili di tipo valore, i e j, come illustrato di seguito, i e j sono completamente indipendenti l'una dall'altra:

int i = 10;
int j = 20;

A queste variabili vengono assegnate posizioni di memoria separate:

Separazione degli indirizzi di memoria per tipi di valori

Se si modifica il valore di una di queste variabili, il valore dell'altra resterà invariato. Ad esempio, in un'espressione come la seguente, non esiste alcuna correlazione tra le variabili:

int k = i;

Se pertanto si modifica il valore di i, k conserverà il valore attribuito a i al momento dell'assegnazione.

i = 30;

System.Console.WriteLine(i.ToString());  // 30
System.Console.WriteLine(k.ToString());  // 10

I tipi di riferimento invece funzionano in modo diverso. È ad esempio possibile dichiarare due variabili come illustrato di seguito:

Employee ee1 = new Employee();
Employee ee2 = ee1;

Poiché le classi sono tipi di riferimento in C#, ee1 è un riferimento a Employee. La prima delle due righe precedenti determina la creazione di un'istanza di Employee in memoria e imposta ee1 in modo che faccia riferimento a tale classe. Di conseguenza, quando si imposta ee2 come uguale ad ee1, questa variabile conterrà un duplicato del riferimento alla classe in memoria. Se poi si modificano le proprietà relative a ee2, quelle relative ad ee1 rifletteranno tali modifiche, poiché entrambe le variabili puntano allo stesso oggetto in memoria, come illustrato di seguito:

Posizioni di memoria per tipi di riferimenti

Boxing e unboxing

Il processo di conversione da un tipo di valore a un tipo di riferimento è noto come boxing. Il processo inverso, ossia la conversione da un tipo di riferimento a un tipo di valore, è definito unboxing. Questi processi vengono illustrati nel codice seguente:

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;  // unboxing

In Java queste conversioni devono essere eseguite manualmente. I tipi di dati primitivi possono essere convertiti in oggetti di classi wrapper mediante la costruzione o conversione boxing di tali oggetti. Analogamente, i valori dei tipi di dati primitivi possono essere estratti dagli oggetti delle classi wrapper chiamando un metodo appropriato su tali oggetti, ossia eseguendo la conversione unboxing. Per ulteriori informazioni sulle conversioni boxing e unboxing, vedere Boxing e unboxing (Guida per programmatori C#).

Vedere anche

Concetti

Guida per programmatori C#

Riferimenti

Tipi (Guida per programmatori C#)

Altre risorse

Visual C#

Linguaggio di programmazione C# per sviluppatori Java