来博客园已经快八个月了,这是第一次发表博客,以前之因此没有动笔写,彻底是由于本身功力还太浅薄,喜欢博客园的缘由,就是由于这里干净,纯粹,写下垃圾的博客,供别人阅读是一件可耻的事。能与博客园结缘是必然的事,我喜欢.net技术,而博客园是国内公认的,关于.net技术网站中最好的一个,在这里能够见到众多MS的MVP,就是最好的证实。“原创才是博客”的理念在这里,被你们践行的很好,这样的氛围很适合对技术感兴趣的人。八个月前在网上搜索资料时,无心看到了站长嘟嘟大神的佳做,文笔清晰,观点独特,细致入微,读后让我钦佩不已;便注册了一个博客园的帐号。废话不说了,上码,若是你能够一眼看穿下面这段代码的执行流程,请您就飘过吧(这段代码摘抄自《C#高级编程》,第七版,让你们带着问题来学习我以为是很好的方式)编程
static void Main() 安全
{ 函数
var game = new GameMoves();学习
var a = game.Cross(); 网站
while (a.MoveNext())ui
{.net
a = a.Current as IEnumerator;设计
} 继承
Console.Read(); 递归
}
public class GameMoves
{
IEnumerator cross;
IEnumerator circle;
public GameMoves()
{
cross = Cross();
circle = Circle();
}
int move = 0;
public const int MaxMoves = 10;
public IEnumerator Cross()
{
while (true)
{
Console.WriteLine("Cross,move{0}", move);
if (++move >= MaxMoves) yield break;
yield return circle;
}
}
public IEnumerator Circle()
{
while (true)
{
Console.WriteLine("Circle,move{0}", move);
if (++move >= MaxMoves) yield break;
yield return cross;
}
}
}
上面的代码我不会作太多的解释,若是你理解了本文内容,你应该能够看懂(注:实在看不懂,就用单步执行吧)。下面再来看看另外这段代码,当我第一次看到这段代码的执行结果时,被惊呆了,彻底颠覆了我两年编程学习对函数执行的认识。
class Program
{
static void Main(string[] args)
{
var ie = IE();
Console.WriteLine("IE()已经调用了,你看到输出了吗?"); ie.MoveNext();
Console.Read();
}
public static IEnumerator IE()
{
while (true)
{
Console.WriteLine("你在调用IE()时看不到个人执行,我不是并行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值调用了MoveNext()\n而且每次调用均可以看到我哦");
yield return "任意类型均可以直接返回哦";
}
}
}
这段代码稍后我会详细的解释,为了解释清楚,请耐心听我慢慢道来,再看一段代码(哈哈,学编程的,要习惯哦)
static void Main(string[] args)
{
String[] myArr = new String[] { "The", "quick", "brown"};
Console.WriteLine("用标准的C#语法使用枚举器");
foreach (var str in myArr)
{
Console.WriteLine(str);
}
Console.WriteLine("使用枚举接口使用枚举器");
var myEnumerator = myArr.GetEnumerator();
while ((myEnumerator.MoveNext()) && (myEnumerator.Current != null))
{
Console.WriteLine("{0}", myEnumerator.Current);
}
Console.Read();
}
能够看到,执行结果彻底同样,编译器在处理foreach语句块时,就是编译为与以上相似的等价代码的(对GetEnumerator()的调用);foreach只是语法糖而已;这个方法(GetEnumerator())的返回值是一个接口(IEnumerator),也就是说,在这个方法体内须要出现一个实现了这个接口(System.Collections.IEnumerator)的类。那就先来看看这个接口的定义;
public interface IEnumerator
{
object Current{get;} //注意这个属性是只读的,这也为何咱们不能在foreach块中改变集合元素的缘由(集合元素内部的属性能够更改哦);
bool MoveNext(); // 将枚举数推动到集合的下一个元素,成功返回true,失败返回false
void Reset(); //将枚举数设置为其初始位置,该位置位于集合中第一个元素以前
}
这个接口还有一个泛型版本
public interface IEnumerator<T>:IDisposable,IEnumerator
{
T Current{get;};
bool MoveNext();
void Reset();
void Dispose();
}
值得注意的是,泛型版本继承了IDispose接口,(关于泛型,理解的重点是,要区分开泛型的外壳类和外壳类包含的元素类,这个元素的类型是可变的,而本质上,真正可变的类型确是外壳类自己,这里的名词是个人杜撰,还算形象吧)其余的注释已经很清楚,再也不过多解释;(这里涉及的接口比较多,名称还很相似,注意分辨哦) 而这个方法(System.Collections.IEnumerator GetEnumerator())自己倒是在IEnumerable接口中定义,能够看到不少的集合类都继承实现了这个接口(IEnumerable),而事实上,要使用枚举器,这个接口并非必须的,只要集合类有一个这个签名的的方法(System.Collections.IEnumerator GetEnumerator())就足够了,编译器根本不会检查集合类是否实现了接口IEnumberable,这看起来有点神奇,彷佛对代码安全有点影响,事实上根本没有,仔细想一下就会明白,编译器默认会尝试将foreach转换为对 GetEnumerator()的调用,但它发现,类中根本没有这个方法,天然报错,注意,报错不是由于集合没有实现接口IEnumberable,而是由于集合没有提供GetEnumerator(),这个我讲的已经太罗嗦了,为了证实这一点,运行下面代码就能够证实;注意program类没有继承IEnumberable哦,
class Program
{
static void Main(string[] args)
{
Program program=new Program();
foreach (string s in program)
{
Console.WriteLine(s);
}
Console.Read();
}
public IEnumerator GetEnumerator()
{
yield return"一";
yield return "二";
yield return "三";
}
}
下面仔细解释一下刚才遗留的代码(再次贴来,请你把它贴到VS里执行一下)
class Program
{
static void Main(string[] args)
{
var ie = IE();
Console.WriteLine("IE()已经调用了,你看到输出了吗?");
ie.MoveNext();
Console.Read();
}
public static IEnumerator IE()
{
while (true)
{
Console.WriteLine("你在调用IE()时看不到个人执行,我不是并行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值调用了MoveNext()\n而且每次调用均可以看到我哦");
yield return "任意类型均可以直接返回哦";
}
}
}
至于MS为何要这样设计,若是你已经对LINQ有必定的理解,相信你应该能够理解这一点,LINQ的延迟查询正是利用这一点。关于LINQ的问题不是这篇博客的重点,反正记住这是MS特殊处理的就行啦。若是要本身实现枚举器,这个特性也是颇有的。下面咱们再看看这段代码的另外一个值得关注的地方;yield return语句,能够看到IE()的返回值是 IEnumerator接口类型,而咱们根本没有定义实现这个接口的类,那这个方法还怎么工做啊,应该直接编译错误才是啊,哈哈,要注意,返回值是经过yield return传递的哦,但是yield return里的类型和返回值类型不同啊,相信不少新手都有这样的迷惑,其实这是又是语法糖而已,(MS对开发人员真是太好了,为了咱们少击打键盘,费劲心思啊,但同时对于新手也真是不小的挑战,容易让新手浮于技术的表面,而看不到本质的原理)编译器会把yield return语句转化为一个实现了IEnumerator的类,这个类对于编程人员是透明的;每次yield return时都由这个类来收集结果,至于返回值,在调用这个这个方法时,已经当即返回了。能够把这个这个返回值看作一个特殊的句柄,只有用它调用MoveNext()时(MoveNext()会由foreach语句自动调用哦),该方法才真正执行,固然,yield return部分这时已经再也不方法内了,不然就陷入死递归了,感兴趣的同窗能够试试吧yield return返回语句分散开来,中间再夹杂一些其余语句,而后手工调用Movenext()观察一下奇怪的执行路线,好了,要介绍的已经差很少了,不明白孩子再好好理解理解吧,否则LINQ就无法学了,本文介绍的内容虽然很基础,但我认为对很多人仍是有很多意义的,因此就大胆发到首页了哈,请大鸟们不要~~