Introduzione alle proprietà e alle funzioni di accesso della classe
Le proprietà della classe forniscono un meccanismo flessibile per leggere, scrivere o calcolare il valore di un campo dati. Vengono visualizzati come membri dati pubblici, ma vengono implementati come metodi speciali denominati funzioni di accesso . Questa funzionalità consente ai chiamanti di accedere facilmente ai dati e contribuisce comunque a promuovere la sicurezza e la flessibilità dei dati.
Uso delle proprietà
Le proprietà combinano aspetti di campi e metodi. Per l'utente di un oggetto, una proprietà sembra essere un campo. L'accesso alla proprietà richiede la stessa sintassi dell'accesso a un campo. Per l'implementatore di una classe, una proprietà è uno o due blocchi di codice, che rappresentano una funzione di accesso get e/o una set o una funzione di accesso init. Il blocco di codice per la funzione di accesso get viene eseguito quando la proprietà viene letta. Il blocco di codice per la funzione di accesso set o init viene eseguito quando alla proprietà viene assegnato un valore. Una proprietà senza una funzione di accesso set è considerata di sola lettura. Una proprietà senza una funzione di accesso get è considerata di sola scrittura. Una proprietà con entrambe le funzioni di accesso è di lettura/scrittura. È possibile usare una funzione di accesso init anziché una funzione di accesso set per abilitare l'impostazione della proprietà come parte dell'inizializzazione dell'oggetto, ma renderla di sola lettura.
A differenza dei campi, le proprietà non vengono classificate come variabili. Pertanto, non è possibile passare una proprietà come parametro ref o out.
Le proprietà vengono spesso usate per supportare gli scenari seguenti:
- Possono convalidare i dati prima di consentire una modifica.
- Possono esporre in modo trasparente i dati in una classe in cui tali dati vengono recuperati da un'altra origine, ad esempio un database.
- Possono eseguire un'azione quando i dati vengono modificati, ad esempio la generazione di un evento o la modifica del valore di altri campi.
Le proprietà vengono dichiarate nel blocco di codice di una definizione di classe. Le proprietà vengono dichiarate specificando il livello di accesso del campo, seguito dal tipo della proprietà, seguito dal nome della proprietà, seguito da un blocco di codice che dichiara una funzione di accesso get e/o una funzione di accesso set.
Funzione di accesso get
Il corpo della funzione di accesso get è simile a quello di un metodo. Deve restituire un valore del tipo di proprietà. Il compilatore C# e il compilatore JIT rilevano modelli comuni per l'implementazione della funzione di accesso get e l'ottimizzazione per tali modelli.
Ad esempio, una funzione di accesso get che restituisce un campo senza eseguire alcun calcolo è probabilmente ottimizzata per una lettura di memoria di tale campo. Le proprietà implementate automaticamente, esaminate nell'unità successiva di questo modulo, seguono questo modello e traggono vantaggio da queste ottimizzazioni. Tuttavia, un metodo della funzione di accesso virtuale get non può essere inlined perché il compilatore non conosce in fase di compilazione quale metodo potrebbe effettivamente essere chiamato in fase di esecuzione.
Nell'esempio seguente viene illustrata una funzione di accesso get che restituisce il valore di un campo privato _name:
public class Employee
{
private string _name = "unknown"; // the name field
public string Name
{
get { return _name; } // the Name property
}
}
In questo esempio, la classe Employee include un campo privato e una proprietà pubblica. Ecco una spiegazione della definizione della classe Employee:
- La classe
Employeeviene definita con il modificatore di accesso pubblico, ovvero è accessibile da qualsiasi altro codice nello stesso assembly o da un altro assembly che vi fa riferimento. - Il campo privato
_nameviene dichiarato con il tipo distringe viene assegnato un valore predefinito,"unknown". - La proprietà pubblica
Nameviene dichiarata con il tipo distring. La proprietà include una funzione di accessogetche restituisce il valore del campo_name.
Nota
I campi che forniscono i valori per le funzioni di accesso alle proprietà vengono spesso chiamati campi di backup.
Prendere in considerazione il codice che crea un'istanza di un oggetto Employee e legge il valore della proprietà Name:
Employee employee = new Employee(); // create an instance of the Employee class
Console.Write(employee.Name); // use the get accessor to read the value of the Name property
Questo esempio di codice crea un'istanza di un nuovo oggetto Employee denominato employee. Quando il codice fa riferimento alla proprietà employee.Name, ad eccezione della destinazione di un'assegnazione, la funzione di accesso get viene richiamata per leggere il valore della proprietà. In questo caso, la funzione di accesso get restituisce il valore del campo _name, a cui viene assegnato il valore "unknown".
Il blocco di codice per una funzione di accesso get può essere usato anche per restituire un valore calcolato. Ciò può essere utile quando si desidera assicurarsi che una proprietà restituisca sempre un valore non Null. Per esempio:
public class Manager
{
private string? _name;
public string Name
{
get
{
return _name != null ? _name : "NA";
}
}
}
Funzione di accesso set
La funzione di accesso set è simile a un metodo il cui tipo restituito è void. Usa un parametro implicito denominato value, il cui tipo è il tipo della proprietà . Il compilatore e il compilatore JIT riconoscono anche modelli comuni per una funzione di accesso set o init. Questi modelli comuni sono ottimizzati, scrivendo direttamente la memoria per il campo sottostante. Nell'esempio seguente viene aggiunta una funzione di accesso set alla proprietà Name:
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get
{
return _name != null ? _name : "NA";
}
set
{
_name = value;
}
}
}
Prendere in considerazione il codice che crea un'istanza della classe Student. Quando si assegna un valore alla proprietà Name, la funzione di accesso set viene richiamata usando un argomento che fornisce il nuovo valore. Per esempio:
var student = new Student();
student.Name = "StudentName"; // the set accessor is invoked here
Console.Write(student.Name); // the get accessor is invoked here
Funzione di accesso init
Il codice per creare una funzione di accesso init è uguale al codice per creare una funzione di accesso set, ad eccezione del fatto che si usa la parola chiave init anziché set. La differenza è che la funzione di accesso init può essere usata solo nel costruttore o usando un inizializzatore oggetto .
Dichiarare e usare le proprietà di lettura/scrittura
Le proprietà offrono la comodità dei membri dati pubblici senza i rischi che comportano l'accesso non protetto, non controllato e non verificato ai dati di un oggetto. Le proprietà dichiarano le funzioni di accesso: metodi speciali che assegnano e recuperano valori dal membro dati sottostante. La funzione di accesso set consente di assegnare membri dati e la funzione di accesso get recupera i valori dei membri dati.
Questo esempio mostra una classe Person con due proprietà: Name (stringa) e Age (int). Entrambe le proprietà forniscono get e set funzioni di accesso, quindi sono considerate proprietà di lettura/scrittura.
class Person
{
private string _name = "N/A";
private int _age = 0;
// Declare a Name property of type string:
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
// Declare an Age property of type int:
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
}
class TestPerson
{
static void Main()
{
// Create a new Person object named person:
Person person = new Person();
// Print out the default name and age of the person:
Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");
// Set some values on the person object:
person.Name = "PersonName";
person.Age = 99;
Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");
// Increment the Age property:
person.Age += 1;
Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Person details - Name = N/A, Age = 0
Person details - Name = PersonName, Age = 99
Person details - Name = PersonName, Age = 100
*/
Sintassi e tecniche di codifica delle funzioni di accesso
Oltre alla sintassi di base per la dichiarazione delle proprietà, C# include la sintassi che consente di scrivere codice più conciso ed espressivo. Queste funzionalità includono:
- Membri con corpo di espressione
- Proprietà supportate dal campo
- Proprietà obbligatorie
Membri con corpo di espressione
I membri con corpo di espressione forniscono una sintassi più concisa per la scrittura di funzioni di accesso alle proprietà a riga singola.
Nota
I membri con corpo di espressione possono essere applicati anche a metodi, indicizzatori e funzioni di accesso agli eventi.
Le funzioni di accesso alle proprietà sono spesso costituite da istruzioni a riga singola che assegnano o restituiscono il risultato di un'espressione. La classe Person esaminata in precedenza in questa unità è un buon esempio:
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get
{
return _name != null ? _name : "NA";
}
set
{
_name = value;
}
}
}
In questo esempio, la proprietà Name usa un'istruzione a riga singola per restituire il valore del campo _name se non è Null o la stringa "NA" se è Null. La funzione di accesso set usa un'istruzione a riga singola per assegnare il valore della proprietà Name al campo _name.
Una definizione del corpo dell'espressione è costituita dal token => seguito dall'espressione usata per assegnare o recuperare il valore della proprietà. Poiché le funzioni di accesso definite per la classe Student implementano un'istruzione a riga singola, è un buon candidato per l'aggiornamento usando le definizioni del corpo dell'espressione.
Il frammento di codice seguente aggiorna la classe Student usando le definizioni del corpo dell'espressione per le funzioni di accesso get e set:
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get => _name ?? "NA";
set => _name = value;
}
}
Proprietà supportate dal campo
A partire da C# 13, è possibile aggiungere la convalida o un'altra logica nella funzione di accesso per una proprietà usando la funzionalità di anteprima delle parole chiave field. La parola chiave field accede al campo sottostante sintetizzato dal compilatore per una proprietà. Consente di scrivere una funzione di accesso alle proprietà senza dichiarare esplicitamente un campo sottostante separato.
public class Person
{
public string? FirstName
{
get;
set => field = value.Trim();
}
// Omitted for brevity.
}
Importante
La parola chiave field è una funzionalità di anteprima in C# 13. È necessario usare .NET 9 e impostare l'elemento <LangVersion> in anteprima nel file di progetto per usare la parola chiave contestuale field.
È consigliabile prestare attenzione usando la funzionalità di parola chiave field in una classe con un campo denominato field. La nuova parola chiave field ombreggiate un campo denominato field nell'ambito di una funzione di accesso a una proprietà. È possibile modificare il nome della variabile field oppure usare il token di @ per fare riferimento all'identificatore di campo come @field.
Proprietà obbligatorie
L'esempio precedente consente a un chiamante di creare un Person usando il costruttore predefinito, senza impostare la proprietà FirstName. Il tipo di proprietà è impostato su una stringa nullable. A partire da C# 11, è possibile richiedere ai chiamanti di impostare una proprietà:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;
public required string FirstName { get; init; }
// Omitted for brevity.
}
Il codice precedente apporta due modifiche alla classe Person. Prima di tutto, la dichiarazione di proprietà FirstName include il modificatore required. Ciò significa che qualsiasi codice che crea un nuovo Person deve impostare questa proprietà usando un inizializzatore di oggetto. In secondo luogo, il costruttore che accetta un parametro firstName ha l'attributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Questo attributo informa il compilatore che questo costruttore imposta tutti i membri necessari. I chiamanti che usano questo costruttore non devono impostare required proprietà con un inizializzatore di oggetto.
Importante
Non confondere required con non nullable. È possibile impostare una proprietà required su null o default. Se il tipo non è nullable, ad esempio string in questi esempi, il compilatore genera un avviso.
var aPerson = new Person("PersonName");
aPerson = new Person{ FirstName = "PersonName"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();