recherche
Maisondéveloppement back-endTutoriel C#.NetPièges C# que vous ne connaissez peut-être pas, explication détaillée de l'exemple de code de l'interface IEnumerable

Les pièges C# que vous ne connaissez peut-être pas, explication détaillée de l'exemple de code de l'interface IEnumerable :

L'importance de l'interface de l'énumérateur IEnumerable, parlons-en. Les mots ne suffisent pas. Presque toutes les collections implémentent cette interface, et le cœur de Linq s'appuie également sur cette interface universelle. La boucle for en langage C est fastidieuse à écrire, mais foreach est beaucoup plus fluide.

J'aime beaucoup cette interface, mais je rencontre aussi beaucoup de questions lors de son utilisation. Avez-vous la même confusion que moi :

(1) Quelle est la différence entre IEnumerable et. IEnumerator ?

(2) Les énumérations peuvent-elles être accessibles hors limites ? Quelles sont les conséquences d'un accès hors limites ? Pourquoi la valeur d'une collection ne peut-elle pas être modifiée lors d'une énumération ?

(3) Quelle est l'implémentation spécifique de Linq ? Par exemple, Skip ignore certains éléments accessibles  ?

(4) Quelle est l’essence d’IEnumérable ?

(5) Une fermeture sera-t-elle formée dans l'énumération IEnumerable ? Plusieurs processus de dénombrement vont-ils interférer les uns avec les autres ? Puis-je modifier dynamiquement les éléments d’une énumération au sein d’une énumération ?

….

Si vous êtes intéressé, nous continuerons avec le contenu ci-dessous.

Avant de commencer, notre article stipule qu'une énumération est un IEnumerable, une itération est un IEnumerable et celle qui a été instanciée (comme ToList()) est une collection.

1. IEnumerable et IEnumerable

IEnumerable n'a qu'une seule méthode abstraite : GetEnumerator(), et IEnumerable est un itérateur, qui réalise véritablement la fonction d'accès à une collection. IEnumerator n'a qu'une seule propriété Current et deux méthodes : MoveNext et Reset.

Il y a un petit problème. Ne suffit-il pas de simplement créer une interface d'accès ? Pourquoi deux interfaces qui semblent déroutantes ? L’un est appelé énumérateur et l’autre est appelé itérateur. Parce que

(1) implémenter IEnumerator est un sale boulot, ajouter deux méthodes et un attribut en vain, et ces deux méthodes ne sont en fait pas faciles à implémenter (seront mentionnées plus tard).

(2) Il doit maintenir l'état initial, savoir comment MoveNext, comment terminer, et en même temps renvoie l'état précédent de l'itération , ce qui n'est pas facile.

(3) L'itération n'est évidemment pas thread-safe. Chaque IEnumerable générera un nouveau IEnumerator, formant ainsi plusieurs processus d'itération qui ne s'affectent pas les uns les autres. Pendant le processus d'itération, la collection d'itérations ne peut pas être modifiée, sinon elle est dangereuse.

Donc, tant que vous implémentez IEnumerable, le compilateur nous aidera à implémenter IEnumerable. De plus, dans la plupart des cas, elles sont héritées de collections existantes et il n'est généralement pas nécessaire de remplacer les méthodes MoveNext et Reset. Bien entendu, IEnumerable a également une implémentation générique, ce qui n'affecte pas la discussion sur le problème.

IEnumerable nous rappelle une liste chaînée à sens unique. En C, un champ de pointeur est nécessaire pour enregistrer les informations du nœud suivant. Donc dans IEnumerable, qui aide à enregistrer ces informations ? Ce processus prend-il de la mémoire ? Occupe-t-il la zone du programme ou la zone du tas ?

