给出以下代码片段:
public class Foo
{
public IEnumerable<string> Sequence { get; set; }
public IEnumerable<string> Bar()
{
foreach (string s in Sequence)
yield return s;
}
}
是以下代码片段在语义上等效,还是不同?如果不同,它们的功能如何不同?
public class Foo2
{
public IEnumerable<string> Sequence { get; set; }
public IEnumerable<string> Bar2()
{
return Sequence;
}
}
这个问题的灵感来自this question,它提出了一个类似情况的不同问题。
答案 0 :(得分:23)
这两者并不相同。两种Bar
方法之间延迟执行的语义是不同的。当您致电Foo.Bar
时,Sequence
会将IEnumerable
评估为Bar
值。当您枚举Foo2.Bar2
返回的序列时,Sequence
会将Bar2
评估为该变量中的值。
我们可以写一个足够简单的程序来观察这里的差异。
//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));
打印出来:
新
老
在这种特殊情况下,我们的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;
}
}
现在让我们尝试比较这两种方法,看看它们是如何运作的:
var query = foo.IteratorBlock();
var query2 = foo.NoIteratorBlock();
Console.WriteLine("---");
query.Count();
query.Count();
query2.Count();
query2.Count();
这将打印出来:
我在没有迭代器块的情况下迭代序列 ---
我在迭代器块中迭代Sequence 我在迭代器块中迭代序列
在这里我们可以看到,当方法本身被调用时,非迭代器块的副作用发生,并且迭代器块的副作用不会发生在那个时间点。然后,稍后,每次迭代非迭代器块时它根本不会产生副作用,但迭代器块会在每次迭代查询时产生副作用。 / p>