Prise en main des propriétés et des accesseurs de classe
Les propriétés de classe fournissent un mécanisme flexible pour lire, écrire ou calculer la valeur d’un champ de données. Ils apparaissent en tant que membres de données publiques, mais ils sont implémentés en tant que méthodes spéciales appelées accesseurs. Cette fonctionnalité permet aux appelants d’accéder facilement aux données et contribue toujours à promouvoir la sécurité et la flexibilité des données.
Utilisation des propriétés
Les propriétés combinent des aspects des champs et des méthodes. Pour l’utilisateur d’un objet, une propriété semble être un champ. L’accès à la propriété nécessite la même syntaxe que l’accès à un champ. Pour l’implémenteur d’une classe, une propriété est un ou deux blocs de code représentant un accesseur get et/ou un accesseur set ou init. Le bloc de code de l’accesseur get est exécuté lorsque la propriété est lue. Le bloc de code de l’accesseur set ou init est exécuté lorsque la propriété est affectée à une valeur. Une propriété sans accesseur set est considérée comme en lecture seule. Une propriété sans accesseur get est considérée comme accessible en écriture uniquement. Une propriété qui possède les deux accesseurs est en lecture-écriture. Vous pouvez utiliser un accesseur init au lieu d’un accesseur set pour permettre à la propriété d’être définie dans le cadre de l’initialisation d’objet, mais dans le cas contraire, faites-le en lecture seule.
Contrairement aux champs, les propriétés ne sont pas classées comme variables. Par conséquent, vous ne pouvez pas passer une propriété en tant que paramètre ref ou out.
Les propriétés sont souvent utilisées pour prendre en charge les scénarios suivants :
- Ils peuvent valider les données avant d’autoriser une modification.
- Ils peuvent exposer de manière transparente des données sur une classe où ces données sont récupérées à partir d’une autre source, telle qu’une base de données.
- Ils peuvent effectuer une action lorsque les données sont modifiées, telles que le déclenchement d’un événement ou la modification de la valeur d’autres champs.
Les propriétés sont déclarées dans le bloc de code d’une définition de classe. Les propriétés sont déclarées en spécifiant le niveau d’accès du champ, suivi du type de la propriété, suivi du nom de la propriété, suivi d’un bloc de code qui déclare un accesseur get et/ou un accesseur set.
Accesseur get
Le corps de l’accesseur get ressemble à celui d’une méthode. Elle doit retourner une valeur du type de propriété. Le compilateur C# et le compilateur JIT (juste à temps) détectent les modèles courants pour implémenter l’accesseur get et les optimisent.
Par exemple, un accesseur get qui retourne un champ sans effectuer de calcul est probablement optimisé pour une lecture mémoire de ce champ. Les propriétés implémentées automatiquement, examinées dans l’unité suivante de ce module, suivent ce modèle et bénéficient de ces optimisations. Toutefois, une méthode d’accesseur de get virtuelle ne peut pas être incorporée, car le compilateur ne sait pas au moment de la compilation quelle méthode peut réellement être appelée au moment de l’exécution.
L’exemple suivant montre un accesseur get qui retourne la valeur d’un champ privé _name:
public class Employee
{
private string _name = "unknown"; // the name field
public string Name
{
get { return _name; } // the Name property
}
}
Dans cet exemple, la classe Employee inclut un champ privé et une propriété publique. Voici une explication de la définition de classe Employee :
- La classe
Employeeest définie avec le modificateur d’accès public, ce qui signifie qu’elle est accessible à partir de n’importe quel autre code dans le même assembly ou un autre assembly qui le référence. - Le champ privé
_nameest déclaré avec le typestringet reçoit une valeur par défaut,"unknown". - La propriété publique
Nameest déclarée avec le typestring. La propriété inclut un accesseurgetqui retourne la valeur du champ_name.
Note
Les champs qui fournissent les valeurs aux accesseurs de propriété sont souvent appelés champs de support.
Prenez une minute pour prendre en compte le code qui instancie un objet Employee et lit la valeur de la propriété 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
Cet exemple de code instancie un nouvel objet Employee nommé employee. Lorsque votre code référence la propriété employee.Name, à l’exception de la cible d’une affectation, l’accesseur get est appelé pour lire la valeur de la propriété. Dans ce cas, l’accesseur get retourne la valeur du champ _name, qui est affecté à la valeur "unknown".
Le bloc de code d’un accesseur get peut également être utilisé pour retourner une valeur calculée. Cela peut être utile lorsque vous souhaitez vous assurer qu’une propriété retourne toujours une valeur non null. Par exemple:
public class Manager
{
private string? _name;
public string Name
{
get
{
return _name != null ? _name : "NA";
}
}
}
Accesseur set
L’accesseur set ressemble à une méthode dont le type de retour est void. Il utilise un paramètre implicite appelé value, dont le type est le type de la propriété. Le compilateur et le compilateur JIT reconnaissent également les modèles courants pour un accesseur set ou init. Ces modèles courants sont optimisés, en écrivant directement dans la mémoire pour le champ de stockage. Dans l’exemple suivant, un accesseur set est ajouté à la propriété Name :
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get
{
return _name != null ? _name : "NA";
}
set
{
_name = value;
}
}
}
Prenez une minute pour prendre en compte le code qui crée une instance de la classe Student. Lorsque vous affectez une valeur à la propriété Name, l’accesseur set est appelé à l’aide d’un argument qui fournit la nouvelle valeur. Par exemple:
var student = new Student();
student.Name = "StudentName"; // the set accessor is invoked here
Console.Write(student.Name); // the get accessor is invoked here
Accesseur init
Le code permettant de créer un accesseur init est identique au code pour créer un accesseur set, sauf que vous utilisez le mot clé init au lieu de set. La différence est que l’accesseur init ne peut être utilisé que dans le constructeur ou à l’aide d’un d’initialiseur d’objet.
Déclarer et utiliser des propriétés en lecture-écriture
Les propriétés offrent la commodité des membres de données publics sans les risques liés à un accès non protégé, non contrôlé et non vérifié aux données d’un objet. Les propriétés déclarent des accesseurs : méthodes spéciales qui attribuent et récupèrent des valeurs à partir du membre de données sous-jacent. L’accesseur set permet aux membres de données d’être affectés, et l’accesseur get récupère les valeurs des membres de données.
Cet exemple montre une classe Person qui a deux propriétés : Name (chaîne) et Age (int). Les deux propriétés fournissent des accesseurs get et set, de sorte qu’elles sont considérées comme des propriétés en lecture/écriture.
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
*/
Syntaxe d’accesseur et techniques de codage
En plus de la syntaxe de base pour déclarer des propriétés, C# inclut la syntaxe qui vous permet d’écrire du code plus concis et expressif. Ces fonctionnalités sont les suivantes :
- Membres expression-bodied
- Propriétés reposant sur un champ de stockage
- Propriétés requises
Membres expression-bodied
Les membres à corps d'expression offrent une syntaxe plus concise pour écrire des accesseurs de propriété sur une seule ligne.
Note
Les membres expression-bodied peuvent également être appliqués aux méthodes, aux indexeurs et aux accesseurs d’événements.
Les accesseurs de propriété sont souvent des instructions sur une ligne qui ne font qu’assigner ou retourner le résultat d’une expression. La classe Person que vous avez examinée précédemment dans cette unité est un bon exemple :
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get
{
return _name != null ? _name : "NA";
}
set
{
_name = value;
}
}
}
Dans cet exemple, la propriété Name utilise une instruction à ligne unique pour renvoyer la valeur du champ _name s’il n’est pas null, ou la chaîne « NA » si elle a la valeur Null. L’accesseur set utilise une instruction à ligne unique pour affecter la valeur de la propriété Name au champ _name.
Une définition de corps d’expression se compose du jeton => suivi de l’expression utilisée pour affecter ou récupérer la valeur de propriété. Étant donné que les accesseurs définis pour la classe Student implémentent une instruction à ligne unique, il s’agit d’un bon candidat pour la mise à jour à l’aide de définitions de corps d’expression.
L’extrait de code suivant met à jour la classe Student à l’aide de définitions de corps d’expression pour les accesseurs get et set :
class Student
{
private string? _name; // the name field
public string Name // the Name property
{
get => _name ?? "NA";
set => _name = value;
}
}
Propriétés adossées à un champ
À compter de C# 13, vous pouvez ajouter une validation ou une autre logique dans l’accesseur d’une propriété à l’aide de la fonctionnalité d’aperçu de mot clé field. Le mot clé field accède au champ de stockage synthétisé du compilateur pour une propriété. Il vous permet d’écrire un accesseur de propriété sans déclarer explicitement un champ de support distinct.
public class Person
{
public string? FirstName
{
get;
set => field = value.Trim();
}
// Omitted for brevity.
}
Important
Le mot clé field est une fonctionnalité d’aperçu en C# 13. Vous devez utiliser .NET 9 et définir votre élément <LangVersion> pour afficher un aperçu dans votre fichier projet afin d’utiliser le mot clé contextuel field.
Vous devez être prudent à l’aide de la fonctionnalité de mot clé field dans une classe qui a un champ nommé field. Le nouveau mot-clé field masque un champ nommé field dans la portée d’un accesseur de propriété. Vous pouvez modifier le nom de la variable field ou utiliser le jeton @ pour référencer l’identificateur de champ comme @field.
Propriétés requises
L’exemple précédent permet à un appelant de créer un Person à l’aide du constructeur par défaut, sans définir la propriété FirstName. Le type de propriété est défini sur une chaîne nullable. À compter de C# 11, vous pouvez exiger que les appelants définissent une propriété :
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;
public required string FirstName { get; init; }
// Omitted for brevity.
}
Le code précédent apporte deux modifications à la classe Person. Tout d’abord, la déclaration de propriété FirstName inclut le modificateur required. Cela signifie que tout code qui crée une nouvelle Person doit définir cette propriété à l’aide d’un initialiseur d’objet. Deuxièmement, le constructeur qui accepte un paramètre firstName a l’attribut System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Cet attribut informe le compilateur que ce constructeur définit tous les membres requis. Les appelants qui utilisent ce constructeur n’ont pas besoin de définir les propriétés required avec un initialiseur d’objet.
Important
Ne confondez pas required avec des valeurs non nullables. Il est valide de définir une propriété required sur null ou default. Si le type n’est pas nullable, par exemple string dans ces exemples, le compilateur émet un avertissement.
var aPerson = new Person("PersonName");
aPerson = new Person{ FirstName = "PersonName"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();