Cependant, IEnumerable a aussi ses défauts. Il ne peut pas revenir en arrière ou sauter (il ne peut sauter qu'un par un), et il n'est pas facile d'implémenter la réinitialisation et l'accès à l'index ne peut pas être obtenu. Pensez-y, s'il s'agit d'un processus d'énumération d'une collection d'instances, revenez simplement directement au 0ème élément, mais si ce IEnumerable est une longue chaîne d'accès, il sera très difficile de retrouver la racine d'origine ! Ainsi, l'auteur de CLR via C# vous dit qu'en fait, de nombreuses implémentations de Reset sont simplement des mensonges. Sachez simplement que cette chose existe et ne vous y fiez pas trop.

2. Y a-t-il une différence entre foreach et MoveNext

La plus grande caractéristique d'IEnumérable est qu'il place le processus d'accès sous le contrôle du visiteur lui-même. En langage C, le contrôle des tableaux est complètement externe. Cette interface encapsule le processus d'accès en interne, améliorant encore l'encapsulation. Par exemple :

public class People  //定义一个简单的实体类
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class PersonList
    {
        private readonly List<People> peoples;

        public PersonList()  //为了方便,构造过程中插入元素
        {
            peoples = new List<People>();
            for (int i = 0; i < 5; i++)
            {
                peoples.Add(new People {Name = "P" + i, Age = 30 + i});
            }
        }

        public int OldAge = 31;
        public IEnumerable<People> OlderPeoples
        {
            get
            {
                foreach (People people in _people)
                {
                    if (people.Age > OldAge)
                        yield return people;
                }
                yield break;
            }
        }
    }

L'essence de IEnumerable est une machine à états, qui est quelque peu similaire au concept d'événements. Elle jette l'implémentation à l'extérieur et réalise des voyages entre les codes (pensez interstellaire. voyage), c'est la base de Linq. Les itérateurs sympas sont-ils vraiment aussi simples qu’on le pense ?

En langage C, un tableau est un tableau, un véritable espace mémoire Alors que signifie IEnumerable ? S'il est implémenté par une vraie collection (comme List), alors pas de problème, c'est aussi de la vraie mémoire, mais que se passe-t-il si c'est l'exemple ci-dessus ? Le retour de rendement renvoyé par le filtrage ne renvoie que des éléments, mais cette collection réelle peut ne pas exister. Si vous décompilez le retour de rendement d'un simple énumérateur, vous constaterez qu'il s'agit en fait d'un ensemble de cas de commutation et que le compilateur travaille dans le cas. contexte. Il a fait beaucoup de travail pour nous.

Current est en fait vide si MoveNext n'est pas utilisé dans le nouvel itérateur généré. Pourquoi est-ce ? Pourquoi un itérateur ne pointe-t-il pas directement vers l'élément head ?

(Merci pour la réponse : tout comme le pointeur de tête d'une liste chaînée unidirectionnelle en langage C, vous pouvez ainsi spécifier une énumération qui ne contient aucun élément, ce qui rend la programmation plus pratique)

foreach à chaque fois Avancez d'un espace et arrêtez-vous lorsqu'il atteint la fin. Attends, tu es sûr que ça s'arrêtera quand ça se terminera ? Faisons une expérience :

public IEnumerable<People> Peoples1   //直接返回集合
        {
            get { return peoples; }
        }public IEnumerable<People> Peoples2  //包含yield break;
        {
            get
            {
                foreach (var people in peoples)
                {
                    yield return people;
                }
                yield break;  //其实这个用不用都可以
            }
        }

Les deux méthodes ci-dessus sont nos méthodes courantes. Notez que dans la deuxième implémentation, ReSharper marque la rupture de rendement en gris (répété).

我们再写下如下的测试代码,peopleList集合只有五个元素,但尝试去MoveNext 8次。可以把peopleList.Peoples1换成2,3,分别测试。

            var peopleList = new PeopleList();  //内部构造函数插入了五个元素
            IEnumerator<People> e1 = peopleList.Peoples1.GetEnumerator();
            if (e1.Current == null)
            {
                Console.WriteLine("迭代器生成后Current为空");
            }
            int i = 0;
            while (i<8)  //总共只有五个元素,看看一直迭代会发生什么效果
            {
                e1.MoveNext();
                if (e1.Current == null)
                {
                    Console.WriteLine("迭代第{0}次后为空",i);
                }
                else
                {
                    Console.WriteLine("迭代第{0}次后为{1}",i,e1.Current.Name);
                }
                i++;
            }
//PeopleEnumerable1   (直接返回集合)
迭代器生成后Current为空
迭代第0次后为P0
迭代第1次后为P1
迭代第2次后为P2
迭代第3次后为P3
迭代第4次后为P4
迭代第5次后为空
迭代第6次后为空
迭代第7次后为空

//PeopleEnumerable2 (不加yield break)
迭代器生成后Current为空
迭代第0次后为P0
迭代第1次后为P1
迭代第2次后为P2
迭代第3次后为P3
迭代第4次后为P4
迭代第5次后为P4
迭代第6次后为P4
迭代第7次后为P4

//PeopleEnumerable2 (加上yield break)
迭代器生成后Current为空
迭代第0次后为P0
迭代第1次后为P1
迭代第2次后为P2
迭代第3次后为P3
迭代第4次后为P4
迭代第5次后为P4
迭代第6次后为P4
迭代第7次后为P4

越界枚举测试结果

真让人吃惊,返回原始集合,越界之后就返回null了,但如果是MoveNext,不论有没有加yield break, 越界迭代后还是返回最后一个元素! 也许就是我们在第1节里提到的,迭代器只返回上一次的状态,因为无法后移,所以就重复返回,那为什么List集合就不会这样呢?问题留给大家。

(感谢回答:越界枚举到底是null还是最后一个元素的问题,其实没有明确规定,具体看.NET的实现,在.NET Framework中,越界后依然是最后一个元素)。

不过各位看官尽管放心,在foreach的标准枚举过程下,枚举是肯定能枚举完的,这就说明了MoveNext和foreach两种在实现上的不同,显然foreach更安全。同时还注意,不能在yield过程中实现try-catch代码块,为什么呢?因为yield模式组合了来自不同位置的代码和逻辑,怎么可能靠编译给每个引用的代码块加上try-catch?这太复杂了。

枚举的特性在处理大数据的时候很有帮助,就是因为它的状态性,一个超大的文件,我只要每次读一部分,就可以顺次的读取下去,直到文件结束,由于不需要实例化集合,内存占用是很低的。对数据库也是如此,每次读取一部分,就能应对很多难以应付的情况。

3.在枚举中修改枚举器参数?

在枚举过程中,集合是不能被修改的,比如在foreach循环中,如果插入或者删除一个元素,肯定会报运行时异常。有经验的程序员告诉 你,此时用for循环。for和foreach的本质区别是什么呢?

在MoveNext中,我突然改变了枚举的参数,使得它的数据量变多或者变少了,又会发生什么?

           Console.WriteLine("不修改OldAge参数");
            foreach (var olderPeople in peopleList.OlderPeoples)
            {
                Console.WriteLine(olderPeople);

            }

            Console.WriteLine("修改了OldAge参数");
            i = 0;
            foreach (var olderPeople in peopleList.OlderPeoples)
            {
                Console.WriteLine(olderPeople);
                i++;
                if (i ==1)
                    peopleList.OldAge = 33;  //只枚举一次后,修改OldAge 的值
            }

测试结果是:

不修改OldAge参数
ID:2,NameP2,Age32
ID:3,NameP3,Age33
ID:4,NameP4,Age34

修改了OldAge参数
ID:2,NameP2,Age32
ID:4,NameP4,Age34

可以看到,在枚举过程中修改了控制枚举的值,能动态改变枚举的行为。上面是在一个yield结构中改变变量的情况,我们再试试在迭代器和Lambda表达式的情况(代码略), 得到结果是:

在迭代中修改变量值
ID:2,NameP2,Age32
ID:4,NameP4,Age34
在Lambda表达式中修改变量值
ID:2,NameP2,Age32
ID:4,NameP4,Age34

可以看出,外部修改变量能够控制内部的迭代过程,动态改变了“集合的元素”。 这是一个好事,因为它的行为确实是对的;也是坏事:在迭代过程中,修改了变量的值,上下文语境变化,可是如果还按之前的语境进行处理,显然就会酿成大错。 这里和闭包没关系。

因此,如果一个枚举需要在上下文会发生变化的情况下保持原有的行为,就需要手动保存变量的副本。

如果你把两个集合A,B用Concat函数顺次拼接起来,也就是A-B, 而且不实例化,那么在枚举A的阶段中,修改集合B的元素,会报错么? 为什么?

比如如下的测试代码:

       List<People> peoples=new List<People>(){new People(){Name = "PA"}};
            Console.WriteLine("将一个虚拟枚举A连接到集合B,并在枚举A阶段修改集合B的元素");
            var e8 = peopleList.PeopleEnumerable1.Concat(peoples);
            i = 0;
            foreach (var people in e8)
            {
                Console.WriteLine(people);
                i++;
                if (i == 1)   
                  peoples.Add(new People(){Name = "PB"});  //此时还在枚举PeopleEnumerable1阶段
        }

如果你想知道,可以自己做个试验(在我附件里也有这个例子)。留给大家讨论。

4. 更多LINQ的讨论

你可以在yield中插入任何代码,这就是延迟(Lazy)的表现,只是需要执行的时候才执行。 我们不难想象Linq很多函数的实现方式,比较有意思的包括Concat,它将两个集合连在了一起,就像下面这样:

public static IEnumerable<T> Concat<T>(this IEnumerable<T> source, IEnumerable<T> source2)
       {
           foreach (var r in source)
           {
               yield return r;
           }
           foreach (var r in source2)
           {
               yield return r;
           }
       }

还有Select, Where都好实现,就不讨论了。

Skip怎么实现的呢?  它跳过了集合中的一部分元素,我猜是这样的:

public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count)
       {
           int t = 0;
           foreach (var r in source)
           {
               t++;
               if(t<=count)
                   continue;
               yield return r;
           }
       }

那么,被跳过的元素,到底被访问过没有?它的代码被执行了么?

 Console.WriteLine("Skip的元素是否会被访问到?");
 IEnumerable<People> e6 = peopleList.PeopleEnumerable1.Select(d =>
       {
              Console.WriteLine(d);
              return d;
       }).Skip(3);
 Console.WriteLine("只枚举,什么都不做:");
 foreach (var  r in e6){}  
 Console.WriteLine("转换为实体集合,再次枚举");
 IEnumerable<People> e7 = e6.ToList();
 foreach (var r in e7){}

测试结果如下:

只枚举,什么都不做:
ID:0,NameP0,Age30
ID:1,NameP1,Age31
ID:2,NameP2,Age32
ID:3,NameP3,Age33
ID:4,NameP4,Age34
转换为实体集合,再次枚举
ID:0,NameP0,Age30
ID:1,NameP1,Age31
ID:2,NameP2,Age32
ID:3,NameP3,Age33
ID:4,NameP4,Age34

可以看出,Skip虽然是跳过,但还是会“访问”元素的,因此会执行额外的操作,比如lambda表达式,这不论是枚举器还是实体集合都是如此。这个角度说,要优化表达式,应当尽可能在linq中早的Skip和Take,以减少额外的副作用。

但对于Linq to SQL的实现中,显然Skip是做过额外优化的。我们是否也能优化Skip的实现,使得上层尽可能提升海量数据下的Skip性能呢?

5. 有关IEnumerable枚举的更多问题

(1) 枚举过程如何暂停?有暂停这一说么? 如何取消?

(2) PLinq的实现原理是什么?它改变的到底是IEnumerable接口的哪种特性?是否产生了乱序枚举?这种乱序枚举到底是怎么实现?

(3) IEnumerable实现了链条结构,这是Linq的基础,但这个链条的本质是什么?

(4) Parce que IEnumerable représente l'état et le délai, il n'est pas difficile de comprendre que l'essence de nombreuses opérations asynchrones est IEnumerable. Au cours d'un de mes entretiens, on m'a posé des questions sur l'essence de l'asynchronisme, selon vous, quelle est l'essence de l'asynchronisme ? Async n'est pas du multithread ! La beauté de l’asynchrone réside essentiellement dans la réorganisation du code, car les opérations asynchrones à long terme sont des machines à états. . . Par exemple, la bibliothèque CCR. Je ne vais pas m’étendre là-dessus ici car cela dépasse temporairement la réserve de connaissances de l’auteur. J’en parlerai la prochaine fois.

(5) Si le même énumérateur est implémenté en langage C, le même Linq cool peut-il être implémenté sans recourir à un compilateur ? Ne parlons pas de l'astuce Lambda, utilisons des pointeurs de fonction.

(6) IEnumerable est écrit en MapReduce ? Linq pour MapReduce ?

(7) Comment trier IEnumerable Peut-il être instancié dans un ensemble puis trié ? S’il s’agit d’une très grande collection virtuelle, comment l’optimiser ?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
C # .NET et l'avenir: s'adapter aux nouvelles technologiesC # .NET et l'avenir: s'adapter aux nouvelles technologiesApr 14, 2025 am 12:06 AM

C # et .NET s'adaptent aux besoins des technologies émergentes à travers des mises à jour et des optimisations continues. 1) C # 9.0 et .NET5 introduire le type d'enregistrement et l'optimisation des performances. 2) .netcore améliore le support natif et conteneurisé cloud. 3) ASP.Netcore s'intègre aux technologies Web modernes. 4) ML.NET prend en charge l'apprentissage automatique et l'intelligence artificielle. 5) La programmation asynchrone et les meilleures pratiques améliorent les performances.

C # .NET vous convient-il? Évaluer son applicabilitéC # .NET vous convient-il? Évaluer son applicabilitéApr 13, 2025 am 12:03 AM

C # .NeTissuitableFormenterprise-LevelApplications withithemicrosofosystématetoitsstrongThpyping, RichLibrary, androbustperformance.wowever, itmayNotBeidealForcross-PlatformDevelopmentorwhenRawpeediscritical, whileLanguageSlikerUstorGomightBeferable.

C # code dans .NET: Explorer le processus de programmationC # code dans .NET: Explorer le processus de programmationApr 12, 2025 am 12:02 AM

Le processus de programmation de C # dans .NET comprend les étapes suivantes: 1) l'écriture de code C #, 2) la compilation dans un langage intermédiaire (IL) et 3) l'exécution par .NET Runtime (CLR). Les avantages de C # dans .NET sont sa syntaxe moderne, son système de type puissant et son intégration serrée avec le Framework .NET, adapté à divers scénarios de développement des applications de bureau aux services Web.

C # .NET: Explorer les concepts de base et les principes fondamentaux de la programmationC # .NET: Explorer les concepts de base et les principes fondamentaux de la programmationApr 10, 2025 am 09:32 AM

C # est un langage de programmation moderne et orienté objet développé par Microsoft et dans le cadre du .NET Framework. 1.C # prend en charge la programmation orientée objet (POO), y compris l'encapsulation, l'héritage et le polymorphisme. 2. La programmation asynchrone en C # est implémentée via Async et attend des mots clés pour améliorer la réactivité des applications. 3. Utilisez LINQ pour traiter les collections de données concisement. 4. Les erreurs courantes incluent les exceptions de référence NULL et les exceptions indexes hors gamme. Les compétences de débogage comprennent l'utilisation d'un débogueur et une gestion des exceptions. 5. L'optimisation des performances comprend l'utilisation de StringBuilder et d'éviter l'emballage et le déballage inutiles.

Test C # .NET Applications: unité, intégration et tests de bout en boutTest C # .NET Applications: unité, intégration et tests de bout en boutApr 09, 2025 am 12:04 AM

Les stratégies de test pour les applications C # .NET comprennent les tests unitaires, les tests d'intégration et les tests de bout en bout. 1. Le test unitaire garantit que l'unité minimale du code fonctionne indépendamment, en utilisant le cadre MSTEST, NUnit ou Xunit. 2. Les tests intégrés vérifient les fonctions de plusieurs unités combinées et des données simulées couramment utilisées et des services externes. 3. Les tests de bout en bout simulent le processus de fonctionnement complet de l'utilisateur et le sélénium est généralement utilisé pour les tests automatisés.

Tutoriel avancé C # .NET: Ace votre prochain entretien de développeur seniorTutoriel avancé C # .NET: Ace votre prochain entretien de développeur seniorApr 08, 2025 am 12:06 AM

L'entrevue avec C # Developer Senior Developer nécessite de maîtriser les connaissances de base telles que la programmation asynchrone, la LINQ et les principes de travail internes des frameworks .NET. 1. La programmation asynchrone simplifie les opérations par asynchronisation et attend pour améliorer la réactivité de l'application. 2.Linq exploite des données dans le style SQL et fait attention aux performances. 3. La CLR du cadre net gère la mémoire et la collecte des ordures doit être utilisée avec prudence.

C # .NET des questions et réponses d'entrevue: améliorez votre expertiseC # .NET des questions et réponses d'entrevue: améliorez votre expertiseApr 07, 2025 am 12:01 AM

C # .NET Les questions et réponses d'entrevue comprennent les connaissances de base, les concepts de base et l'utilisation avancée. 1) Connaissances de base: C # est un langage orienté objet développé par Microsoft et est principalement utilisé dans le framework .NET. 2) Concepts de base: la délégation et les événements permettent des méthodes de liaison dynamiques, et LINQ fournit des fonctions de requête puissantes. 3) Utilisation avancée: la programmation asynchrone améliore la réactivité et les arbres d'expression sont utilisés pour la construction de code dynamique.

Construire des microservices avec C # .net: un guide pratique pour les architectesConstruire des microservices avec C # .net: un guide pratique pour les architectesApr 06, 2025 am 12:08 AM

C # .NET est un choix populaire pour construire des microservices en raison de son fort écosystème et de son riche soutien. 1) Créez RestulAPI à l'aide d'Asp.Netcore pour traiter la création et la requête de l'ordre. 2) Utilisez GRPC pour obtenir une communication efficace entre les microservices, définir et mettre en œuvre les services de commande. 3) Simplifiez le déploiement et la gestion via des microservices conteneurisés Docker.

See all articles

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
3 Il y a quelques semainesBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
3 Il y a quelques semainesBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Comment réparer l'audio si vous n'entendez personne
3 Il y a quelques semainesBy尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Comment déverrouiller tout dans Myrise
4 Il y a quelques semainesBy尊渡假赌尊渡假赌尊渡假赌

Outils chauds

SublimeText3 Linux nouvelle version

SublimeText3 Linux nouvelle version

Dernière version de SublimeText3 Linux

Dreamweaver Mac

Dreamweaver Mac

Outils de développement Web visuel

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

mPDF

mPDF

mPDF est une bibliothèque PHP qui peut générer des fichiers PDF à partir de HTML encodé en UTF-8. L'auteur original, Ian Back, a écrit mPDF pour générer des fichiers PDF « à la volée » depuis son site Web et gérer différentes langues. Il est plus lent et produit des fichiers plus volumineux lors de l'utilisation de polices Unicode que les scripts originaux comme HTML2FPDF, mais prend en charge les styles CSS, etc. et présente de nombreuses améliorations. Prend en charge presque toutes les langues, y compris RTL (arabe et hébreu) ​​et CJK (chinois, japonais et coréen). Prend en charge les éléments imbriqués au niveau du bloc (tels que P, DIV),

VSCode Windows 64 bits Télécharger

VSCode Windows 64 bits Télécharger

Un éditeur IDE gratuit et puissant lancé par Microsoft