!!! 完整代码在文章末尾 !!!
1. 遍历int数组输出
int[] nums = { 1, 3, 5, 7, 9 };
foreach (var item in nums)
{
Console.WriteLine(item);
}
输出结果:
2. 自定义类型能否像这样用foreach输出?
代码如下:
class Program
{
static void Main(string[] args)
{
int[] nums = { 1, 3, 5, 7, 9 };
foreach (var item in nums)
{
Console.WriteLine(item);
}
Hand hand = new Hand();
foreach (var item in hand)
{
}
}
}
class Hand
{
private string[] handName = { "大拇指", "食指", "中指", "无名指", "小拇指" };
}
发现报了一个编译时异常:
foreach语句不能操作于类型Hand的变量,因为Hand对于GetEnumerator不包含一个公开的实例方法或扩展定义
3. 为什么基本数据类型可以?来看Array源码
翻看Array源码可知,数组实现了一个接口,IEnumerable(可枚举)
并且重写了GetEnumerator
//
// Summary:
// Returns an System.Collections.IEnumerator for the System.Array.
//
// Returns:
// An System.Collections.IEnumerator for the System.Array.
public IEnumerator GetEnumerator();
所以,数组是可以被遍历输出的
因此,自定义类型 Hand 也实现这个接口,就可以被遍历啦!
4. 自定义类型实现IEnumerable
其实,实不实现 接口IEnumerable 都可, 只要有 public IEnumerator GetEnumerator() 这个方法即可
还记得上面提到的异常吗
foreach语句不能操作于类型Hand的变量,因为Hand对于GetEnumerator不包含一个公开的实例方法或扩展定义
4.1 重写GetEnumerator()
/// <summary>
/// 实不实现 接口IEnumerable 都可, 只要有public IEnumerator GetEnumerator()这个方法即可
/// </summary>
class Hand //: IEnumerable
{
private string[] handName = { "大拇指", "食指", "中指", "无名指", "小拇指" };
public IEnumerator GetEnumerator()
{
return new HandEnumrator(handName);
}
}
4.2 自定义类实现接口IEnumerator
在4.1上述代码中,重写 GetEnumerator 方法,它的返回值是一个 IEnumerator(枚举器) ,所以需要自定义一个类,实现IEnumerator接口
class HandEnumrator : IEnumerator
{
private string[] names;
private int index = -1;
public HandEnumrator(string[] names)
{
this.names = names;
}
public bool MoveNext()
{
index++;
return index < names.Length;
}
public object Current => names[index];
public void Reset()
{
index = -1;
}
}
4.3 运行输出
4.4 分析
原来 调用GetEnumerator() 返回一个IEnumerator迭代器对象,这个对象能往下移(MoveNext()),条件成立,就可以取出当前指向的值(Current)
5. 那么问题来了,讲了这么多,跟标题2里用foreach带来的异常有什么关联呢?
问得好,重点来了,接下来删除 HandEnumerator 代码
完整代码如下:
class Program
{
static void Main(string[] args)
{
Hand hand = new Hand();
foreach (var item in hand)
{
Console.WriteLine(item);
}
}
}
class Hand
{
private string[] handName = { "大拇指", "食指", "中指", "无名指", "小拇指" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < handName.Length; i++)
{
yield return handName[i];
}
}
}
输出结果:
没什么问题。
哎嗨这是怎么肥四???
原来在底层,
yiel之前的代码相当于
public bool MoveNext()
{
index++;
return index < names.Length;
}
return之后的代码相当于
public object Current => names[index];
并且通过打断点调试发现GetEnumerator方法的调用时机
- foreach 进循环,调用 GetEnumerator 方法,进方法循环
- 执行到 yield 暂时离开方法
- 当代码执行到 Console.WriteLine(item); 时
- 回到 GetEnumerator 方法,执行 return 后的代码 handName[i]
- 控制台输出结果
6. 理解协程
这个方法写法就是重点!
public IEnumerator GetEnumerator()
{
for (int i = 0; i < handName.Length; i++)
{
yield return handName[i];
}
}
这和Unity脚本里协程不能说是异曲同工,可以说是完全一样
private void Start()
{
StartCoroutine(DemoCoroutine());
}
private IEnumerator DemoCoroutine()
{
yield return new WaitForSeconds(1);
Debug.Log("我是协程方法");
}
7. 完整代码
using System;
using System.Collections;
namespace IEnumeratorStudy
{
class Program
{
static void Main(string[] args)
{
int[] nums = { 1, 3, 5, 7, 9 };
foreach (var item in nums)
{
Console.WriteLine(item);
}
Hand hand = new Hand();
//var iter = hand.GetEnumerator();
//while (iter.MoveNext())
//{
// Console.WriteLine(iter.Current);
//}
foreach (var item in hand)
{
Console.WriteLine(item);
}
}
}
/// <summary>
/// 实不实现 接口IEnumerable 都可, 只要有public IEnumerator GetEnumerator()这个方法即可
/// </summary>
class Hand //: IEnumerable
{
private string[] handName = { "大拇指", "食指", "中指", "无名指", "小拇指" };
//public IEnumerator GetEnumerator()
//{
// return new HandEnumrator(handName);
//}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < handName.Length; i++)
{
yield return handName[i];
}
}
}
class HandEnumrator : IEnumerator
{
private string[] names;
private int index = -1;
public HandEnumrator(string[] names)
{
this.names = names;
}
public bool MoveNext()
{
index++;
return index < names.Length;
}
public object Current => names[index];
public void Reset()
{
index = -1;
}
}
}