【C#】让自定义类变为可迭代

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aqtata/article/details/82263115

假设有两个类,一个学生类Student,它用来存储一个学生的信息,如名字和年龄。第二个是学校类School,它是学生类的集合。接下来我们一点点的看可迭代对象是如何进化的

一、石器时代的写法

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    internal class Student
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    internal class School
    {
        private readonly List<Student> _list = new List<Student>();

        public int Count => _list.Count();

        public Student this[int index] => _list[index];

        public void Add(Student item)
        {
            _list.Add(item);
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var school = new School();

            // 添加3个学生
            school.Add(new Student("ahri", 15));
            school.Add(new Student("ashe", 20));
            school.Add(new Student("annie", 25));

            // 遍历学校输出所有学生信息
            for (var i = 0; i < school.Count; i++)
            {
                var item = school[i];
                Console.WriteLine($"{item.Name}\t{item.Age}");
            }

            Console.ReadLine();
        }
    }
}

通过for循环遍历是在任何编程语言的入门书籍中都会提到的方法,因为它最基础。

二、使用IEnumerable

现代编程语言中基本上都会提供类似foreach这样的关键字,它可以说是for的升级版本,它不需要你提供集合对象的元素数量就可以遍历。而第一种遍历方法的缺点是无法使用foreach关键字

看到提示,一个集合类想通过foreach来遍历就得提供GetEnumerator方法,而这个方法正是IEnumerable接口唯一的一个方法,我们只需要继承这个接口

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    internal class Student
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    internal class School : IEnumerable
    {
        private readonly List<Student> _list = new List<Student>();

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new StudentEnumerator(_list);
        }

        public void Add(Student item)
        {
            _list.Add(item);
        }
    }

    internal class StudentEnumerator : IEnumerator
    {
        private readonly List<Student> _list;
        private int _index = -1;

        public StudentEnumerator(List<Student> items)
        {
            _list = items;
        }

        public bool MoveNext()
        {
            var count = _list.Count();
            if (_index < count)
            {
                _index++;
            }
            return _index < _list.Count();
        }

        public void Reset()
        {
            _index = -1;
        }

        public object Current => _list[_index];
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };

            foreach (var item in school)
            {
                var student = (Student)item;
                Console.WriteLine($"{student.Name}\t{student.Age}");
            }
            Console.ReadLine();
        }
    }
}

怎么看上去代码越来越多了呢?是的,为了能用上foreach,结果还得多写一个StudentEnumerator类,它继承自IEnumerator,必须实现2个方法和1个属性,在foreach遍历时会调用MoveNext来得知遍历是否要结束了(内部通过索引来判断是否到列表末尾了),没遍历完的话则通过Current属性来获取元素。可以看出,遍历的核心实质上都是在StudentEnumerator这个类里面,反而School显得多余了,C#支持继承多接口,所以对于这个例子我们是可以将它们合并的

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    internal class Student
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    internal class School : IEnumerable, IEnumerator
    {
        private readonly List<Student> _list = new List<Student>();
        private int _index = -1;

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this;
        }

        public void Add(Student item)
        {
            _list.Add(item);
        }

        public bool MoveNext()
        {
            var count = _list.Count();
            if (_index < count)
            {
                _index++;
            }
            return _index < _list.Count();
        }

        public void Reset()
        {
            _index = -1;
        }

        public object Current => _list[_index];
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };

            foreach (var item in school)
            {
                var student = (Student)item;
                Console.WriteLine($"{student.Name}\t{student.Age}");
            }
            Console.ReadLine();
        }
    }
}

一般不推荐这样写,因为在GetEnumerator这里返回的是自身,意味着迭代器不是独立的,如果在foreach嵌套的情况下可能不是你想要的

foreach (var item in school)
{
    var student = (Student)item;
    Console.WriteLine($"Dep1:{student.Name}\t{student.Age}");
    foreach (var item2 in school)
    {
        var student2 = (Student)item2;
        Console.WriteLine($"Dep2:{student2.Name}\t{student2.Age}");
    }
}

因为共享的是一个迭代器,所以外面的foreach只执行了一次

三、引入yield,抛弃IEnumerator

为了循环时方便一点,似乎在集合类内部需要更多处理,有些得不偿失的感觉。还好,C#没让我们失望,它提供了一个更现代的武器:yield

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    internal class Student
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    internal class School : IEnumerable
    {
        private readonly List<Student> _list = new List<Student>();

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var item in _list)
            {
                yield return item;
            }
        }

        public void Add(Student item)
        {
            _list.Add(item);
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };

            foreach (var item in school)
            {
                var student = (Student)item;
                Console.WriteLine($"{student.Name}\t{student.Age}");
            }
            Console.ReadLine();
        }
    }
}

通过yield return来一个个返回元素,就可以完全抛弃了StudentEnumerator类了。但请明白,yield只是语法糖,实际上在程序编译时C#会自动实现一个对应的Enumerator类来完成迭代工作,只不过我们看不到而已。

最后,在foreach循环中每次都要做一次强制转换也让人受不了!还好,IEnumerable支持泛型

using System;
using System.Collections;
using System.Collections.Generic;

namespace ConsoleApp1
{
    internal class Student
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    internal class School : IEnumerable<Student>
    {
        private readonly List<Student> _list = new List<Student>();

        public void Add(Student item)
        {
            _list.Add(item);
        }

        public IEnumerator<Student> GetEnumerator()
        {
            foreach (var item in _list)
            {
                yield return item;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };

            foreach (var student in school)
            {
                Console.WriteLine($"{student.Name}\t{student.Age}");
            }

            Console.ReadLine();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/aqtata/article/details/82263115