哪里与foreach与if - 为什么不同的结果?

时间:2017-08-25 18:29:21

标签: c# .net closures

此代码

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

     

GetDistinctValuesUsingForEach No2:1,2,3,4

我不明白为什么我没有在行中获得任何值&#34; GetDistinctValuesUsingWhere No2&#34;。

任何人都可以向我解释这个吗?

在Scott的回答之后

更新,我将示例更改为以下内容:

       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(items,capturedVariables);

并注释行

  

foreach(var i in items)                 if(capturedVariables.set.Add(i))                      收益率回报i;

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

我仍然没有得到它......

2 个答案:

答案 0 :(得分:8)

GetDistinctValuesUsingWhere No2没有返回任何结果的原因是因为变量捕获。

你的where方法更像是这个函数

    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。

答案 1 :(得分:3)

回答更新后的版本:

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

如果有必要,在代码的不同位置放置一些断点:这可以帮助您理解我在这里解释的内容。