如何转换此迭代器阻止功能更改?

时间:2014-08-26 21:24:27

标签: 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;
    }
}

是以下代码片段在语义上等效,还是不同?如果不同,它们的功能如何不同?

public class Foo2
{
    public IEnumerable<string> Sequence { get; set; }
    public IEnumerable<string> Bar2()
    {
        return Sequence;
    }
}

这个问题的灵感来自this question,它提出了一个类似情况的不同问题。

1 个答案:

答案 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>