38 votes

Problèmes liés à l'ajout d'un mot-clé `lazy` en C#

J'adorerais écrire un tel code :

class Zebra
{
    public lazy int StripeCount
    {
        get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); }
    }
}

EDIT : Pourquoi ? Je pense que c'est mieux que :

class Zebra
{
    private Lazy<int> _StripeCount;

    public Zebra()
    {
        this._StripeCount = new Lazy(() => ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce());
    }

    public lazy int StripeCount
    {
        get { return this._StripeCount.Value; }
    }
}

La première fois que vous appelez la propriété, elle exécute le code dans le champ get et, ensuite, renvoie simplement la valeur de ce bloc.

Mes questions :

  1. Quels seraient les coûts liés à l'ajout de ce type de mot-clé à la bibliothèque ?
  2. Dans quelles situations cela pourrait-il être problématique ?
  3. Cela vous serait-il utile ?

Je ne suis pas en train de lancer une croisade pour que cette fonctionnalité soit intégrée à la prochaine version de la bibliothèque, mais je suis curieux de savoir à quel genre de considérations une telle fonctionnalité devrait être soumise.

56voto

Eric Lippert Points 300275

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é.

5voto

Matt Greer Points 29401

La bibliothèque du système possède déjà une classe qui fait ce que vous voulez : System.Lazy<T>

Je suis sûr qu'il pourrait être intégré dans la langue, mais en tant que Eric Lippert vous diront que l'ajout de fonctionnalités à un langage n'est pas une chose à prendre à la légère. De nombreux éléments doivent être pris en compte, et le rapport bénéfice/coût doit être très bon. Depuis System.Lazy gère déjà cela assez bien, je doute que nous voyions cela de sitôt.

3voto

Brian Genisio Points 30777

Connaissez-vous le Lazy<T> qui a été ajoutée dans .Net 4.0 ?

http://sankarsan.wordpress.com/2009/10/04/laziness-in-c-4-0-lazyt/

3voto

Stephen Jennings Points 3429

Il est peu probable que cela soit ajouté au langage C# car vous pouvez facilement le faire vous-même, même sans Lazy<T> .

Un simple, mais pas de sécurité thread par exemple :

class Zebra
{
    private int? stripeCount;

    public int StripeCount
    {
        get
        {
            if (this.stripeCount == null)
            {
                this.stripeCount = ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce();
            }
            return this.stripeCount;
        }
    }
}

3voto

Mg. Points 667

Avez-vous essayé / Dou vous voulez dire cela ?

private Lazy<int> MyExpensiveCountingValue = new Lazy<int>(new Func<int>(()=> ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce()));
        public int StripeCount
        {
            get
            {
                return MyExpensiveCountingValue.Value;
            }
        }

EDITAR:

après l'édition de votre post, j'ajouterais que votre idée est définitivement plus élégante, mais a toujours la même fonctionnalité !!!.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X