您的位置:首页 > 编程语言 > C#

C# 传统遍历与迭代器

2017-03-07 10:33 162 查看
引言:

  在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable或IEnumerable接口,(之所以来必须要实现IEnumerable这个接口,是因为foreach是迭代语句,要使用foreach必须要有一个迭代器才行的,然而IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以实现了IEnumerable接口,就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器就自然就可以使用foreach语句了),然而在C# 1.0中要获得迭代器就必须实现IEnumerable接口中的GetEnumerator()方法,然而要实现一个迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法,然而 C# 2.0中提供 yield关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。下面就具体介绍了C# 2.0 中如何提供对迭代器的支持.

一、迭代器的介绍

  迭代器大家可以想象成数据库的游标,即一个集合中的某个位置,C# 1.0中使用foreach语句实现了访问迭代器的内置支持,使用foreach使我们遍历集合更加容易(比使用for语句更加方便,并且也更加容易理解),foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置(foreach其实也相当于是一个语法糖,把复杂的生成代码工作交给编译器去执行)。

二、C#1.0如何实现迭代器

  在C# 1.0 中实现一个迭代器必须实现IEnumerator接口,下面代码演示了传统方式来实现一个自定义的迭代器:

using System;
using System.Collections;

namespace 迭代器Demo
{
class Program
{
static void Main(string[] args)
{
Friends friendcollection = new Friends();
foreach (Friend f in friendcollection)
{
Console.WriteLine(f.Name);
}

Console.Read();
}
}

/// <summary>
///  朋友类
/// </summary>
public class Friend
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Friend(string name)
{
this.name = name;
}
}

/// <summary>
///   朋友集合
/// </summary>
public class Friends : IEnumerable
{
private Friend[] friendarray;

public Friends()
{
friendarray = new Friend[]
{
new Friend("张三"),
new Friend("李四"),
new Friend("王五")
};
}

// 索引器
public Friend this[int index]
{
get { return friendarray[index]; }
}

public int Count
{
get { return friendarray.Length; }
}

// 实现IEnumerable<T>接口方法
public  IEnumerator GetEnumerator()
{
return new FriendIterator(this);
}
}

/// <summary>
///  自定义迭代器,必须实现 IEnumerator接口
/// </summary>
public class FriendIterator : IEnumerator
{
private readonly Friends friends;
private int index;
private Friend current;
internal FriendIterator(Friends friendcollection)
{
this.friends = friendcollection;
index = 0;
}

#region 实现IEnumerator接口中的方法
public object Current
{
get
{
return this.current;
}
}

public bool MoveNext()
{
if (index + 1 > friends.Count)
{
return false;
}
else
{
this.current = friends[index];
index++;
return true;
}
}

public void Reset()
{
index = 0;
}

#endregion
}
}


运行结果:



三、使用C#2.0的新特性简化迭代器的实现

  在C# 1.0 中要实现一个迭代器必须实现IEnumerator接口,这样就必须实现IEnumerator接口中的MoveNext、Reset方法和Current属性,从上面代码中看出,为了实现FriendIterator迭代器需要写40行代码,然而在C# 2.0 中通过yield return语句简化了迭代器的实现,下面看看C# 2.0中简化迭代器的代码:

using System;
using System.Collections;

