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

被折叠的 条评论
为什么被折叠?



