黑马程序员:C#基础篇(五)迭代与泛型

本文介绍了C#中的迭代器和泛型。迭代器允许在类或结构中支持foreach循环,无需实现完整的'IEnumerable'接口,而泛型则提供了类型参数化的集合操作,增强了代码的复用性和安全性。文章详细讲解了迭代器的工作原理,包括IEnumerator、IEnumerable接口,以及如何自定义迭代器。同时,探讨了泛型类和泛型方法的应用,以及在设计泛型时需要考虑的问题。

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

迭代

迭代器是方法、get访问器或运算符;使得在类或结构中能够支持foreach循环,而不必实现整个“IEnumerable接口,只要提供一个迭代器即可。当编译器检测到迭代器时,会自动生成IEnumerable或IEnumerator接口的Current属性、MoveNext和Reset方法。

说到迭代器,不得不说四个接口,IEnumerator、IEnumerable、IEnumerator<T>、IEnumerable<T>

IEnumerator支持在集合上进行简单的迭代,通过元数据查看到的定义如下:

        public interface IEnumerator
        {
            // 摘要:
            //     获取集合中的当前元素。
            //
            // 返回结果:
            //     集合中的当前元素。
            object Current { get; }

            // 摘要:
            //     将枚举数推进到集合的下一个元素。
            //
            // 返回结果:
            //     如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
            //
            // 异常:
            //   System.InvalidOperationException:
            //     在创建了枚举数后集合被修改了。
            bool MoveNext();
            //
            // 摘要:
            //     将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
            //
            // 异常:
            //   System.InvalidOperationException:
            //     在创建了枚举数后集合被修改了。
            void Reset();
        }
IEnumerable是一个公开枚举数,如下:
    public interface IEnumerable
    {
        // 摘要:
        //     返回一个循环访问集合的枚举数。
        //
        // 返回结果:
        //     一个可用于循环访问集合的 System.Collections.IEnumerator 对象。
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }

后两个其实就是前两个加上一般参数,用于泛型集合上的迭代。

 

foreach通常只能作用于包含了GetEnumerator方法的公共定义的类型的变量(就是IEnumerable接口、IEnumerable<T>接口中定义的那个方法)想用foreach就要有迭代器,可以继承自接口也可以在类中直接定义迭代器(返回一个IEnumerable或者IEnumerable<T>类型的话方法名无约束,但是想返回IEnumerator或IEnumerator<T>的话那方法名就必须是GetEnumerator),例子:

继承了接口(注意,显式继承的话在foreach中就必须用接口存MyClass然后调用):

    class MyClass:IEnumerable
    {
        public virtual IEnumerator GetEnumerator()
        {
            //具体的实现,需要用yield语句返回一个枚举数
            for (int i = 0; i < int.MaxValue; i++)
            {
                yield return i;
            }
        }
    }

不继承接口,自定义迭代器:

    class MyClass
    {
        static MyClass mc = new MyClass();
        public virtual IEnumerator GetEnumerator()
        {
            //具体的实现,需要用yield语句返回一个枚举数
            for (int i = 0; i < int.MaxValue; i++)
            {
                yield return i;
            }
        }

        static IEnumerable Func()
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                yield return i;
            }
        }
        static void Main(string[] args)
        {
            foreach (var item in Func())
            {
                Console.Write(item+" ");
            }
            foreach (var item in mc)
            {
                Console.Write(item + " ");
            }
            Console.ReadKey();
        }
}

例子中2个自定义迭代器,注意看他们在foreach中怎么调用的。说明一个问题,foreach最后总是会自动调用变量的GetEnumerator方法。把第一个迭代器GetEnumerator改下名字,会发现下面foreach语句报错了,因为反射mc时发现没有GetEnumerator方法了当然编译器就会报错。

 

