zoukankan      html  css  js  c++  java
  • 初次使用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里面都有详细的讲解,感觉没有什么好多说的。

  • 相关阅读:
    k8s pod 定义 文件 yaml
    动态规划(1)
    Angular封装WangEditor富文本组件
    使用Angular CDK实现一个Service弹出Toast组件
    让Angular自定义组件支持form表单验证
    等了半年,m1芯片原生支持的.NET IDE出现了
    eachrt
    dgy
    开源节流
    company
  • 原文地址:https://www.cnblogs.com/xiao99/p/6098285.html
Copyright © 2011-2022 走看看