好久不写了,总想接着之前的写,但是随着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里吧。
就讨论这么多吧,以后继续……