Membres d’instance en lecture seule
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Elle inclut les changements de spécification proposés, ainsi que les informations nécessaires à la conception et au développement de la fonctionnalité. Ces articles sont publiés jusqu'à ce que les changements proposés soient finalisés et incorporés dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l'implémentation réalisée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).
Pour en savoir plus sur le processus d'adoption des speclets de fonctionnalité dans la norme du langage C#, consultez l'article sur les spécifications.
Problème de champion : <https://github.com/dotnet/csharplang/issues/1710>
Récapitulatif
Fournissez un moyen de spécifier des membres d’instance individuels dans une structure qui ne modifient pas l’état, de la même façon que readonly struct
spécifie qu'aucun membre d'instance ne modifie l’état.
Il convient de noter que readonly instance member != pure instance member. Un membre d’instance pur garantit qu’aucun état ne sera modifié. Un membre d’instance readonly
garantit uniquement que l’état de l’instance ne sera pas modifié.
Tous les membres d'instance d'un readonly struct
pourraient être considérés comme des membres d'instance implicitement en lecture seule Les membres d'instance explicites en lecture seule déclarés sur des structures non lisibles se comporteraient de la même manière. Par exemple, ils continueraient à créer des copies cachées si vous appeliez un membre d'instance (sur l'instance courante ou sur un champ de l'instance) qui n'était pas lui-même en lecture seule.
Motivation
Avant C# 8.0, les utilisateurs ont la possibilité de créer des types readonly struct
, le compilateur s'assure que tous les champs sont en lecture seule (et par extension, qu’aucun membre d’instance ne modifie l’état). Toutefois, il existe certains scénarios où vous disposez d’une API existante qui expose des champs accessibles ou qui utilise une combinaison de membres mutables et non mutables. Dans ces circonstances, vous ne pouvez pas marquer le type comme readonly
(il s’agirait d’un changement majeur).
Cela n’a normalement pas beaucoup d’impact, sauf dans le cas des paramètres in
. Avec les paramètres in
pour les structures non lisibles, le compilateur fera une copie du paramètre pour chaque invocation du membre d'instance, puisqu'il ne peut pas garantir que l'invocation ne modifie pas l'état interne. Cela peut conduire à une multitude de copies et à des performances globales moins bonnes que si vous aviez passé la structure directement par sa valeur. Pour obtenir un exemple, consultez ce code sur sharplab.
Certains autres scénarios où des copies masquées peuvent se produire incluent les champs ,static readonly
, et les littéraux ,. Si elles sont supportées à l'avenir, les constantes blittables se retrouveraient dans le même bateau, c'est-à-dire qu'elles nécessitent toutes actuellement une copie complète (lors de l'invocation d'un membre de l'instance) si la structure n'est pas marquée readonly
.
Création
Autorisez un utilisateur à spécifier qu’un membre d’instance est, lui-même, readonly
et ne modifie pas l’état de l’instance (avec toute les vérifications appropriées effectuées par le compilateur, bien sûr). Exemple :
public struct Vector2
{
public float x;
public float y;
public readonly float GetLengthReadonly()
{
return MathF.Sqrt(LengthSquared);
}
public float GetLength()
{
return MathF.Sqrt(LengthSquared);
}
public readonly float GetLengthIllegal()
{
var tmp = MathF.Sqrt(LengthSquared);
x = tmp; // Compiler error, cannot write x
y = tmp; // Compiler error, cannot write y
return tmp;
}
public readonly float LengthSquared
{
get
{
return (x * x) +
(y * y);
}
}
}
public static class MyClass
{
public static float ExistingBehavior(in Vector2 vector)
{
// This code causes a hidden copy, the compiler effectively emits:
// var tmpVector = vector;
// return tmpVector.GetLength();
//
// This is done because the compiler doesn't know that `GetLength()`
// won't mutate `vector`.
return vector.GetLength();
}
public static float ReadonlyBehavior(in Vector2 vector)
{
// This code is emitted exactly as listed. There are no hidden
// copies as the `readonly` modifier indicates that the method
// won't mutate `vector`.
return vector.GetLengthReadonly();
}
}
Readonly peut être appliqué aux accesseurs de propriété pour indiquer que this
ne sera pas muté dans l'accesseur. Les exemples suivants ont des setters en lecture seule, car ces accesseurs modifient l’état du champ membre, mais ne modifient pas sa valeur.
public readonly int Prop1
{
get
{
return this._store["Prop1"];
}
set
{
this._store["Prop1"] = value;
}
}
Lorsque readonly
est appliqué à la propriété syntaxique, cela signifie que tous les accesseurs sont readonly
.
public readonly int Prop2
{
get
{
return this._store["Prop2"];
}
set
{
this._store["Prop2"] = value;
}
}
Readonly ne peut être appliqué qu’aux accesseurs qui ne modifient pas le type contenant.
public int Prop3
{
readonly get
{
return this._prop3;
}
set
{
this._prop3 = value;
}
}
Readonly peut être appliqué à certaines propriétés auto-implémentées, mais cela n’aura pas d’effet significatif. Le compilateur traitera tous les getters implémentés automatiquement comme étant en lecture seule, que le mot clé readonly
soit présent ou non.
// Allowed
public readonly int Prop4 { get; }
public int Prop5 { readonly get; set; }
// Not allowed
public int Prop6 { readonly get; }
public readonly int Prop7 { get; set; }
public int Prop8 { get; readonly set; }
Readonly peut être appliqué à des événements implémentés manuellement, mais pas à des événements de type champ. La lecture seule ne peut pas être appliquée aux accesseurs d'événements individuels (ajout/suppression).
// Allowed
public readonly event Action<EventArgs> Event1
{
add { }
remove { }
}
// Not allowed
public readonly event Action<EventArgs> Event2;
public event Action<EventArgs> Event3
{
readonly add { }
readonly remove { }
}
public static readonly event Event4
{
add { }
remove { }
}
Voici d’autres exemples de syntaxe :
- Membres à corps d'expression :
public readonly float ExpressionBodiedMember => (x * x) + (y * y);
- Contraintes génériques :
public readonly void GenericMethod<T>(T value) where T : struct { }
Le compilateur émettrait le membre d’instance, comme d’habitude, et émettrait en plus un attribut reconnu par le compilateur indiquant que le membre d’instance ne modifie pas l’état. Cela entraîne effectivement la transformation du paramètre caché this
en in T
au lieu de ref T
.
Cela permettrait à l’utilisateur d’appeler en toute sécurité la méthode d’instance sans que le compilateur ait besoin d’en faire une copie.
Les restrictions seraient les suivantes :
- Le modificateur
readonly
ne peut pas être appliqué aux méthodes statiques, aux constructeurs ou aux destructeurs. - Le modificateur
readonly
ne peut pas être appliqué aux délégués. - Le modificateur
readonly
ne peut pas être appliqué aux membres de classe ou d’interface.
Inconvénients
Les mêmes inconvénients que ceux des méthodes readonly struct
aujourd'hui. Certaines lignes de code peuvent toujours entraîner des copies masquées.
Notes
L’utilisation d’un attribut ou d’un autre mot clé peut également être possible.
Cette proposition est quelque peu liée à (mais est plus un sous-ensemble de) functional purity
et/ou constant expressions
, qui ont tous deux fait l’objet de propositions existantes.
C# feature specifications