none
一个Linq 的 ToList()问题 RRS feed

  • 问题

  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                List<CategoryLevel> allLevels = new List<CategoryLevel>();
                allLevels.Add(new CategoryLevel
                {
                    CategoryLevelId = 1,
                    CategoryId = 1,
                    LevelName = "Level1"
                });
                allLevels.Add(new CategoryLevel
                {
                    CategoryLevelId = 2,
                    CategoryId = 2,
                    LevelName = "Level2"
                });
                List<Category> allCategories = new List<Category>();
                allCategories.Add(new Category
                {
                    CategoryId = 1
                });
                allCategories.Add(new Category
                {
                    CategoryId = 2
                });
                foreach (var category in allCategories)
                {
                    category.CategoryLevels = allLevels.Where(l => l.CategoryId == category.CategoryId); //为什么如果这里加ToList() 后就不会失败
                }
                var checkedLevels = allCategories.Where(c => c.CategoryId == 1).First().CategoryLevels;
                Debug.Assert(checkedLevels.Where(l => l.CategoryLevelId == 2).Count() == 0); //断言失败, 为什么???
            }
        }
        internal class Category
        {
            public int CategoryId { get; set; }
            public IEnumerable<CategoryLevel> CategoryLevels { get; set; }
        }

        internal class CategoryLevel
        {
            public int CategoryLevelId { get; set; }
            public int CategoryId { get; set; }
            public string LevelName { get; set; }
        }
    }

    • 已移动 mldark 2010年4月1日 7:51 (发件人:Visual C#)
    2010年3月31日 13:49

答案

  • 这是一个延迟执行(Deferred excution)的问题。当调用where时,实际得到的不是集合,而是运算表达式,直到ToList时,他才成为对应的集合。在你的循环中,运算式中的变量值发生了变化(category),等同于如下代码,所以 levellist1 levellist2在执行时运算了相同的结果。

                var cate1 = allCategories[0];
                var level1 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                cate1 = allCategories[1];
                var level2 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist1 = level1.ToList();
                var levellist2 = level2.ToList();

    你改成这样,结果就对了


                var cate1 = allCategories[0];
                var level1 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist1 = level1.ToList();

                cate1 = allCategories[1];
                var level2 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist2 = level2.ToList();

    其实,也就对应于你把 ToList() 放到循环里。所以,在使用deferred excution时,小心中间变量变化的副作用。


    Mog Liang
    • 已标记为答案 Mog Liang 2010年4月7日 8:37
    2010年4月6日 10:21

全部回复

  • 因为 checkedLevels.Where(l => l.CategoryLevelId == 2).Count() 的结果是"1"

    断言1 == 0 当然是失败的


    起云
    2010年4月6日 7:49
  • 我知道 checkedLevels.Where(l => l.CategoryLevelId == 2).Count()的结果是"1", 我想知道为什么加了ToList以后得到的结果是0, 而不加得到的结果是1.

    2010年4月6日 7:59
  • 这是一个延迟执行(Deferred excution)的问题。当调用where时,实际得到的不是集合,而是运算表达式,直到ToList时,他才成为对应的集合。在你的循环中,运算式中的变量值发生了变化(category),等同于如下代码,所以 levellist1 levellist2在执行时运算了相同的结果。

                var cate1 = allCategories[0];
                var level1 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                cate1 = allCategories[1];
                var level2 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist1 = level1.ToList();
                var levellist2 = level2.ToList();

    你改成这样,结果就对了


                var cate1 = allCategories[0];
                var level1 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist1 = level1.ToList();

                cate1 = allCategories[1];
                var level2 = allLevels.Where(l => l.CategoryId == cate1.CategoryId);

                var levellist2 = level2.ToList();

    其实,也就对应于你把 ToList() 放到循环里。所以,在使用deferred excution时,小心中间变量变化的副作用。


    Mog Liang
    • 已标记为答案 Mog Liang 2010年4月7日 8:37
    2010年4月6日 10:21