Une propriété ressemble à un champ pour le code qui l'utilise (order.Total, order.Total = 5), mais c'est en réalité une paire de méthodes : un accesseur get qui renvoie une valeur et un accesseur set qui en reçoit une. Cette indirection permet à une classe de vérifier des valeurs, de les calculer ou de refuser des écritures, sans changer la façon dont les appelants l'utilisent.
Champs ou propriétés
Un champ public donne un accès direct au stockage de l'objet. N'importe qui peut y mettre n'importe quoi :
public class Account
{
public decimal Balance; // any code can write -500 here
}
Une propriété place du code entre l'appelant et le stockage. Voici la forme complète, écrite à la main : un champ de stockage privé plus une propriété avec les deux accesseurs. Dans set, le mot-clé value est la valeur affectée.
Sortie :
250
Rejected a negative balance
250
Au-delà de la validation, les propriétés comptent parce que la plupart de l'écosystème .NET travaille avec elles et ignore les champs : les interfaces peuvent déclarer des propriétés mais pas de champs d'instance, la liaison de données de WPF et MAUI se lie à des propriétés, et System.Text.Json sérialise par défaut les propriétés publiques et ignore les champs.
Propriétés automatiques
Quand les accesseurs ne feraient que lire et écrire le champ, laissez le compilateur les écrire. { get; set; } est une propriété implémentée automatiquement : le compilateur génère pour vous le champ de stockage caché.
Sortie :
Notebook: 9.99, 10 left
Cela ressemble à un champ, alors pourquoi s'en donner la peine ? Parce que transformer plus tard un champ public en propriété est un changement cassant pour le code compilé qui l'utilise (et pour les arguments ref et out), alors que transformer une propriété automatique en propriété complète avec validation est invisible pour les appelants. Commencer par { get; set; } garde cette option ouverte gratuitement.
private set et propriétés en lecture seule
La plupart des états doivent être lisibles par tous et modifiables seulement par l'objet lui-même. Donnez au setter un modificateur d'accès plus restrictif :
Sortie :
Ines: 2 items, total 15.75
Trois sortes de lecture seule ici :
{ get; private set; }: la classe peut la modifier à tout moment, le code extérieur non.{ get; }(propriété automatique en lecture seule, C# 6) : ne peut être affectée que dans un constructeur ou un initialiseur. Après la construction, personne ne peut la modifier, pas même la classe. C'est ainsi qu'on crée une propriété immuable.=> expression(propriété calculée) : aucun stockage. L'expression s'exécute à chaque lecture, doncTotalest toujours à jour avec la liste.
Une propriété calculée doit être peu coûteuse et sans effet de bord, car les appelants lisent les propriétés sans y penser, dans des boucles et dans le débogueur. Si obtenir la valeur demande un vrai travail (une requête en base, un gros calcul), faites-en une méthode comme CalculateTotal() pour que le coût soit visible.
Accesseurs à corps d'expression
Les propriétés complètes aux accesseurs d'une ligne peuvent utiliser => pour chaque accesseur :
private string title;
public string Title
{
get => title;
set => title = value?.Trim() ?? "";
}
public decimal Total => SumPrices(); est un raccourci pour une propriété qui n'a qu'un getter ; get => ... applique la même idée dans une propriété qui a aussi un setter.
Logique dans le getter et le setter : notifications et valeurs paresseuses
Comme les accesseurs sont des méthodes, ils peuvent faire plus que stocker une valeur. Deux schémas courants : déclencher une notification de changement depuis le setter, et calculer une valeur à la première lecture puis la mettre en cache.
Sortie :
(building summary)
Theme: light
Theme: light
Theme changed
(building summary)
Theme: dark
Changed est un événement, et le schéma du setter est celui des implémentations de INotifyPropertyChanged dans WPF et MAUI. nameof(Theme) garde la chaîne synchronisée si la propriété est renommée.
L'erreur de récursion infinie
Le bug de propriété le plus courant est un setter qui affecte la propriété au lieu du champ de stockage :
public string Name
{
get { return Name; } // calls get again, forever
set { Name = value; } // calls set again, forever
}
Chaque accesseur s'appelle lui-même, et le programme meurt avec une StackOverflowException, qui ne peut pas être interceptée. La correction consiste à lire et écrire un champ distinct (name, en minuscules), ou à utiliser une propriété automatique. C# 14 ajoute le mot-clé field exactement pour ce cas : dans un accesseur, field désigne le champ de stockage généré par le compilateur, donc set => field = value.Trim(); fonctionne sans en déclarer un.
Les accesseurs init (C# 9)
Une propriété en lecture seule vous oblige à passer chaque valeur par un constructeur. C# 9 a ajouté init, un setter autorisé seulement pendant la création de l'objet : dans un constructeur, ou dans un initialiseur d'objet.
public class Product
{
public string Sku { get; init; }
public decimal Price { get; init; }
}
var p = new Product { Sku = "MUG-01", Price = 8.50m }; // fine: during creation
p.Price = 4m; // error CS8852: Init-only property or indexer 'Product.Price' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor
Le résultat est un objet immuable avec la syntaxe d'initialiseur, exactement ce qu'utilisent les classes record pour leurs propriétés positionnelles. Pour « modifier » un tel objet, créez-en un nouveau ; les records en font une seule ligne avec with.
En C# 7, l'équivalent le plus proche est une propriété en lecture seule plus un paramètre de constructeur :
public class Product
{
public string Sku { get; }
public decimal Price { get; }
public Product(string sku, decimal price) { Sku = sku; Price = price; }
}
Membres required (C# 11)
Un initialiseur d'objet ne peut obliger les appelants à définir quoi que ce soit : new Product { } compile même quand Sku ne doit jamais être vide. C# 11 a ajouté le modificateur required :
public class User
{
public required string Email { get; init; }
public string DisplayName { get; init; } = "";
}
var ok = new User { Email = "ana@example.com" };
var bad = new User { DisplayName = "Ana" }; // error CS9035: Required member 'User.Email'
// must be set in the object initializer or attribute constructor.
required fonctionne avec set et init. Un constructeur qui définit lui-même les membres requis peut l'indiquer avec l'attribut [SetsRequiredMembers], pour que les appelants qui utilisent ce constructeur n'aient pas à les définir à nouveau.
Erreurs courantes
- Récursion dans un accesseur.
set { Name = value; }s'appelle lui-même. Utilisez un champ de stockage ou une propriété automatique. - Des getters coûteux ou à effets de bord. Les lecteurs s'attendent à ce qu'une lecture de propriété soit rapide et répétable sans risque. Utilisez une méthode pour le travail.
- Des champs publics « pour l'instant ». Convertir un champ en propriété plus tard casse la compatibilité binaire, et les sérialiseurs ignorent les champs par défaut. Commencez par une propriété automatique.
- Des setters publics partout.
{ get; set; }sur un solde ou un statut invite aux états invalides. Utilisezprivate set,initou la lecture seule, et modifiez l'état via des méthodes. - Renvoyer une collection modifiable depuis une propriété en lecture seule.
public List<string> Tags { get; }empêche les appelants de remplacer la liste, pas d'y ajouter des éléments. RenvoyezIReadOnlyList<string>quand la collection ne doit pas changer depuis l'extérieur.
Questions fréquentes
Que signifie { get; set; } en C# ?
Cela déclare une propriété implémentée automatiquement : le compilateur crée un champ privé caché, un accesseur get qui le renvoie et un accesseur set qui l'affecte. public string Name { get; set; } se comporte comme un champ public pour les appelants, mais c'est une paire de méthodes, donc vous pouvez plus tard ajouter une validation ou la passer en private set sans changer le code qui l'utilise.
Quelle est la différence entre un champ et une propriété en C# ?
Un champ est une variable stockée dans l'objet. Une propriété est une paire de méthodes (get et set) qui ressemble à un champ vue de l'extérieur. Les propriétés permettent à une classe de valider des valeurs, de calculer des résultats ou de restreindre l'écriture, et ce sont elles que manipulent la liaison de données, les sérialiseurs et les interfaces. Les données publiques doivent être exposées sous forme de propriétés ; les champs restent en général privés.
Qu'est-ce que private set en C# ?
public int Stock { get; private set; } permet à n'importe quel code de lire la propriété, mais seul le code de la classe peut la modifier. C'est ainsi qu'une classe expose un état qui lui appartient, comme un solde ou un compteur, tout en gardant chaque modification derrière des méthodes qui appliquent les règles.
Que fait init en C# ?
init (C# 9) est un setter qui ne fonctionne que pendant la création de l'objet : dans un constructeur ou un initialiseur d'objet. public string Sku { get; init; } autorise new Product { Sku = "A-1" } mais rejette ensuite product.Sku = "B-2" avec l'erreur CS8852. Il donne aux objets immuables la syntaxe d'initialiseur.
Qu'est-ce qu'une propriété required en C# ?
Une propriété marquée required (C# 11) doit être définie par chaque initialiseur d'objet qui crée le type ; l'omettre est une erreur de compilation (CS9035). Elle se combine bien avec init : public required string Email { get; init; } doit être fournie à la création et ne peut plus changer ensuite.