Utilisation de propriétés (Guide de programmation C#)

Les propriétés allient des caractéristiques des champs et des méthodes. Pour l’utilisateur d’un objet, une propriété apparaît comme un champ. Pour accéder à celle-ci, il doit utiliser la même syntaxe. 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 correspondant à l’accesseur get est exécuté à la lecture de la propriété ; le bloc de code correspondant à l’accesseur set ou init est exécuté au moment où une valeur est attribuée à la propriété. Une propriété sans accesseur set est considérée comme étant en lecture seule. Une propriété sans accesseur get est considérée comme étant en écriture seule. 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 de l’objet, mais autrement la rendre accessible en lecture seule.

Contrairement aux champs, les propriétés ne sont pas classifiées en tant que variables. Par conséquent, vous ne pouvez pas passer une propriété en tant que paramètre ref ou out.

Il existe de nombreuses utilisations pour les propriétés :

  • Elles peuvent valider les données avant d’autoriser une modification.
  • Elles peuvent exposer de manière transparente des données dans une classe où ces données sont extraites à partir d’une autre source, telle qu’une base de données.
  • Elles peuvent agir lorsque les données sont modifiées, par exemple en déclenchant un événement ou en modifiant la valeur d’autres champs.

Les propriétés sont déclarées dans le bloc de classe en spécifiant le niveau d’accès du champ, suivi du type de la propriété, du nom de la propriété et d’un bloc de code qui déclare un accesseur get et/ou un accesseur set. Par exemple :

public class Date
{
    private int _month = 7;  // Backing store

    public int Month
    {
        get => _month;
        set
        {
            if ((value > 0) && (value < 13))
            {
                _month = value;
            }
        }
    }
}

Dans cet exemple, Month est déclaré en tant que propriété pour permettre à l’accesseur set de vérifier que la valeur définie de Month se trouve bien entre 1 et 12. La propriété Month utilise un champ privé pour assurer le suivi de la valeur réelle. L’emplacement réel des données d’une propriété est souvent appelé « magasin de stockage » de la propriété. Il est courant que les propriétés utilisent des champs privés comme magasin de stockage. Le champ est marqué comme étant privé pour garantir qu’il ne peut être modifié qu’en appelant la propriété. Pour plus d’informations sur les restrictions d’accès public et privé, consultez Modificateurs d’accès. Les propriétés implémentées automatiquement fournissent une syntaxe simplifiée pour des déclarations de propriété simples. Pour plus d’informations, consultez Propriétés implémentées automatiquement.

Accesseur get

Le corps de l’accesseur get ressemble à celui d’une méthode. Il doit retourner une valeur du type de la propriété. Le compilateur C# et le compilateur Juste-à-temps (JIT) détectent les modèles courants de mise en œuvre de l’accesseur get et optimisent ces modèles. 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 suivent ce modèle et bénéficient de ces optimisations. Cependant, une méthode d’accesseur get virtuelle ne peut pas être intégrée car le compilateur ne sait pas, au moment de la compilation, quelle méthode pourrait être appelée au moment de l’exécution. Voici un exemple d’accesseur get qui retourne la valeur d’un champ privé _name :

class Employee
{
    private string _name;  // the name field
    public string Name => _name;     // the Name property
}

Quand vous faites référence à la propriété (pas en tant que cible d’une assignation), l’accesseur get est appelé pour lire la valeur de la propriété. Par exemple :

var employee= new Employee();
//...

System.Console.Write(employee.Name);  // the get accessor is invoked here

L’accesseur get doit être un membre à corps d’expression, ou se terminer par une instruction return ou throw, et le contrôle ne peut pas circuler hors du corps de l’accesseur.

Avertissement

Modifier l’état de l’objet en utilisant l’accesseur get n’est pas un style de programmation approprié.

L’accesseur get peut être utilisé pour retourner la valeur du champ ou pour la calculer et la retourner. Par exemple :

class Manager
{
    private string _name;
    public string Name => _name != null ? _name : "NA";
}

Dans l’exemple précédent, si vous n’attribuez pas de valeur à la propriété Name, la valeur NA est retournée.

Accesseur set

L’accesseur set ressemble à une méthode dont le type de retour est void. Il utilise un paramètre implicite nommé value, dont le type est celui de la propriété. Le compilateur et le compilateur JIT reconnaissent également des 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 ci-dessous, un accesseur set est ajouté à la propriété Name :

class Student
{
    private string _name;  // the name field
    public string Name    // the Name property
    {
        get => _name;
        set => _name = value;
    }
}

Quand vous assignez une valeur à la propriété, l’accesseur set est appelé en utilisant un argument qui fournit la nouvelle valeur. Par exemple :

var student = new Student();
student.Name = "Joe";  // the set accessor is invoked here

System.Console.Write(student.Name);  // the get accessor is invoked here

Utiliser le nom de paramètre implicite, value, pour une déclaration de variable locale dans un accesseur set, est une erreur.

Accesseur init

Le code permettant de créer un accesseur init est identique au code utilisé 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 initialiseur d’objet.

Remarques

Les propriétés peuvent être marquées comme étant public, private, protected, internal, protected internal ou private protected. Ces modificateurs d’accès définissent comment les utilisateurs de la classe peuvent accéder à la propriété. Les accesseurs get et set d’une même propriété peuvent avoir des modificateurs d’accès différents. Par exemple, le get peut être public pour autoriser l’accès en lecture seule en dehors du type, tandis que le set peut être private ou protected. Pour plus d’informations, consultez Modificateurs d’accès.

Une propriété peut être déclarée en tant que propriété statique à l’aide du mot clé static. Les propriétés statiques sont accessibles à tout moment aux appelants, même s’il n’existe aucune instance de la classe. Pour plus d’informations, consultez la page Classes statiques et membres de classes statiques.

Une propriété peut être marquée comme étant une propriété virtuelle à l’aide du mot clé virtual. Les propriétés virtuelles permettent aux classes dérivées de substituer le comportement de la propriété à l’aide du mot clé override. Pour plus d'informations sur ces options, consultez Héritage.

Une propriété qui se substitue à une propriété virtuelle peut aussi être sealed, ce qui signifie que pour les classes dérivées, elle n’est plus virtuelle. Enfin, une propriété peut être déclarée comme étant abstract. Les propriétés abstraites ne définissent pas d’implémentation dans la classe, et les classes dérivées doivent écrire leur propre implémentation. Pour plus d’informations sur ces options, consultez Classes abstract et sealed et membres de classe.

Notes

Il s’agit d’une erreur d’utiliser un modificateur virtual, abstract ou override sur un accesseur de propriété static.

Exemples

Cet exemple illustre les propriétés d’instance, statiques et en lecture seule. Il accepte le nom de l’employé à partir du clavier, incrémente NumberOfEmployees de 1 et affiche le nom et le numéro de l’employé.

public class Employee
{
    public static int NumberOfEmployees;
    private static int _counter;
    private string _name;

    // A read-write instance property:
    public string Name
    {
        get => _name;
        set => _name = value;
    }

    // A read-only static property:
    public static int Counter => _counter;

    // A Constructor:
    public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}

Exemple de propriété masquée

Cet exemple montre comment accéder à une propriété de classe de base qui est masquée par une autre propriété qui porte le même nom dans une classe dérivée :

public class Employee
{
    private string _name;
    public string Name
    {
        get => _name;
        set => _name = value;
    }
}

public class Manager : Employee
{
    private string _name;

    // Notice the use of the new modifier:
    public new string Name
    {
        get => _name;
        set => _name = value + ", Manager";
    }
}

class TestHiding
{
    public static void Test()
    {
        Manager m1 = new Manager();

        // Derived class property.
        m1.Name = "John";

        // Base class property.
        ((Employee)m1).Name = "Mary";

        System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
        System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
    }
}
/* Output:
    Name in the derived class is: John, Manager
    Name in the base class is: Mary
*/

Voici les points importants de l’exemple précédent :

  • La propriété Name de la classe dérivée masque la propriété Name de la classe de base. En pareil cas, le modificateur new est utilisé dans la déclaration de la propriété de la classe dérivée :
    public new string Name
    
  • La conversion de type (Employee) est utilisée pour accéder à la propriété masquée de la classe de base :
    ((Employee)m1).Name = "Mary";
    

Pour plus d’informations sur le masquage des membres, consultez new, modificateur.

Exemple de propriété de remplacement

Dans cet exemple, deux classes, Cube et Square, implémentent une classe abstract, Shape, et remplacent sa propriété Area abstract. Notez l’utilisation du modificateur override sur les propriétés. Le programme accepte le côté (« side ») comme entrée et calcule les surfaces (« areas ») du carré (« square ») et du cube. De même, il accepte la surface (« area ») comme entrée et calcule le côté (« side ») correspondant du carré (« square ») et du cube.

abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    //constructor
    public Square(double s) => side = s;

    public override double Area
    {
        get => side * side;
        set => side = System.Math.Sqrt(value);
    }
}

class Cube : Shape
{
    public double side;

    //constructor
    public Cube(double s) => side = s;

    public override double Area
    {
        get => 6 * side * side;
        set => side = System.Math.Sqrt(value / 6);
    }
}

class TestShapes
{
    static void Main()
    {
        // Input the side:
        System.Console.Write("Enter the side: ");
        double side = double.Parse(System.Console.ReadLine());

        // Compute the areas:
        Square s = new Square(side);
        Cube c = new Cube(side);

        // Display the results:
        System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
        System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
        System.Console.WriteLine();

        // Input the area:
        System.Console.Write("Enter the area: ");
        double area = double.Parse(System.Console.ReadLine());

        // Compute the sides:
        s.Area = area;
        c.Area = area;

        // Display the results:
        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}
/* Example Output:
    Enter the side: 4
    Area of the square = 16.00
    Area of the cube = 96.00

    Enter the area: 24
    Side of the square = 4.90
    Side of the cube = 2.00
*/

Voir aussi