Tipi di riferimento predefiniti (riferimenti per C#)

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 viene convertita in un tipo valore, viene detto object 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 a = "good " + "morning";

Questo crea un oggetto stringa contenente "good morning".

Le stringhe sono immutabili: non è possibile modificare il contenuto di un oggetto stringa dopo la creazione dell'oggetto, sebbene la sintassi sembri indicare che è possibile apportare modifiche. 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 l'accesso in sola lettura ai singoli caratteri di una stringa. I valori di indice 0 validi iniziano da 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 l'iterazione di 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

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 ("):

"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 sono elaborate, quindi rendono più semplice scrivere ad esempio un nome file di Windows completo:

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

Per includere le virgolette doppie in una stringa @-quoted, duplicarla:

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

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 di delegati e la rimozione hanno esito negativo 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 runtime.
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;

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

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

Vedi anche