Una classe è un tipo che definisci tu: raggruppa dei dati (campi) con il codice che lavora su quei dati (metodi). Un oggetto è un'istanza di una classe, creata a runtime con new. Una classe, tanti oggetti, ognuno con i propri valori.
Dichiarare una classe e creare oggetti
La dichiarazione di una classe elenca i suoi membri tra parentesi graffe. I campi contengono i dati, i metodi il comportamento. new ClassName() alloca un oggetto e ti dà un riferimento a esso.
Output:
Maya: 120.50
Omar: 40
Withdraw 200 succeeded: False
Ogni oggetto ha i propri Owner e Balance. Dentro un metodo, Balance significa "il saldo dell'oggetto su cui è stato chiamato questo metodo", ed è per questo che maya.Deposit e omar.Deposit modificano numeri diversi. Quell'oggetto implicito è disponibile come this quando ti serve nominarlo.
I campi che non assegni partono dal loro valore predefinito: 0 per i numeri, false per bool e null per le stringhe e gli altri tipi classe.
Qui i membri sono public perché Main possa raggiungerli. Le classi reali di solito tengono i campi private ed espongono invece delle proprietà; la pagina sui modificatori di accesso spiega le regole. Una classe può anche dichiarare un costruttore per impostare i suoi campi quando l'oggetto viene creato, argomento della pagina sui costruttori.
Una classe è un tipo riferimento
Una variabile di tipo classe non contiene l'oggetto. Contiene un riferimento a un oggetto che vive nell'heap. Assegnare la variabile copia il riferimento, quindi due variabili possono puntare allo stesso oggetto.
Output:
5
6
True
False
Qui si vedono tre conseguenze:
- Modificare l'oggetto tramite
secondè visibile tramitefirst, perché esiste un solo oggetto. - Passare un oggetto a un metodo passa il riferimento. Il metodo può modificare i campi dell'oggetto e il chiamante vede la modifica. (Assegnare un nuovo oggetto al parametro dentro il metodo non influisce sulla variabile del chiamante; per quello serve
ref.) ==tra due istanze di una classe confronta per impostazione predefinita i riferimenti: "è lo stesso oggetto?", non "hanno gli stessi valori?".lookalikeha dati identici ed è comunque un oggetto diverso.
Se vuoi copie nell'assegnazione e confronto per valore, guarda le struct, che sono tipi valore, o i record, che si confrontano per valore.
null e NullReferenceException
Una variabile riferimento può contenere null, cioè "nessun oggetto". Usare un membro tramite null lancia NullReferenceException, l'eccezione a runtime più comune nel codice C#.
Output:
True
no city on file
NullReferenceException: Address was never set
Lisbon
La soluzione non è quasi mai catturare l'eccezione. Scopri perché il riferimento è null e poi crea l'oggetto prima, controlla prima dell'uso oppure usa l'operatore null-condizionale ?., che si ferma e restituisce null invece di lanciare un'eccezione. ?? fornisce poi un valore di ripiego. Entrambi gli operatori sono trattati nei tipi nullable.
Override di ToString
Ogni classe eredita da object, che fornisce ToString(). La versione ereditata restituisce il nome completo del tipo (incluso il namespace, se ne ha uno), che raramente è ciò che vuoi stampare. Fanne l'override:
Output:
Plain
Mug (8.50)
In cart: Mug (8.50)
Total line: Mug (8.50)
Console.WriteLine, l'interpolazione di stringhe e + con una stringa chiamano tutti ToString() al posto tuo. Anche il debugger lo mostra, il che rende utile un override anche quando non stampi mai l'oggetto.
Inizializzatori di oggetto
Un inizializzatore di oggetto imposta campi e proprietà pubblici subito dopo la costruzione dell'oggetto, nella stessa espressione. Si legge meglio di una serie di assegnazioni e funziona bene dentro i letterali di collezione.
Output:
A12: 25 (0 extras)
A13: 40 (2 extras)
Due dettagli nel secondo biglietto. Price = 40m sovrascrive il 25m dell'inizializzatore del campo, perché l'inizializzatore di oggetto viene eseguito dopo che l'oggetto è stato costruito. Extras = { "Parking", "Poster" } non ha new: chiama Add sulla lista che il campo contiene già, invece di sostituirla.
Un inizializzatore di oggetto non è un costruttore. Può impostare solo i membri accessibili dal codice chiamante e non può imporre che un membro venga impostato. Quando un oggetto non è valido senza certi valori, richiedili in un costruttore.
Classi partial
La parola chiave partial divide una classe su più file. Il compilatore unisce le parti in un'unica classe.
// Invoice.cs
public partial class Invoice
{
public decimal Subtotal;
public decimal Total() => Subtotal + Tax();
}
// Invoice.Tax.cs
public partial class Invoice
{
private decimal Tax() => Subtotal * 0.2m;
}
Ogni parte deve dire partial e stare nello stesso namespace e nello stesso progetto. L'uso tipico è il codice generato: i designer di WinForms, i source generator e alcuni ORM scrivono una parte e lasciano l'altra a te, così una nuova generazione non sovrascrive mai il tuo codice. Dividere su più file una classe scritta a mano solo per accorciarla di solito nasconde una classe che dovrebbe essere due classi.
Errori comuni
- Usare un oggetto prima di crearlo. Un campo di tipo classe è
nullfinché non gli assegninew .... Il primo accesso a un membro lanciaNullReferenceException. - Aspettarsi che l'assegnazione copi.
var b = a;con una classe dà un secondo nome allo stesso oggetto. Crea un nuovo oggetto e copia i valori quando te ne serve uno indipendente. - Confrontare oggetti con
==aspettandosi un confronto per valore. Per le classi confronta i riferimenti, a meno che la classe non ridefinisca==(come fastring). Confronta i campi rilevanti oppure fai l'override diEquals. - Stampare un oggetto e ottenere il nome del tipo. Fai l'override di
ToString(). - Dichiarare tutto
public. Funziona negli esempi piccoli, ma permette a qualsiasi codice di mettere l'oggetto in uno stato non valido, per esempio un saldo negativo. Tieni i campi privati e modificali tramite metodi che controllano le regole.
Domande frequenti
Cos'è una classe in C#?
Una classe è un tipo che definisci tu: un gruppo con un nome di dati (campi e proprietà) e comportamento (metodi). class Account { public decimal Balance; public void Deposit(decimal amount) { Balance += amount; } } ne dichiara una. Una classe è un progetto; non fa nulla finché non crei degli oggetti a partire da essa con new.
Che differenza c'è tra una classe e un oggetto in C#?
La classe è la definizione, scritta una volta nel codice sorgente. Un oggetto è una sua istanza, creata a runtime con new, con una propria copia di ogni campo di istanza. Scrivere new Account() due volte ti dà due oggetti della stessa classe con saldi indipendenti.
Come si crea un oggetto in C#?
Usa new seguito dal nome della classe e dagli argomenti del costruttore: var account = new Account();. Puoi impostare campi e proprietà pubblici nella stessa espressione con un inizializzatore di oggetto: new Account { Owner = "Maya" }. La variabile contiene un riferimento al nuovo oggetto, non l'oggetto stesso.
Perché ottengo una NullReferenceException in C#?
Hai usato un membro (.Name, .Length, una chiamata di metodo) tramite una variabile che contiene null, quindi non c'è alcun oggetto su cui agire. Le cause comuni sono un campo mai assegnato (i campi di una classe partono da null), una ricerca che ha restituito null o un metodo che ha restituito null in caso di errore. Controlla con if (x != null), usa x?.Name oppure assicurati che l'oggetto venga creato prima dell'uso.
Come faccio l'override di ToString in C#?
Dichiara public override string ToString() nella tua classe e restituisci il testo che vuoi, per esempio public override string ToString() => $"{Name} ({Price:F2})";. Senza di esso, Console.WriteLine(obj) e l'interpolazione di stringhe stampano il nome della classe, perché è ciò che restituisce object.ToString().