Je suis curieux de savoir quel genre de considérations un dispositif tel que celui-ci doit subir.
Tout d'abord, j'écris un blog sur ce sujet, entre autres. Voir mon ancien blog :
http://blogs.msdn.com/b/ericlippert/
et mon nouveau blog :
http://ericlippert.com
pour de nombreux articles sur divers aspects de la conception des langues.
Deuxièmement, le processus de conception de C# est désormais ouvert au public. Vous pouvez donc voir par vous-même ce que l'équipe de conception du langage prend en compte lors de l'examen des suggestions de nouvelles fonctionnalités. Voir https://github.com/dotnet/roslyn/ pour les détails.
Quels seraient les coûts liés à l'ajout de ce type de mot-clé à la bibliothèque ?
Cela dépend de beaucoup de choses. Il n'y a pas, bien sûr, de fonctionnalités faciles et bon marché. Il n'y a que des fonctions moins chères et moins difficiles. En général, les coûts sont ceux qui impliquent la conception, la spécification, l'implémentation, les tests, la documentation et la maintenance de la fonctionnalité. Il y a aussi des coûts plus exotiques, comme le coût d'opportunité de ne pas faire une meilleure fonctionnalité, ou le coût de choisir une fonctionnalité qui interagit mal avec des fonctionnalités futures que nous pourrions vouloir ajouter.
Dans ce cas, la fonctionnalité consisterait probablement à faire du mot-clé "lazy" un sucre syntaxique pour l'utilisation de l'option Lazy<T>
. Il s'agit d'une fonctionnalité assez simple, qui ne nécessite pas d'analyse syntaxique ou sémantique sophistiquée.
Dans quelles situations cela pourrait-il être problématique ?
Je peux penser à un certain nombre de facteurs qui me pousseraient à repousser cette fonctionnalité.
Tout d'abord, il n'est pas nécessaire ; c'est simplement un sucre pratique. Il n'ajoute pas vraiment de nouvelle puissance à la langue. Les avantages ne semblent pas en valoir le coût.
Deuxièmement, et plus important encore, elle consacre une particulier une sorte de paresse dans la langue. Il y a plus d'un type de paresse, et nous pourrions faire un mauvais choix.
Comment se fait-il qu'il y ait plus d'un type de paresse ? Eh bien, réfléchissez à la façon dont elle serait mise en œuvre. Les propriétés sont déjà "paresseuses" dans la mesure où leurs valeurs ne sont pas calculées avant que la propriété ne soit appelée, mais vous voulez plus que cela ; vous voulez une propriété qui est appelée une fois, puis la valeur est mise en cache pour la prochaine fois. Par "paresseux", vous entendez essentiellement une propriété mémorisée. Quelles garanties devons-nous mettre en place ? Il existe de nombreuses possibilités :
Possibilité n° 1 : pas du tout à l'abri du filtrage. Si vous appelez la propriété pour la "première" fois sur deux threads différents, tout peut arriver. Si vous voulez éviter les conditions de course, vous devez ajouter la synchronisation vous-même.
Possibilité n° 2 : Threadsafe, de sorte que deux appels à la propriété sur deux threads différents appellent tous deux la fonction d'initialisation, puis font la course pour voir qui remplit la valeur réelle dans le cache. On peut supposer que la fonction renverra la même valeur sur les deux threads, donc le coût supplémentaire ici est simplement dans l'appel supplémentaire gaspillé. Mais le cache est threadsafe, et ne bloque aucun thread. (Parce que le cache threadsafe peut être écrit avec du code low-lock ou no-lock).
Le code destiné à mettre en œuvre la sécurité des fils a un coût, même s'il s'agit d'un code à faible verrouillage. Ce coût est-il acceptable ? La plupart des gens écrivent des programmes qui sont effectivement monofilaires ; est-il juste d'ajouter les frais généraux de la sécurité des fils à chaque appel de propriété paresseux, qu'il soit nécessaire ou non ?
Possibilité n°3 : Threadsafe tel qu'il y a une forte garantie que la fonction d'initialisation ne sera appelée qu'une seule fois ; il n'y a pas de course sur le cache. L'utilisateur peut s'attendre implicitement à ce que la fonction d'initialisation ne soit appelée qu'une seule fois ; elle peut être très coûteuse et deux appels sur deux threads différents peuvent être inacceptables. L'implémentation de ce type de paresse nécessite une synchronisation complète où il est possible qu'un thread se bloque indéfiniment pendant que la méthode paresseuse est exécutée sur un autre thread. Cela signifie également qu'il peut y avoir des blocages si la méthode paresseuse pose un problème d'ordre de blocage.
Cela ajoute encore plus de coût à la fonction, un coût qui est supporté de manière égale par les personnes qui font pas d'en tirer parti (parce qu'ils écrivent des programmes monofilaires).
Alors, comment faire face à cette situation ? Nous pourrions ajouter trois fonctionnalités : "lazy not threadsafe", "lazy threadsafe with races" et "lazy threadsafe with blocking and maybe deadlocks". Et maintenant, la fonctionnalité vient de devenir beaucoup plus coûteuse et chemin plus difficile à documenter. Cela produit un énorme problème d'éducation des utilisateurs. Chaque fois que vous donnez un tel choix à un développeur, vous lui donnez l'occasion d'écrire de terribles bogues.
Troisièmement, la fonctionnalité semble faible comme indiqué. Pourquoi la paresse devrait-elle être appliquée uniquement aux propriétés ? Il semble que cela pourrait être appliqué de manière générale à travers le système de type :
lazy int x = M(); // doesn't call M()
lazy int y = x + x; // doesn't add x + x
int z = y * y; // now M() is called once and cached.
// x + x is computed and cached
// y * y is computed
Nous essayons de ne pas faire de petites fonctionnalités faibles s'il existe une fonctionnalité plus générale qui est une extension naturelle de celle-ci. Mais maintenant, nous parlons de coûts de conception et de mise en œuvre vraiment importants.
Cela vous serait-il utile ?
Personnellement ? Pas vraiment utile. J'écris beaucoup de code paresseux simple à faible verrouillage en utilisant principalement Interlocked.Exchange. (Je ne me soucie pas de savoir si la méthode paresseuse est exécutée deux fois et si l'un des résultats est jeté ; mes méthodes paresseuses ne sont jamais aussi coûteuses). Le modèle est simple, je sais qu'il est sûr, il n'y a jamais d'objets supplémentaires alloués pour le délégué ou les verrous, et si j'ai quelque chose d'un peu plus complexe, je peux toujours utiliser Lazy<T>
pour faire le travail à ma place. Ce serait une petite commodité.