2017-08-25 15 views
7

此代码哪里对foreach与if - 为什么不同的结果?

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ConsoleApplication 
{ 
    internal class Program 
    { 
     public static void Main() 
     { 
      var values = new[] {1, 2, 3, 3, 2, 1, 4}; 
      var distinctValues = GetDistinctValuesUsingWhere(values); 
      Console.WriteLine("GetDistinctValuesUsingWhere No1: " + string.Join(",", distinctValues)); 
      Console.WriteLine("GetDistinctValuesUsingWhere No2: " + string.Join(",", distinctValues)); 
      distinctValues = GetDistinctValuesUsingForEach(values); 
      Console.WriteLine("GetDistinctValuesUsingForEach No1: " + string.Join(",", distinctValues)); 
      Console.WriteLine("GetDistinctValuesUsingForEach No2: " + string.Join(",", distinctValues)); 
      Console.ReadLine(); 
     } 

     private static IEnumerable<T> GetDistinctValuesUsingWhere<T>(IEnumerable<T> items) 
     { 
      var set=new HashSet<T>(); 
      return items.Where(i=> set.Add(i)); 
     } 

     private static IEnumerable<T> GetDistinctValuesUsingForEach<T>(IEnumerable<T> items) 
     { 
      var set=new HashSet<T>(); 
      foreach (var i in items) 
      { 
       if (set.Add(i)) 
        yield return i; 
      } 
     } 
    } 
} 

导致以下输出:

GetDistinctValuesUsingWhere NO1:1,2,3,4

GetDistinctValuesUsingWhere NO2:

GetDistinctValuesUsingForEach NO1:1,2- ,3,4

GetDistinctValuesUsingForEac h No2:1,2,3,4

我不明白为什么我没有在“GetDistinctValuesUsingWhere No2”行中得到任何值。

任何人都可以解释这一点吗?

UPDATE从斯科特的回答后,我改变了例子如下:

 private static IEnumerable<T> GetDistinctValuesUsingWhere2<T>(IEnumerable<T> items) 
    { 
     var set = new HashSet<T>(); 
     var capturedVariables = new CapturedVariables<T> {set = set}; 

     foreach (var i in items) 
      if (capturedVariables.set.Add(i)) 
       yield return i; 
     //return Where2(items, capturedVariables); 
    } 

    private static IEnumerable<T> Where2<T>(IEnumerable<T> source, CapturedVariables<T> variables) 
    { 
     foreach (var i in source) 
      if (variables.set.Add(i)) 
       yield return i; 
    } 

    private class CapturedVariables<T> 
    { 
     public HashSet<T> set; 
    } 

这将导致两个倍的输出1,2,3,4。

但是,如果我只是取消对该行

回报Where2(项目,capturedVariables);

和注释行

的foreach(VAR i的项) 如果(capturedVariables.set.Add(i))的产量 返回I;

在GetDistinctValuesUsingWhere2方法中,我只会得到输出1,2,3,4一次。这很简单,删除的行和现在未注释的方法完全相同。

我还是不明白....

+0

题外话,为什么不直接使用'Distinct'(或者用于具有属性的对象,MoreLINQ的'DistinctBy')而不是滚动自己的?或者这仅仅是一个学习练习? –

+0

尝试在你的末尾添加'.ToList()',你应该得到正确的结果。问题在于您要返回表达式,所以每次请求对象时都会重新评估它。 – Kolichikov

+0

可能的重复:[如何判断一个IEnumerable 是否需要延迟执行?](https://stackoverflow.com/questions/1168944/how-to-tell-if-an-ienumerablet-is-subject-to-推迟执行) – Igor

回答

3

接听更新版本:

  • 在包含foreach 环路GetDistinctValuesUsingWhere2()方法的情况下,返回IEnumerable捕获在一个封闭的方法的全部内容,包括set初始化语句。因此,每次您在初始调用GetDistinctValuesUsingWhere2()期间开始迭代可枚举对象而不是时,都会执行此语句。
  • 对于其他变体,如果返回Where2(),则GetDistinctValuesUsingWhere2()方法不需要捕获方法的内容,因为您没有在其中定义迭代器或委托。相反,您返回Where2()作为IEnumerable。 后一种方法仅捕获foreach循环及其参数(已初始化),但不包括set初始化语句本身。因此这次,在初始调用GetDistinctValuesUsingWhere2()期间,设置的初始化语句将只执行一次。

如果需要,在代码的不同位置放置一些断点:这应该有助于您理解我在此尝试解释的内容。

+0

我觉得这个答案在解释两种不同的方法方面做得更好。 –

8

原因GetDistinctValuesUsingWhere NO2不返回任何结果,因为变量捕获。

你在其中方法更像是这个函数

private static IEnumerable<T> GetDistinctValuesUsingWhere<T>(IEnumerable<T> items) 
    { 
     var set=new HashSet<T>(); 
     var capturedVariables = new CapturedVariables {set = set} 
     return Where(items, capturedVariables); 
    } 

    IEnumerable<T> Where(IEnumerable<T> source, CapturedVariables variables) 
    { 
     foreach (var i in items) 
     { 
      if (variables.set.Add(i)) 
       yield return i; 
     } 

    } 

所以,这两种方法都是引擎盖下yield return,但GetDistinctValuesUsingWhere重用的HashSet对于其中GetDistinctValuesUsingForEach生成一个新的HashSet每个枚举每次调用。

+0

问题是,我不明白为什么或在哪里CLR决定它需要保持捕获的变量存活。如果只是在“Where”方法中内联代码,则不会捕获变量,并且该示例在多次调用中按预期工作。 (请参阅我的更新示例)。 –

+1

@umei一个带有“yield return”语句的方法将至少保持到枚举结束(整个方法将保持活动状态)。换句话说,枚举表示整个方法(整个方法转换为状态机)。对于'foreach'版本,lambda表达式捕获对'HashSet'的引用,而enumerable只代表该'lambda'的应用,而不是'HashSet'的实例化。您需要第二次调用'GetDistinctValuesUsingWhere'来获取新的'HashSet'。 –

相关问题