闲话 + .Net yeild语句

好久不写了,总想接着之前的写,但是随着mvc慢慢熟悉,不知道写什么了,又因为比较忙,也懒得写了。

于是写点小玩意儿吧。其实.net从开始写,就是写写asp.net。那时候,对前端后端怎么交互的,还一知半解的。用了asp.net,更是似乎不需要深入理解其原理,也能像写winform一样写web应用。拖拖控件,写写事件,就完了。后来慢慢研究.net,发现.net框架确实是不折不扣的重量级库和运行时,想要实现的功能,基本都能找到官方的封装,虽然不一定好用。再后来,接触Linq,Lambada,Expression,Attribute,才发现.net有这么多新颖高端的玩意儿,跟之前写c++完全不同。

今儿个说说yield语句吧。yield语句分为yield return (value)和yeild break,前者用于迭代返回迭代器接口数据,后者退出迭代过程。

刚接触的时候,各种不理解。后来理解了一点,觉得用了这货的方法应该是异步执行的吧。再后来,做了个小实验,才明白这货是怎么玩的。

考虑下面的简单代码:

class Program
    {
        public static IEnumerable<int> GetIEnum()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("a:" + i);
                yield return i;
            }
        }
        static void Main(string[] args)
        {
            var data = GetIEnum();
            foreach (var i in data)
            {
                Console.WriteLine("b:" + i);
            }
        }
    }
运行的结果会是什么呢?

没错,不是随机顺序,而是很规律的a、b交替出现:

a:0

b:0

a:1

b:1

……

从上面看出迭代方法的执行不是异步的,而是每执行一次迭代方法中的for,就再执行一次main里的foreach

如果稍微加点代码我们可以看出他也不是先执行到第一个yield return停止执行,然后返回执行调用方法的下条语句的:

class Program
    {
        public static IEnumerable<int> GetIEnum()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("a:" + i);
                yield return i;
            }
        }
        static void Main(string[] args)
        {
            var data = GetIEnum();
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("c:" + i);
            }
            foreach (var i in data)
            {
                Console.WriteLine("b:" + i);
            }
        }
    }
这回是什么呢
结果是这样:

c:0

c:1

c:2

……

a:0

b:0

a:1

b:1

……

说明在调用

var data = GetIEnum();
时候,GetIEnum方法压根没有执行一条语句,而是直接返回,等到遇到foreach遍历结果的时候,才会开始迭代。

再次修改代码我们能看到迭代的运行机制:

class Program
    {
        public static IEnumerable<int> GetIEnum()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("a:" + i);
                yield return i;
                Console.WriteLine("d:" + i);
            }
        }
        static void Main(string[] args)
        {
            var data = GetIEnum(); 
                foreach (var i in data)
                {
                         Console.WriteLine("b:" + i); 
                }
        }
    }

在yield return后面再次输出,执行结果如下:

a:0

b:0

d:0

a:1

b:1

d:1

……

结果说明yield return是如何工作的:在执行到foreach语句迭代获取迭代方法返回结果时,迭代方法才开始运行,这时,会从第一条语句开始执行,直到遇到yield return。此时,方法的执行状态将被保存,跳转回foreach继续执行,将return的值作为foreach取出的元素,执行完foreach体后,又会跳回之前yield return的语句的下一条,继续执行,直到下一条yield return,继续foreach体-迭代方法的循环,直到运行到迭代过程结束,或者遇到yield break跳出迭代过程为止。

由此看,似乎在运行到foreach语句的时候,角色发生了变化,迭代方法似乎成了调用者,foreach块倒成了被调用者,yield return语句相当于做了一次foreach块的调用,何时调用结束取决于迭代方法何时结束。

为了验证上面的推断是否正确,我们再修改下代码:

class Program
    {
        public static IEnumerable<int> GetIEnum()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("a:" + i);
                yield return i;
                yield return i;
            }
        }
        static void Main(string[] args)
        {
            var data = GetIEnum();
            foreach (var i in data)
            {
                Console.WriteLine("b:" + i);
            }
        }
    }
结果不出意外:

a:0

b:0

b:0

a:1

b:1

b:1

……

foreach块看起来就像是一个方法,在迭代过程每次yield return时都会被调用一次。

那么,使用迭代过程延后获取数据的时候,如果要多次使用foreach遍历,似乎有些要担心了。迭代方法会不会不缓存数据,在每次调用foreach时,迭代方法都要运行一次?

我们试一下:

class Program
    {
        public static IEnumerable<int> GetIEnum()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("a:" + i);
                yield return i;
            }
        }
        static void Main(string[] args)
        {
            var data = GetIEnum();
            foreach (var i in data)
            {
                Console.WriteLine("b:" + i);
            }
            foreach (var i in data)
            {
                Console.WriteLine("c:" + i);
            }
        }
    }

结果很悲剧的印证了:

a:0

b:0

a:1

b:1

……

a:0

c:0

a:1

c:1

……

可以看到,用foreach遍历多少次迭代器方法的结果,就会导致迭代方法执行多少次,如果迭代方法获取数据本身比较耗时或者消耗资源,foreach的次数不止一次的话,这个方法会造成资源浪费。所以这种时候,还是老实把结果一次取出,存到Array或者List里吧。

就讨论这么多吧,以后继续……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值