2009-06-24 45 views
2

我碰到一个有趣的问题今天在这里我有两个方法,快速浏览,都做同样的事情来了。这是返回一个IEnumerable Foo对象。为什么LINQ会以不同的方式处理两个“相同”事物的方法?

我已经低于它们定义为列表1和列表2:

public class Foo 
{ 
    public int ID { get; set; } 
    public bool Enabled { get; set;}  
} 

public static class Data 
{ 
    public static IEnumerable<Foo> List1 
    { 
     get 
     { 
      return new List<Foo> 
      { 
       new Foo {ID = 1, Enabled = true}, 
       new Foo {ID = 2, Enabled = true}, 
       new Foo {ID = 3, Enabled = true} 
      }; 
     } 
    } 

    public static IEnumerable<Foo> List2 
    { 
     get 
     { 
      yield return new Foo {ID = 1, Enabled = true}; 
      yield return new Foo {ID = 2, Enabled = true}; 
      yield return new Foo {ID = 3, Enabled = true}; 
     } 
    } 
} 

现在考虑下面的测试:

IEnumerable<Foo> listOne = Data.List1; 
listOne.Where(item => item.ID.Equals(2)).First().Enabled = false; 
Assert.AreEqual(false, listOne.ElementAt(1).Enabled); 
Assert.AreEqual(false, listOne.ToList()[1].Enabled); 

IEnumerable<Foo> listTwo = Data.List2; 
listTwo.Where(item => item.ID.Equals(2)).First().Enabled = false; 
Assert.AreEqual(false, listTwo.ElementAt(1).Enabled); 
Assert.AreEqual(false, listTwo.ToList()[1].Enabled); 

这两种方法似乎做“相同”的事情。

为什么在测试代码中的第二个断言失败?
为什么listTwo的第二个“富”项目没有得到设置为false时,它是那么listOne?

注意:我解释了为什么允许这种情况发生,以及两者的区别。不知道如何解决第二个断言,因为我知道如果我添加一个ToList调用List2它将工作。

+0

感谢您的所有答案。我很清楚发生了什么。我经过一些不同的解释来清除它!干杯! – Michael 2009-06-25 22:47:51

回答

6

第一个代码块构建一次项目并返回一个包含项目的列表。

每当IEnumerable被浏览时,第二块代码就会生成这些项目。

这意味着第一个块的第二行和第三行对同一个对象实例进行操作。第二块的第二和第三行中的Foo 不同情况下工作(如您遍历新实例被创建)。

看到这个的最好方法是在方法中设置断点并在调试器下运行此代码。第一个版本只会触发一次断点。第二个版本将在两次,一次在.Where()调用期间以及在.ElementAt调用期间一次。 (编辑:使用修改后的代码,它也会在ToList()调用期间第三次到达断点)。

这里要记住的是,迭代器方法(即使用yield return)将会每运行枚举器迭代通过,而不仅仅是当初始返回值被构造时。

1

listTwo是一个迭代器 - 状态机。

的ElementAt必须开始在迭代的开始正确地得到了IEnumerable第i个指标(无论它是否是一个迭代器状态机或一个真正的IEnumerable实例),正因为如此,listTwo将被重新初始化所有三个项目的默认值Enabled = true。

0

建议:编译代码,并与反射器打开。收益率是一种语法结果。您将能够在您编写的代码和为yield关键字生成的代码中看到代码逻辑差异。两者都不一样。

2

那些绝对是不是一样的东西。

第一次构建并在您调用它时返回列表,如果需要,您可以将它重新列表并列出它,包括添加或删除项目,并且一旦将结果在一个变量中,你正在处理那一组结果。调用该函数会产生另一组结果,但重复使用单个调用的结果会影响相同的对象。

第二个构建IEnumerable。您可以枚举它,但是如果不先调用.ToList()就不能将其视为列表。实际上,调用该方法不会执行什么,直到您实际遍历它为止。试想一下:

var fooList = Data.List2().Where(f => f.ID > 1); 
// NO foo objects have been created yet. 
foreach (var foo in fooList) 
{ 
    // a new Foo object is created, but NOT until it's actually used here 
    Console.WriteLine(foo.Enabled.ToString()); 
} 

注意,上面的代码将创建第一个(未使用)的Foo实例,但直到进入foreach循环。所以这些项目直到需要时才会创建。但这意味着每次你打电话给他们时,你都在建立一套新的物品。

相关问题