namespace CSharp2._0版迭代器
{
class Program
{
#region C# 2.0 使用 yield return 语句实现迭代器
public class Car
{
public string BrandName { get; set; }
public Car() { }
public Car(string name)
{
this.BrandName = name;
}
}

public class Cars : IEnumerable
{
private Car[] carArray;

public Cars()
{
carArray = new Car[] {
new Car("Toyota"),
new Car("Nissna"),
new Car("Audi")
};
}
/// <summary>
/// 索引器
/// </summary>
public Car this[int index]
{
get { return carArray[index]; }
}

public int Count
{
get { return carArray.Length; }
}
// C# 2.0中简化迭代器的实现
public IEnumerator GetEnumerator()
{
for (int index = 0; index < carArray.Length; index++)
{
// 使用yield return 就不需要额外定义一个类似FriendIterator的迭代器来实现IEnumerator
// 在C# 2.0中只需要使用下面语句就可以实现一个迭代器
yield return carArray[index];
}
}

}

#endregion

static void Main(string[] args)
{
#region C#2.0的新特性简化迭代器的实现

Cars carsCollection = new Cars();
foreach (Car car in carsCollection)
{
Console.WriteLine(car.BrandName);
}

Console.ReadKey();
#endregion
}
}
}


 在上面代码中有一个yield return 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到yield return语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,大家可以通过Reflector工具进行查看:

 


从上面截图可以看出,yield return 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成。

四、迭代器的执行过程



五、迭代器的延迟计算

从第四部分中迭代器的执行过程中可以知道迭代器是延迟计算的, 因为迭代的主体在MoveNext()中实现(因为在MoveNext()方法中访问了集合中的当前位置的元素),Foreach中每次遍历执行到in的时候才会调用MoveNext()方法,所以迭代器可以延迟计算,下面通过一个示例来演示迭代器的延迟计算:

using System;
using System.Collections.Generic;

namespace CSharp2._0迭代器的执行过程
{
class Program
{
#region 迭代器的延迟计算

public static IEnumerable<int> WithNoIterator()
{
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("当前i的值为:{0}", i);
if (i > 1)
{
list.Add(i);
}
}

return list;
}

public static IEnumerable<int> WithIterator()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
if (i > 1)
{
yield return i;
}
}
}

#endregion
static void Main(string[] args)
{
// 测试一
Console.WriteLine("测试一:");
WithNoIterator();
Console.WriteLine();

// 测试二
Console.WriteLine("测试二:");
WithIterator();
Console.WriteLine();

// 测试三
Console.WriteLine("测试三:");
foreach (int j in WithIterator())
{
Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);
}

Console.ReadKey();
}
}
}


运行结果:



测试一:正如我们期望的那样输出了结果,列出来是为了更好说明测试二迭代器的延迟计算

测试二:什么都没有输出,我们用Reflector工具查看WithIterator()方法:



  代码中调用WithIterator()时,对于编译器而言,就是实例化了一个< WithIterator > d_1的对象(< WithIterator > d_1类是编译看到WithIterator方法中包含Yield return 语句生成的一个迭代器类),所以运行测试一的代码时,控制台中什么都不输出。

测试三:为什么2,3,4会运行两次的呢?下面具体为大家分析下为什么会有这样的结果。我们用Reflector工具查看MoveNext():





  从截图中可以看到,将下面的输出语句

Console.WriteLine(“在WithIterator方法中的, 当前i的值为:{0}”, i);

生成到迭代器的 MoveNext()方法体中了,所以

public static IEnumerable<int> WithIterator()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
if (i > 1)
{
yield return i;
}
}
}


WithIterator方法中的值,被依次输出到控制台了。

参考 “迭代器的执行过程”图中的示意,程序在执行到yield return i 关键字时才会返回下面的foreach循环,将i值带回给j,输出

Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);


后继续执行for循环,所以 2,3,4被输出到屏幕2次

六、总结

  本文主要介绍了C# 2.0中通过yield return语句对迭代器实现的简化,然而对于编译器而言,却没有简化,它同样生成了一个类去实现IEnumerator接口,只是我们开发人员去实现一个迭代器得到了简化而已。通过本文大家可以对迭代器有一个进一步的认识,并且迭代器的延迟计算也是Linq的基础。

参考链接:

C#稳固基础:传统遍历与迭代器

C#基础知识系列]专题十二:迭代器

不能不说的C#特性-迭代器(上)及一些研究过程中的副产品

不能不说的C#特性-迭代器(下),yield以及流的延迟计算
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  迭代器 c# Linq