初次使用C#中的yield

  这几天在Python程序员的微信订阅号中总是见到yield的关键字,才想起来在C#中也是有yield,但是只是知道有,从来没有了解过他的用法,今天有时间就来看看是怎么使用的。刚开始肯定就是搜索一下用法了,找到两篇说明示例,一是 C# 中的"yield"使用,第二个是MSDN的官方api yield(C# 参考)

说实话第一个示例看完还是很模糊的概念,例子也没有看懂是在干嘛,一直到MSDN中给出结果集我才明白了到底的用法是怎么样的。

先来举例一个需求: 一个方法返回一个IEnumerable 类型结果集(例如返回一个list<int>的结果),通常的代码是这样的

 

 1         /// <summary>
 2         /// 
 3         /// </summary>
 4         /// <returns></returns>
 5         public IEnumerable<int> Method()
 6         {
 7             List<int> results = new List<int>();
 8             int counter = 0;
 9             int result = 1;
10 
11             while (counter++ < 10)
12             {
13                 result = result * 2;
14                 results.Add(result);
15             }
16             return results;
17         }

这样就完成了需求。

但是 有了yield之后就可以这样写了

 1         /// <summary>
 2         /// 
 3         /// </summary>
 4         /// <returns></returns>
 5         public IEnumerable<int> YieldDemo()
 6         {
 7             int counter = 0;
 8             int result = 1;
 9             while (counter++ < 10)
10             {
11                 result = result * 2;              
12                 yield return result;
13             }
14         }

两种效果是一样的,但是从我个人而言我喜欢第二个,感觉更简洁一些。

题外话:在写这两个例子中我又增加了一个知识点

  返回值IEnumerable其实和IEnumerable<object>是等价的,只是IEnumerable的结果需要动态的解析; 

 

但是还搞不清楚这两者实现有什么区别,所以我想看看这两个在做同一件事的时候效率如何,下面来尝试使用while循环10000000的取数据的耗时比较

使用 BenchmarkDotNet  测试结果如下

使用Stopwatch测试结果也是一样的

从这个测试里面可以看出YieldDemo方法几乎没有耗时,但是实际情况是不可能的吧,所以我又尝试做了遍历的测试

 

 1             Stopwatch stop = new Stopwatch();
 2             stop.Start();
 3             var res = new YieldTest().YieldDemo();
 4             foreach (var item in res)
 5             {
 6 
 7             }
 8             var a = stop.ElapsedMilliseconds;
 9             stop.Restart();
10 
11 
12             var rrrrr = new YieldTest().Method();
13             foreach (var item in rrrrr)
14             {
15 
16             }
17             var b = stop.ElapsedMilliseconds;
18             stop.Restart();

这个测试的结果是a=168,b=142.对比上一个测试结果让我更加疑惑,我就开始打断点,看看执行的顺序是怎样的。

结果如下:  

     在 第三行 断点压根就没有进YieldDemo这个方法,而是当进行foreach 遍历结果的时候,才开始进入了YieldDemo这个方法,更奇怪的是每次的foreach 都会进入YieldDemo的while一次去取数据

这个结果让我有点懵了,只能再仔细看看文档解析,

  迭代器方法运行到 yield return 语句时,会返回一个 expression,并保留当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。 可以使用 yield break 语句来终止迭代。 

貌似这里面是涉及到了迭代器的东西。马上找迭代器的知识点,在 详解C# 迭代器 中看到这样一句解释

  需要强调的一点是,对于迭代块,虽然我们写的方法看起来像是在顺序执行,实际上我们是让编译器来为我们创建了一个状态机。这就是在C#1中我们书写的那部分代码---调用者每次调用只需要返回一个值,因此我们需要记住最后一次返回值时,在集合中位置。

  当编译器遇到迭代块时,它创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数

这句话貌似解析了上面的疑问,但是看的有点云里雾里,还要花时间消化一下里面的具体原理 。

官方提示使用yield有一些限制,需要注意

1 不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。
2 可将 yield break 语句置于 try 块或 catch 块中,但不能将其置于 finally 块中。
3 如果 foreach 主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的 finally 块。
4 匿名方法。 有关详细信息,请参阅匿名方法。
5 包含不安全的块的方法。 有关详细信息,请参阅unsafe。

 

针对第一点让我感觉使用好有限制,为啥不能在try-catch 中使用呢?

关于其他的一些使用方法在MSDN里面都有详细的讲解,感觉没有什么好多说的。

 

posted @ 2016-11-24 18:30  xiao99  阅读(9419)  评论(4编辑  收藏  举报