Examiner les propriétés implémentées automatiquement

Terminée

La syntaxe standard pour définir une propriété en C# inclut un accesseur get et un accesseur set. Toutefois, quand aucune autre logique n’est requise dans les accesseurs, vous pouvez utiliser des propriétés implémentées automatiquement.

Propriétés implémentées automatiquement

Les propriétés implémentées automatiquement rendent la déclaration de propriété plus concise lorsqu’aucune autre logique n’est requise dans les accesseurs de propriétés. Elles permettent également au code client de créer des objets. Lorsque vous déclarez une propriété comme indiqué dans l’exemple suivant, le compilateur crée un champ de stockage privé et anonyme accessible uniquement via les accesseurs get et set de la propriété. Les accesseurs init peuvent également être déclarés comme des propriétés implémentées automatiquement.

L’exemple suivant montre une classe simple qui a des propriétés implémentées automatiquement :


// 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;
    }
}

Les propriétés implémentées automatiquement et stockées dans des champs déclarent un champ de stockage d’instance privée. Vous pouvez initialiser les propriétés implémentées automatiquement de la même façon que les champs :


public string FirstName { get; set; } = "FirstName";

La classe Customer indiquée dans l’exemple précédent est mutable. Le code client peut changer les valeurs des objets après leur création. Dans les classes complexes qui contiennent un comportement significatif (méthodes) et des données, il est souvent nécessaire d’avoir des propriétés publiques. Toutefois, pour les petites classes ou structs qui encapsulent juste un ensemble de valeurs (données) et n’ont que peu ou pas de comportements, vous devez utiliser l’une des options suivantes pour rendre les objets immuables :

  • Déclarez uniquement un accesseur get (immuable partout sauf le constructeur).
  • Déclarez un accesseur get et un accesseur init (immuable partout, sauf lors de la construction de l’objet).
  • Déclarez l’accesseur set comme privé (immuable pour les consommateurs).

Vous devrez peut-être ajouter une validation à une propriété implémentée automatiquement. C# 13 ajoute des propriétés stockées dans des champs en tant que fonctionnalité en préversion. Vous utilisez le mot clé de champ pour accéder au champ de stockage synthétisé par le compilateur d’une propriété implémentée automatiquement. Par exemple, vous pouvez vous assurer que la propriété FirstName de l’exemple précédent ne peut pas être définie sur Null ou sur la chaîne vide :


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";

Cette fonctionnalité vous permet d’ajouter une logique aux accesseurs sans vous obliger à déclarer explicitement le champ de stockage. Vous utilisez le mot clé de champ pour accéder au champ de stockage généré par le compilateur.

Important

Le mot clé field est une fonctionnalité en préversion de C# 13. Vous devez utiliser .NET 9 et définir votre élément <LangVersion> sur preview dans votre fichier projet afin d’utiliser le mot clé contextuel field.

Vous devez être prudent lorsque vous utilisez 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 l’étendue d’un accesseur de propriété. Vous pouvez changer le nom de la variable field ou utiliser le jeton @ pour référencer l’identificateur field en tant que @field.

Propriétés avec des champs de stockage

Vous pouvez combiner le concept d’une propriété calculée avec un champ privé et créer une propriété évaluée mise en cache. Par exemple, mettez à jour la propriété FullName afin que la mise en forme de la chaîne ait lieu lors du premier accès :


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;
        }
    }
}

Cette implémentation fonctionne parce que les propriétés FirstName et LastName sont en lecture seule. Les personnes peuvent changer leur nom. La mise à jour des propriétés FirstName et LastName pour autoriser les accesseurs set vous oblige à invalider n’importe quelle valeur mise en cache pour fullName. Vous modifiez les accesseurs set des propriétés FirstName et LastName afin que le champ fullName soit recalculé :


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;
        }
    }
}

Cette version finale évalue la propriété FullName uniquement si nécessaire. La version calculée précédemment est utilisée si elle est valide. Sinon, le calcul met à jour la valeur mise en cache. Les développeurs qui utilisent cette classe n’ont pas besoin de connaître les détails de l’implémentation. Aucune de ces modifications internes n’affecte l’utilisation de l’objet Person.

À compter de C# 13, vous pouvez créer des partial properties dans des classes partielles. La déclaration d’implémentation d’une propriété partial ne peut pas être une propriété implémentée automatiquement. Une propriété implémentée automatiquement utilise la même syntaxe qu’une déclaration de propriété partielle déclarative.

Implémenter une classe légère avec des propriétés implémentées automatiquement

Vous pouvez rencontrer des cas où vous devez créer une classe légère immuable qui sert uniquement à encapsuler un ensemble de propriétés implémentées automatiquement. Utilisez ce type de construction au lieu d’un struct lorsque vous devez utiliser la sémantique de type référence.

Vous pouvez créer une propriété immuable de la manière suivante :

  • Déclarez uniquement l’accesseur get, ce qui rend la propriété immuable partout, sauf dans le constructeur du type.
  • Déclarez un accesseur init au lieu d’un accesseur set, ce qui rend la propriété définissable uniquement dans le constructeur ou à l’aide d’un initialiseur d’objet.
  • Déclarez l’accesseur set comme private. La propriété est définissable dans le type, mais elle est immuable pour les consommateurs.

Vous pouvez ajouter le modificateur required à la déclaration de propriété pour forcer les appelants à définir la propriété dans le cadre de l’initialisation d’un nouvel objet.

L’exemple suivant montre en quoi une propriété avec un seul accesseur get diffère d’une propriété avec un accesseur get et un accesseur set privé.


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’exemple suivant montre deux façons d’implémenter une classe immuable à l’aide de propriétés implémentées automatiquement. Chaque façon déclare l’une des propriétés avec un set privé et l’une des propriétés avec un get uniquement. La première classe utilise un constructeur uniquement pour initialiser les propriétés, tandis que la seconde classe utilise une méthode de fabrique statique qui appelle un constructeur.


// 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.
*/

Le compilateur crée des champs de stockage pour chaque propriété implémentée automatiquement. Les champs ne sont pas accessibles directement à partir du code source.