还可以在一个迭代器中使用多个yield语句,例子:

        static IEnumerable Func()
        {
            yield return "起来,";
            yield return "不愿做驴的人们,";
            yield return "把我们的肾拿去换钱。";
            yield break;//发出迭代结束信号,这里也可以不加
        }
        static void Main(string[] args)
        {
            foreach (var item in Func())
            {
                Console.Write(item);
            }
            Console.ReadKey();
        }
使用yield 语句还要注意几点:不允许不安全快;参数不能是ref 和out;当yield return 后面是表达式时,不能出现在catch块中或含有一个或多个catch子句的try块中;不能在匿名方法中使用。

foreach会自动调用变量的GetEnumerator方法(不能在foreach中显式的调用该方法)并使用返回的枚举数来循环访问值。在foreach循环的每次后续迭代(其实就是对IEnumerator.MoveNext的直接调用)中迭代器代码体将从前一个yield语句之后开始,直到迭代器体的结尾或遇到yield break语句。
迭代器可以用于遍历一些数据结构,例如二叉树或其他包含相互连接节点的数据结构,这时候迭代的优势就很明显了,递归迭代的例子:

        //节点类,T是节点保存的数据
        class Node<T> where T:System.IComparable<T>
        {
            public Node<T> BaseNode;//父节点
            public Node<T> LeftNode;//左节点
            public Node<T> RightNode;//右节点
            public T data;//数据
        }
        //二叉树类
        class BinaryTree<T> where T:System.IComparable<T>
        {
            Node<T> RootNode;//根节点
            //创建二叉树,Add有3个重载
            public void Add(params T[] items)
            {
                foreach (T item in items)
                {
                    Add(item);
                }
            }
            public void Add(T data)
            {
                //比较常见的是判断data大小来添加,用到了T参数约束实现比较
                if (RootNode == null)
                {
                    RootNode = new Node<T>();
                    RootNode.data = data;
                    RootNode.BaseNode = null;
                }
                else
                {
                    Add(RootNode, data);    
                }
            }
            //判断大小来添加
            private void Add(Node<T> node, T data)
            {
                if (node.data.CompareTo(data)<0)
                {
                    if (node.LeftNode == null)
                    {
                        Node<T> temp = new Node<T>();
                        temp.data = data;
                        temp.BaseNode = node;
                        node.LeftNode = temp;
                    }
                    else
                    {
                        Add(node.LeftNode, data);
                    }
                }
                else
                {
                    if (node.RightNode == null)
                    {
                        Node<T> temp = new Node<T>();
                        temp.data = data;
                        temp.BaseNode = node;
                        node.RightNode = temp;
                    }
                    else
                    {
                        Add(node.RightNode, data);
                    }
                }
            }
            //只读属性迭代器,里面调用一个遍历二叉树的私有迭代器,返回数据
            public IEnumerable<T> IndexData
            {
                get { return GetData(RootNode); }
            }
            IEnumerable<T> GetData(Node<T> root)
            {
                if (root.LeftNode != null)//迭代返回左节点数据
                {
                    foreach (T data in GetData(root.LeftNode))
                    {
                        yield return data;
                    }
                }
                yield return RootNode.data;//返回根节点数据
                if (root.RightNode != null)//返回右节点数据
                {
                    foreach (T data in GetData(root.RightNode))
                    {
                        yield return data;
                    }
                }
            }


 

泛型

泛型引入了类型参数的概念,通过使用类型参数,可以不必要进行强制转换或装箱操作,泛型类和泛型方法同时具备了可重用性、类型安全和效率。创建一个自定义泛型类如下:
     //T是类型参数
     public class GenericList<T>
     {
         //嵌套类,表示节点类
        class Node
         {
             Node next;//表示下一个节点
            T data;//表示当前节点存储的数据(T类型)
            public Node Next
             {
                 get { return next; }
                 set { next = value; }
             }
             public T Data
             {
                 get { return data; }
                 set { data = value; }
             }
             //构造函数
            public Node(T t)
             {
                 next = null;
                 data = t;
             }
         }

         Node head;//GenericList<T>类的字段,表示头节点

         public GenericList()
         {
             head = null;
         }
         //为GenericList添加节点的方法,t是数据
         public void AddHead(T t)
         {
            Node n = new Node(t);//首先把数据t保存在节点类型中
            n.Next = head;//当前存了数据的节点的下一个节点的值变为当前list对象的head
             head = n;//把n存为头节点
            //即向前添加的,把一个节点插入到list中的头部,后面的数据依次往后排
        }
        public IEnumerator<T> GetEnumerator()//IEnumerator<T>为泛型集合提供迭代功能的接口
        {
            Node current = head;//当前节点
            while (current!=null)//当前节点不为空,则迭代返回其数据,并且跳到下一个节点
            {
                 yield return current.Data;
                 current = current.Next;
             }
         }
     }
     public class Program
     {
         static void Main(string[] args)
         {
             GenericList<int> list = new GenericList<int>();
             for (int i = 0; i < 10; i++)
             {
                 list.AddHead(i);
             }
             foreach (int data in list)//可以用foreach是因为GenericList实现了IEnumerator<T>
             {
                 Console.Write(data+"  ");
             }
             Console.ReadKey();
         }
     }

当然类型参数可以不止一个,比较Dictionary<T,S>泛型字典就是具备2个类型参数的泛型类。

泛型类其实就是封装了不是特定与具体数据类型的操作,常用于集合、链接列表、哈希表、堆栈、队列和树等,其中从集合中添加和移除项的操作大体相同,与其存储的数据类型无关。使用泛型还需要注意一下问题:

(1)将哪些类型通用化为类型参数

(2)是否将泛型行为分解为基类和子类(泛型类可以作为基类使用)

(3)是否实现了一个或多个泛型接口

泛型类可以从具体且封闭式构造或开放式构造基类继承:

    class Base { }
    class BaseGeneric<T> { }

    class Sub1<T> : Base { }
    class Sub2<T> : BaseGeneric<int> { }
    class Sub3<T> : BaseGeneric<T> { }

说了泛型类,当然也有泛型方法:

        void Func<T>(ref T p1, ref T p2)where T :IComparable<T>
        {
            T temp;
            if (p1.CompareTo(p2) > 0)
            {
                temp = p1;
                p1 = p2;
                p2 = temp;
            }
        }

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

大文件拷贝实例源码,学习关于IO文件流、多线程相关使用做参考。 private void button4_Click(object sender, EventArgs e) { this.progressBar1.Value = 0; this.label1.Text = "0%"; if (!File.Exists(this.textBox1.Text)) { MessageBox.Show("找不到目标文件!"); return; } if (!Directory.Exists(this.textBox2.Text)) { MessageBox.Show("请选择有效的保存路径!"); return; } string fileRead = this.textBox1.Text; string fileSave = Path.Combine(this.textBox2.Text, _fileName); System.Threading.ThreadPool.QueueUserWorkItem((o) => { using (IDisposable file = new FileStream(fileRead, FileMode.Open, FileAccess.Read), fileWrite = new FileStream(fileSave, FileMode.Create, FileAccess.Write)) { int count = 0; long fileLength =((FileStream)file).Length; //目标文件大小 //根据目标文件大小创建byte数组长度 byte[] data = new byte[fileLength > 1024 * 1024 * 30 ? 1024 * 1024 * 30 : fileLength]; //30M 1024 * 1024 * 30 int step = (int)Math.Ceiling(fileLength * 1.0 / data.Length); //分流段数 double n = (100 * 1.0 / step); //每次进度条累加 double m = 0; //累加统计 int spam = 1; //时间间隔 double speed = 0; //拷贝速度 do { DateTime time = DateTime.Now; //文件流操作 count = ((FileStream)file).Read(data, 0, data.Length); ((FileStream)fileWrite).Write(data, 0, count); //保存时间间隔,单位毫秒 spam = (DateTime.Now - time).Milliseconds > 0 ? (DateTime.Now - time).Milliseconds : spam;//必须大于0 //计算速度 单位k/s
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值