Tipi di riferimento predefiniti (riferimenti per C#)

C# include molti 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. Quando una variabile di un tipo valore viene convertita in oggetto, viene definita boxed. Quando una variabile di tipo object viene convertita in un tipo valore, viene definita 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. L'uguaglianza basata su valori rende più intuitivo il test dell'uguaglianza di stringhe. 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));

Nell'esempio precedente viene visualizzato "True" e quindi "False" perché il contenuto delle stringhe è equivalente, ma a e b non fanno riferimento alla stessa istanza di stringa.

L'operatore + concatena le stringhe:

string a = "good " + "morning";

Il codice precedente crea un oggetto stringa che contiene "buongiorno".

Le stringhe non sono modificabili. Il contenuto di un oggetto stringa non può essere modificato dopo la creazione dell'oggetto. Ad esempio, quando si scrive il codice, il compilatore crea un nuovo oggetto stringa per archiviare la nuova sequenza di caratteri e il nuovo oggetto viene assegnato a b. La memoria allocata per b (quando conteneva la stringa "h") è quindi idonea per la garbage collection.

string b = "h";
b += "ello";

L'operatore [] può essere usato per accedere in sola lettura ai singoli caratteri di una stringa. I valori di indice validi iniziano a 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 eseguire l'iterazione su ogni carattere in una stringa:

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

Valori letterali di stringa

I valori letterali stringa sono di tipo string e possono essere scritti in tre forme, raw, quoted e verbatim.

I valori letterali stringa non elaborati sono disponibili a partire da C# 11. I valori letterali stringa non elaborati possono contenere testo arbitrario senza richiedere sequenze di escape. I valori letterali stringa non elaborati possono includere spazi vuoti e nuove righe, virgolette incorporate e altri caratteri speciali. I valori letterali stringa non elaborati sono racchiusi tra tre virgolette doppie (""):

"""
This is a multi-line
    string literal with the second line indented.
"""

È anche possibile includere una sequenza di tre (o più) caratteri virgolette doppie. Se il testo richiede una sequenza incorporata di virgolette, iniziare e terminare il valore letterale stringa non elaborato con più virgolette, in base alle esigenze:

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

I valori letterali stringa non elaborati hanno in genere le sequenze di virgolette iniziale e finale su righe separate dal testo incorporato. I valori letterali stringa non elaborati su più righe supportano stringhe che sono stesse stringhe tra virgolette:

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

Quando le virgolette iniziali e finali si trovano su righe separate, le nuove righe che seguono l'offerta di apertura e le virgolette precedenti non vengono incluse nel contenuto finale. La sequenza di virgolette di chiusura determina la colonna più a sinistra per il valore letterale stringa. È possibile impostare un rientro di un valore letterale stringa non elaborato in modo che corrisponda al formato di codice complessivo:

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

Le colonne a destra della sequenza di virgolette finali vengono mantenute. Questo comportamento abilita le stringhe non elaborate per i formati di dati, ad esempio JSON, YAML o XML, come illustrato nell'esempio seguente:

var json= """
    {
        "prop": 0
    }
    """;

Il compilatore genera un errore se una delle righe di testo si estende a sinistra della sequenza di virgolette di chiusura. Le sequenze di virgolette di apertura e di chiusura possono trovarsi sulla stessa riga, a condizione che il valore stringa letterale non inizi né finisca con un carattere di virgolette:

var shortText = """He said "hello!" this morning.""";

È possibile combinare valori letterali stringa non elaborati con l'interpolazione di stringhe per includere caratteri virgolette e parentesi graffe nella stringa di output.

I valori letterali della stringa tra virgolette sono racchiusi in virgolette doppie ("):

"good morning"  // a string literal

I valori letterali della stringa possono contenere qualsiasi carattere letterale. Sono incluse le sequenze di escape. L'esempio seguente usa una sequenza di escape \\ per la barra rovesciata, \u0066 per la lettera f e \n per la nuova riga.

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

Nota

Il codice di escape \udddd (dove dddd è un numero a quattro cifre) rappresenta il carattere Unicode U+dddd. Vengono riconosciuti anche i codici di escape Unicode a otto cifre: \Udddddddd.

I valori letterali della stringa verbatim iniziano con @ e sono anche racchiusi tra virgolette doppie. Ad esempio:

@"good morning"  // a string literal

Il vantaggio delle stringhe verbatim è che le sequenze di escape non vengono elaborate, semplificando la scrittura. Ad esempio, il testo seguente corrisponde a un nome file di Windows completo:

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

Per includere una virgoletta doppia in una stringa con @, raddoppiarla:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

Valori letterali stringa UTF-8

Le stringhe in .NET vengono archiviate usando la codifica UTF-16. UTF-8 è lo standard per i protocolli Web e altre librerie importanti. A partire da C# 11, è possibile aggiungere il suffisso u8 a un valore letterale stringa per specificare la codifica UTF-8. I valori letterali UTF-8 vengono archiviati come oggetti ReadOnlySpan<byte>. Il tipo naturale di un valore letterale stringa UTF-8 è ReadOnlySpan<byte>. L'uso di un valore letterale stringa UTF-8 crea una dichiarazione più chiara rispetto alla dichiarazione equivalente System.ReadOnlySpan<T>, come illustrato nel codice seguente:

ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;

Per archiviare un valore letterale stringa UTF-8 come matrice, è necessario usare ReadOnlySpan<T>.ToArray() per copiare i byte contenenti il valore letterale nella matrice modificabile:

byte[] AuthStringLiteral = "AUTH "u8.ToArray();

I valori letterali stringa UTF-8 non sono costanti in fase di compilazione; sono costanti di runtime. Pertanto, non possono essere usati come valore predefinito per un parametro facoltativo. I valori letterali stringa UTF-8 non possono essere combinati con l'interpolazione di stringhe. Non è possibile usare il token $ e il suffisso u8 nella stessa espressione stringa.

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.

La combinazione o la rimozione del delegato non riesce con un'eccezione di runtime quando i tipi delegati coinvolti in fase di esecuzione sono diversi a causa della conversione di varianti. L'esempio seguente illustra una situazione che ha esito negativo:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

È possibile creare un delegato con il tipo di runtime corretto creando un nuovo oggetto delegato. Nell'esempio seguente viene illustrato come applicare questa soluzione alternativa all'esempio precedente.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

È possibile dichiarare puntatori a funzione, che usano una sintassi simile. Un puntatore a funzione usa l'istruzione calli anziché creare un'istanza di un tipo delegato e chiamare il metodo virtuale Invoke.

Tipo dinamico

Il tipo dynamic indica l'uso della variabile e dei riferimenti ai relativi membri per escludere il controllo del tipo in fase di compilazione. Queste operazioni vengono risolte in fase di esecuzione. Il tipo dynamic semplifica l'accesso alle API COM, ad esempio le API di automazione di Office, alle API dinamiche, ad esempio le librerie di IronPython, e al modello DOM (Document Object Model) HTML.

Il tipo dynamic si comporta come tipo object nella maggior parte dei casi. In particolare, qualsiasi espressione non null può essere convertita nel tipo dynamic. Il tipo dynamic si comporta diversamente da object nelle operazioni che contengono espressioni di tipo dynamic che non vengono risolte o il tipo non viene verificato dal compilatore. Il compilatore raggruppa le informazioni sull'operazione e tali informazioni successivamente vengono usate per valutare l'operazione in fase di esecuzione. Come parte del processo, le variabili di tipo dynamic vengono compilate in variabili di tipo object. Di conseguenza, il tipo dynamic esiste solo in fase di compilazione, non in fase di esecuzione.

Nell'esempio seguente vengono messe a confronto una variabile di tipo dynamic e una variabile di tipo object. Per verificare il tipo di ogni variabile in fase di compilazione, posizionare il puntatore del mouse su dyn o obj nelle istruzioni WriteLine. Copiare il codice seguente in un editor in cui IntelliSense è disponibile. IntelliSense visualizza dynamic per dyn e object per obj.

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

Le istruzioni WriteLine visualizzano i tipi in fase di esecuzione di dyn e obj. A questo punto, entrambe hanno lo stesso tipo, un numero intero. Viene prodotto l'output seguente:

System.Int32
System.Int32

Per vedere la differenza tra dyn e obj in fase di compilazione, aggiungere le due righe seguenti tra le dichiarazioni e le istruzioni WriteLine dell'esempio precedente.

dyn = dyn + 3;
obj = obj + 3;

Viene segnalato un errore del compilatore per il tentativo di aggiunta di un numero intero e di un oggetto nell'espressione obj + 3. Tuttavia non vengono segnalati errori per dyn + 3. L'espressione che contiene dyn non viene controllata in fase di compilazione perché il tipo di dyn è dynamic.

Nell'esempio seguente viene usato dynamic in diverse dichiarazioni. Il metodo Main confronta anche il controllo dei tipi in fase di compilazione con il controllo dei tipi in fase di esecuzione.

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.ExampleMethod(10));
            Console.WriteLine(ec.ExampleMethod("value"));

            // The following line causes a compiler error because ExampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.ExampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.ExampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to ExampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.ExampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic _field;
        dynamic Prop { get; set; }

        public dynamic ExampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Vedi anche