Esaminare le proprietà implementate automaticamente
La sintassi standard per la definizione di una proprietà in C# include una funzione di accesso get e una funzione di accesso set. Tuttavia, quando non sono necessarie altre logiche nelle funzioni di accesso, è possibile usare le proprietà implementate automaticamente.
Proprietà implementate automaticamente
Le proprietà implementate automaticamente rendono la dichiarazione di proprietà più concisa quando non sono necessarie altre logiche nelle funzioni di accesso alle proprietà. Consentono anche al codice client di creare oggetti. Quando si dichiara una proprietà come illustrato nell'esempio seguente, il compilatore crea un campo sottostante privato e anonimo accessibile solo tramite le funzioni di accesso get e set della proprietà.
init funzioni di accesso possono anche essere dichiarate come proprietà implementate automaticamente.
L'esempio seguente mostra una classe semplice con alcune proprietà implementate automaticamente:
// This class is mutable. Its data can be modified from
// outside the class.
public class Customer
{
// Auto-implemented properties for trivial get and set
public double TotalPurchases { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }
// Constructor
public Customer(double purchases, string name, int id)
{
TotalPurchases = purchases;
Name = name;
CustomerId = id;
}
// Methods
public string GetContactInfo() { return "ContactInfo"; }
public string GetTransactionHistory() { return "History"; }
// .. Other methods, events, etc.
}
class Program
{
static void Main()
{
// Initialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);
// Modify a property.
cust1.TotalPurchases += 499.99;
}
}
Le proprietà supportate dal campo e implementate automaticamente dichiarano un campo sottostante di un'istanza privata. È possibile inizializzare le proprietà implementate automaticamente in modo analogo ai campi:
public string FirstName { get; set; } = "FirstName";
La classe Customer illustrata nell'esempio precedente è modificabile. Il codice client può modificare i valori negli oggetti dopo la creazione. Nelle classi complesse che contengono un comportamento significativo (metodi) e dati, è spesso necessario avere proprietà pubbliche. Tuttavia, per classi o struct di piccole dimensioni che incapsulano semplicemente un set di valori (dati) e presentano comportamenti minimi o nulli, è consigliabile usare una delle opzioni seguenti per rendere gli oggetti non modificabili:
- Dichiarare solo una funzione di accesso get (non modificabile ovunque tranne il costruttore).
- Dichiarare una funzione di accesso get e una funzione di accesso init (non modificabile ovunque tranne durante la costruzione di oggetti).
- Dichiarare la funzione di accesso set come privata (non modificabile ai consumer).
Potrebbe essere necessario aggiungere la convalida a una proprietà implementata automaticamente. C# 13 aggiunge le proprietà supportate dai campi come funzionalità di anteprima. Usare la parola chiave field per accedere al campo sottostante sintetizzato dal compilatore di una proprietà implementata automaticamente. Ad esempio, è possibile assicurarsi che la proprietà FirstName nell'esempio precedente non possa essere impostata su Null o sulla stringa vuota:
public string FirstName
{
get;
set
{
field = (string.IsNullOrWhiteSpace(value) is false
? value
: throw new ArgumentException(nameof(value), "First name can't be whitespace or null"));
}
} = "FirstName";
Questa funzionalità consente di aggiungere logica alle funzioni di accesso senza che sia necessario dichiarare in modo esplicito il campo sottostante. Usare la parola chiave field per accedere al campo sottostante generato dal compilatore.
Importante
La parola chiave field è una funzionalità di anteprima in C# 13. È necessario usare .NET 9 e impostare l'elemento <LangVersion> su preview 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 di field oppure usare il token di @ per fare riferimento all'identificatore field come @field.
Proprietà con campi di backup
È possibile combinare il concetto di una proprietà calcolata con un campo privato e creare una proprietà valutata memorizzata nella cache. Ad esempio, aggiornare la proprietà FullName in modo che la formattazione della stringa venga eseguita al primo accesso:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public required string FirstName { get; init; }
public required string LastName { get; init; }
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
Questa implementazione funziona perché le proprietà FirstName e LastName sono di sola lettura. Le persone possono cambiare il nome. L'aggiornamento delle proprietà FirstName e LastName per consentire le funzioni di accesso set richiede di invalidare qualsiasi valore memorizzato nella cache per fullName. Modifichi le funzioni di accesso set delle proprietà FirstName e LastName in modo che il campo fullName sia calcolato di nuovo.
public class Person
{
private string? _firstName;
public string? FirstName
{
get => _firstName;
set
{
_firstName = value;
_fullName = null;
}
}
private string? _lastName;
public string? LastName
{
get => _lastName;
set
{
_lastName = value;
_fullName = null;
}
}
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
Questa versione finale valuta la proprietà FullName solo quando necessario. La versione calcolata in precedenza viene utilizzata se valida. In caso contrario, il calcolo aggiorna il valore memorizzato nella cache. Gli sviluppatori che usano questa classe non devono conoscere i dettagli dell'implementazione. Nessuna di queste modifiche interne influisce sull'uso dell'oggetto Person.
A partire da C# 13, è possibile creare partial properties in classi parziali. La dichiarazione di implementazione per una proprietà partial non può essere una proprietà implementata automaticamente. Una proprietà implementata automaticamente usa la stessa sintassi di una dichiarazione di proprietà parziale dichiarante.
Implementare una classe leggera con proprietà implementate automaticamente
Possono verificarsi situazioni in cui è necessario creare una classe leggera non modificabile che serve solo per incapsulare un set di proprietà implementate automaticamente. Usare questo tipo di costrutto anziché un struct quando è necessario usare la semantica del tipo di riferimento.
È possibile rendere una proprietà non modificabile nei modi seguenti:
- Dichiarare solo la funzione di accesso
get, che rende la proprietà non modificabile ovunque tranne nel costruttore del tipo. - Dichiarare una funzione di accesso
initanziché una funzione di accessoset, che rende la proprietà impostabile solo nel costruttore o utilizzando un inizializzatore di oggetto. - Dichiarare che la funzione di accesso
setdeve essereprivate. La proprietà è impostabile all'interno del tipo, ma non è modificabile per i consumer.
È possibile aggiungere il modificatore required alla dichiarazione di proprietà per forzare i chiamanti a impostare la proprietà come parte dell'inizializzazione di un nuovo oggetto.
Nell'esempio seguente viene illustrato come una proprietà con solo la funzione di accesso get sia diversa da quella con get e set privato.
class Contact
{
public string Name { get; }
public string Address { get; private set; }
public Contact(string contactName, string contactAddress)
{
// Both properties are accessible in the constructor.
Name = contactName;
Address = contactAddress;
}
// Name isn't assignable here. This will generate a compile error.
//public void ChangeName(string newName) => Name = newName;
// Address is assignable here.
public void ChangeAddress(string newAddress) => Address = newAddress;
}
L'esempio seguente illustra due modi per implementare una classe non modificabile usando proprietà implementate automaticamente. Ogni modo dichiara una delle proprietà con un set privato e una delle proprietà solo con un get. La prima classe usa un costruttore solo per inizializzare le proprietà e la seconda classe usa un metodo factory statico che chiama un costruttore.
// This class is immutable. After an object is created,
// it can't be modified from outside the class. It uses a
// constructor to initialize its properties.
class Contact
{
// Read-only property.
public string Name { get; }
// Read-write property with a private set accessor.
public string Address { get; private set; }
// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}
// This class is immutable. After an object is created,
// it can't be modified from outside the class. It uses a
// static method and private constructor to initialize its properties.
public class Contact2
{
// Read-write property with a private set accessor.
public string Name { get; private set; }
// Read-only property.
public string Address { get; }
// Private constructor.
private Contact2(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
// Public factory method.
public static Contact2 CreateContact(string name, string address)
{
return new Contact2(name, address);
}
}
public class Program
{
static void Main()
{
// Some simple data sources.
string[] names = ["Person One","Person Two", "Person Three",
"Person Four", "Person Five"];
string[] addresses = ["123 Main St.", "345 Cypress Ave.", "678 1st Ave",
"12 108th St.", "89 E. 42nd St."];
// Simple query to demonstrate object creation in select clause.
// Create Contact objects by using a constructor.
var query1 = from i in Enumerable.Range(0, 5)
select new Contact(names[i], addresses[i]);
// List elements can't be modified by client code.
var list = query1.ToList();
foreach (var contact in list)
{
Console.WriteLine("{0}, {1}", contact.Name, contact.Address);
}
// Create Contact2 objects by using a static factory method.
var query2 = from i in Enumerable.Range(0, 5)
select Contact2.CreateContact(names[i], addresses[i]);
// Console output is identical to query1.
var list2 = query2.ToList();
// List elements can't be modified by client code.
// CS0272:
// list2[0].Name = "Person Six";
}
}
/* Output:
Person One, 123 Main St.
Person Two, 345 Cypress Ave.
Person Three, 678 1st Ave
Person Four, 12 108th St.
Person Five, 89 E. 42nd St.
*/
Il compilatore crea campi di backup per ogni proprietà implementata automaticamente. I campi non sono accessibili direttamente dal codice sorgente.