Membres de l’instance Readonly
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .
Problème défendu : https://github.com/dotnet/csharplang/issues/1710
Résumé
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 a un mélange de membres mutants et non mutants. 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 de 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
Autoriser 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 la vérification appropriée effectuée par le compilateur, bien sûr). Par 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 la valeur de ce champ membre.
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 mutent pas le type conteneur.
public int Prop3
{
readonly get
{
return this._prop3;
}
set
{
this._prop3 = value;
}
}
Readonly peut être appliqué à certaines propriétés implémentées automatiquement, mais elle 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 également un attribut reconnu par le compilateur indiquant que le membre de l’instance ne modifie pas l’état. Ainsi, le paramètre de this
masqué devient 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 n’a besoin d’effectuer une copie.
Les restrictions sont les suivantes :
- Le modificateur
readonly
ne peut pas être appliqué aux méthodes statiques, aux constructeurs ou aux destructeurs. - Impossible d’appliquer le modificateur
readonly
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
, dont les deux ont eu des propositions existantes.
C# feature specifications