Ser*_*rvy -22 c# iterator yield-return deferred-execution
给出以下代码段:
public class Foo
{
public IEnumerable<string> Sequence { get; set; }
public IEnumerable<string> Bar()
{
foreach (string s in Sequence)
yield return s;
}
}
Run Code Online (Sandbox Code Playgroud)
以下代码片段在语义上是等价的,还是不同?如果不同,它们的功能如何不同?
public class Foo2
{
public IEnumerable<string> Sequence { get; set; }
public IEnumerable<string> Bar2()
{
return Sequence;
}
}
Run Code Online (Sandbox Code Playgroud)
这个问题的灵感来自于这个问题,它提出了一个类似情况的不同问题.
Ser*_*rvy 24
这两者并不相同.两种Bar方法之间如何延迟执行的语义是不同的. 当你打电话时Foo.Bar会评估Sequence成一个IEnumerable值. 当您枚举返回的序列时,将评估该变量中的值.BarFoo2.Bar2SequenceBar2
我们可以编写一个足够简单的程序来观察这里的差异.
//Using iterator block
var foo = new Foo();
foo.Sequence = new[] { "Old" };
var query = foo.Bar();
foo.Sequence = new[] { "New" };
Console.WriteLine(string.Join(" ", query));
//Not using iterator block
var foo2 = new Foo2();
foo2.Sequence = new[] { "Old" };
var query2 = foo2.Bar2();
foo2.Sequence = new[] { "New" };
Console.WriteLine(string.Join(" ", query2));
Run Code Online (Sandbox Code Playgroud)
打印出:
新
老
在这种特殊情况下,我们的Bar方法也没有副作用.如果确实如此,那么了解程序所具有的语义以及应该具有的语义就不那么重要了.例如,让我们修改这两个方法,使它们有一些可观察到的副作用:
public class Foo
{
public IEnumerable<string> Sequence { get; set; }
public IEnumerable<string> IteratorBlock()
{
Console.WriteLine("I'm iterating Sequence in an iterator block");
foreach (string s in Sequence)
yield return s;
}
public IEnumerable<string> NoIteratorBlock()
{
Console.WriteLine("I'm iterating Sequence without an iterator block");
return Sequence;
}
}
Run Code Online (Sandbox Code Playgroud)
现在让我们尝试比较这两种方法,看看它们是如何运作的:
var query = foo.IteratorBlock();
var query2 = foo.NoIteratorBlock();
Console.WriteLine("---");
query.Count();
query.Count();
query2.Count();
query2.Count();
Run Code Online (Sandbox Code Playgroud)
这将打印出来:
我在没有迭代器块的情况下迭代Sequence
---
我在迭代器块中
迭代Sequence我在迭代器块中迭代Sequence
在这里我们可以看到非迭代器块的副作用在调用方法本身时发生,并且迭代器块的副作用在那个时间点不会发生.然后,稍后,每次迭代非迭代器块时它根本不会产生副作用,但迭代器块会在每次迭代查询时产生副作用.