DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»De quoi se méfier avec les itérateurs et les collections en C#
    Uncategorized

    De quoi se méfier avec les itérateurs et les collections en C#

    février 23, 2023
    De quoi se méfier avec les itérateurs et les collections en C#
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Cet article n’a pas pour but d’essayer de vous persuader, lecteur, que l’utilisation d’un itérateur ou d’une collection matérialisée résoudra universellement vos problèmes. L’itérateur et l’utilisation de la collection matérialisée peuvent être utilisés pour résoudre les scénarios que nous allons examiner, mais les deux viendront avec un ensemble différent d’avantages et d’inconvénients que nous pourrons explorer plus avant.

    Le but de cet article est de mettre en évidence des scénarios basés sur des expériences du monde réel où un itérateur ou une collection matérialisée était mal compris, mal utilisé et, finalement, entraînait une pile de maux de tête.

    En lisant cet article, si vous vous surprenez à dire « Bien sûr, mais ils auraient dû… », vous avez probablement raison. Le problème n’est fondamentalement pas l’utilisation d’un itérateur ou de la collection matérialisée, mais le fait de ne pas comprendre comment les consommer efficacement. Donc, j’espère que lorsque vous travaillez avec de nouveaux ingénieurs en logiciel ou avec des personnes moins familiarisées avec certains de ces concepts, vous pourrez vous rappeler de transmettre votre sagesse. Si vous êtes intéressé par la vidéo associée à cet article, consultez cette vidéo YouTube : « Dangers cachés des itérateurs et des collections en C# ».

    Itérateur commun et configuration du scénario de collecte

    Pour nous donner un terrain d’entente alors que nous explorons une approche avec une collection matérialisée contrairement à un itérateur, développons les exemples du monde réel où je vois ces défis se présenter régulièrement. Supposons que vous ayez une couche d’accès aux données dans votre application qui est responsable de l’obtention des enregistrements d’une base de données ou d’un magasin de données. Vous construisez une API que le reste de votre application peut utiliser, et vous utiliserez les résultats de cette API dans des situations telles que :

    • Exécution des méthodes LINQ (Any(), Count()ou même filtrer à l’aide Where()).
    • Affichage des ensembles de données résultants dans une interface utilisateur.
    • Utilisation des données résultantes pour le tri, le filtrage ou l’exécution d’algorithmes avec ces données comme source.

    Un autre ingrédient clé à mentionner ici est que, parce que cela est ancré dans le monde réel… les bases de code changent et évoluent avec le temps. Les gens proposent de nouveaux cas d’utilisation pour la couche d’accès aux données. Il y a plus de données ajoutées dans le magasin de données qui repoussent des limites que les gens n’auraient jamais acceptées. Vous avez des développeurs nouveaux ou plus juniors qui arrivent dans la base de code.

    C’est la vraie vie et jusqu’à ce que nous ayons plus de technologies automatisées pour contrôler ces choses, nous allons rencontrer des problèmes amusants.

    Matérialiser de grands ensembles de données

    Avant de nous concentrer sur les itérateurs, explorons l’approche la plus courante, qui implique des collections matérialisées. Compte tenu du scénario courant dont nous avons discuté ci-dessus, vous disposez d’une méthode qui pourrait ressembler à ce qui suit :

    public List<string> GetEntriesFromDatabase()
    {
        // incur some latency for connecting to the database
        var connection = _connectionFactory.OpenNew();
        var command = connection.CreateCommand();
    
        // TODO: actually create the query on the command, but this is just to illustrate
    
        var resultsReader = command.Execute();
    
        List<string> results = new List<string>();
        while (resultsReader.Read())
        {
            // TODO: pull the data off the reader... this example just uses a single field
            var value = resultsReader.GetValue(0);
            results.Add(value);
        }
    
        return results;
    }

    Il n’y a rien de manifestement faux dans cet exemple et, en fait, en laissant la question à votre imagination, j’ai omis d’où peuvent provenir une grande partie des problèmes. Utilisons un exemple de mon référentiel GitHub pour simuler à quoi cela pourrait ressembler afin d’avoir un point de référence :

    List<string> PretendGetEntriesFromDatabase()
    {
        // let's simulate some exaggerated latency to the DB
        Thread.Sleep(5000);
        Console.WriteLine($"{DateTime.Now} - <DB now sending back results>");
    
        // now let's assume we run some query that pulls back 100,000 strings from
        // the database
        List<string> results = new List<string>();
        while (results.Count < 100_000)
        {
            // simulate a tiny bit of latency on the "reader" that would be
            // reading data back from the database... every so often we'll
            // sleep a little bit just to slow it down
            if ((results.Count % 100) == 0)
            {
                Thread.Sleep(1);
            }
    
            results.Add(Guid.NewGuid().ToString());
        }
    
        return results;
    }

    Note: les délais dans l’exemple de code ci-dessus sont artificiellement gonflés de sorte que si vous l’exécutez dans une console, vous pouvez observer les différents effets de la modification des variables.

    Maintenant que nous avons l’extrait de code qui simule l’extraction de la base de données en créant d’abord une collection complète, examinons un code d’appel qui peut l’exercer (également sur GitHub) :

    long memoryBefore = GC.GetTotalMemory(true);
    Console.WriteLine($"{DateTime.Now} - Getting data from the database using List...");
    List<string> databaseResultsList = PretendThisGoesToADatabaseAsList();
    Console.WriteLine($"{DateTime.Now} - Got data from the database using List.");
    
    Console.WriteLine($"{DateTime.Now} - Has Data: {databaseResultsList.Any()}");
    Console.WriteLine($"{DateTime.Now} - Count of Data: {databaseResultsList.Count}");
    
    long memoryAfter = GC.GetTotalMemory(true);
    Console.WriteLine($"{DateTime.Now} - Memory Increase (bytes): {memoryAfter - memoryBefore}");

    Le code appelant prendra un instantané de la mémoire avant d’appeler notre méthode et d’effectuer des opérations sur le résultat. Les deux choses que nous allons faire avec le résultat sont :

    1. Appel de la méthode LINQ Any().
    2. Appel Count directement sur la liste.

    En remarque, le Count() La méthode LINQ ne nécessitera pas d’énumération complète car elle a une optimisation pour vérifier s’il existe une longueur connue.

    Examen des résultats de la collection matérialisée

    Avec l’exemple de collection matérialisée, nous pouvons appeler la méthode et stocker le jeu de résultats en mémoire. Étant donné les deux opérations que nous essayons d’utiliser sur la collection, Any() et Countces informations nous sont rapidement accessibles car nous avons payé le coup de performance une fois pour matérialiser les résultats dans une liste.

    Par rapport à un itérateur, cette approche ne risque pas de permettre aux appelants de réénumérer accidentellement entièrement les résultats. En effet, le jeu de résultats est matérialisé une seule fois. Cependant, l’implication ici est que, selon la taille des résultats et le coût de matérialisation complète de cet ensemble de résultats complet, vous pourriez payer un prix disproportionné pour des choses comme Any() qui ont seulement besoin de connaître l’existence d’un élément avant de retourner vrai.

    Si vous vous souvenez de ce que j’ai dit au début de cet article : si votre esprit passe automatiquement à « Eh bien, quelqu’un devrait créer une requête dédiée à cela », alors… oui, c’est absolument une solution. Mais ce que j’entends vous dire, c’est qu’il est très courant que quelque chose comme ça passe entre les mailles du filet d’une révision de code à cause de la syntaxe LINQ dont nous disposons. Surtout si quelqu’un redresse quelque chose comme :

    CallTheMethodThatActuallyMaterializesToAList().Any()

    Dans cet exemple, si le nom de la méthode n’était pas si évident, vous n’auriez aucun problème avec un itérateur, mais un énorme problème avec une matérialisation de liste lourde.

    Pourquoi est-ce si lourd ? Eh bien, on pourrait dire qu’il fait exactement ce pour quoi il a été codé, mais nous devons considérer comment les appelants vont en profiter.

    Si les appelants ont rarement besoin de traiter l’ensemble complet de données et qu’ils doivent faire des choses comme Any(), First() ou autrement des opérations plus légères qui n’ont pas nécessairement besoin de l’ensemble de résultats complet… Ils n’ont pas le choix avec cette API. Ils paieront le prix fort pour matérialiser l’ensemble des résultats alors qu’en réalité, ils n’avaient peut-être qu’à parcourir plusieurs éléments.

    Dans l’exemple de code ci-dessus, cela se traduit par l’allocation de plusieurs mégaoctets de données de chaîne lorsque nous avons besoin d’un nombre de données et pour vérifier s’il y avait des données. Oui, cela semble artificiel, mais c’est simplement pour illustrer que cette conception d’API ne se prête pas bien à des cas d’utilisation particuliers pour les appelants.

    Regardons les itérateurs

    Allons de l’avant et comparons l’exemple précédent avec une approche itérative. Nous allons commencer par le code, que vous pouvez trouver sur GitHub :

    IEnumerable<string> PretendThisGoesToADatabaseAsIterator()
    {
        // let's simulate some exaggerated latency to the DB
        Thread.Sleep(5000);
        Console.WriteLine($"{DateTime.Now} - <DB now sending back results>");
    
        // now let's assume we run some query that pulls back 100,000 strings from
        // the database
        for (int i = 0; i < 100_000; i++)
        {
            // simulate a tiny bit of latency on the "reader" that would be
            // reading data back from the database... every so often we'll
            // sleep a little bit just to slow it down
            if ((i % 100) == 0)
            {
                Thread.Sleep(1);
            }
    
            yield return Guid.NewGuid().ToString();
        }
    }

    Comme vous pouvez le voir dans le code ci-dessus, nous avons un itérateur structuré pour être presque identique à l’exception de :

    • C’est un itérateur.
    • Le mot-clé rendement rendement est requis ici.
    • Le type de retour est IEnumerable<T> au lieu de List<T>.

    Pour récapituler rapidement, un itérateur ne sera pas en mesure de fournir un décompte à un appelant comme nous pourrions le faire avec d’autres types de collection, et tout ce qu’il peut faire est de permettre à un appelant de parcourir élément par élément.

    Nous pouvons utiliser un extrait de code d’appel similaire sur notre itérateur, mais allons-y et ajoutons quelques lignes d’écriture de console supplémentaires (ici sur GitHub) :

    long memoryBefore = GC.GetTotalMemory(true);
    Console.WriteLine($"{DateTime.Now} - Getting data from the database using iterator...");
    IEnumerable<string> databaseResultsIterator = PretendThisGoesToADatabaseAsIterator();
    Console.WriteLine($"{DateTime.Now} - \"Got data\" (not actually... it's lazy evaluated) from the database using iterator.");
    
    Console.WriteLine($"{DateTime.Now} - Has Data: {databaseResultsIterator.Any()}");
    Console.WriteLine($"{DateTime.Now} - Finished checking if database has data using...
    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.