【问题标题】:surprising linq except behavior令人惊讶的 linq 除了行为
【发布时间】:2019-08-20 14:18:12
【问题描述】:

除了行为之外,我对以下 linq 感到非常惊讶,谁能解释为什么?我列出了我对 linq 如何工作的理解/假设。其中至少有一个是错误的。

  1. 第一行定义了 list1,它在计算时会产生 Obj('a') 和 Obj('b')。
  2. 第二行定义了产生 Obj('b') 的 list2,它应该与从 list1 产生的对象(引用相等)相同。
  3. 第三行定义了产生 Obj('a') 的 list3,它应该是从 list1 产生的相同(引用相等)对象。
public class Obj {
    public string Name;
    public Obj(string name)
    {
        this.Name = name;
    }
}

class Program
{
    public static void Main(string[] args)
    {
        var list1 = "a,b"
            .Split(',')
            .Select(x => new Obj(x));
        var list2 = list1.Where(x => x.Name == "b");
        var list3 = list1.Except(list2).ToList();
    }
}  

但显然,情况并非如此。在调试器中检查时,list3 包含 {Obj('a'), Obj('b')},并且这些对象不是 list1 包含的引用。并且 Obj 构造函数被调用了 4 次。

不应该 linq Where 和 Excet 方法只是将对象引用从一个 IEnumerable 复制到另一个 IEnumerable?谁在创建对象副本?

【问题讨论】:

  • 这种行为对我来说似乎是正确的。请记住,LINQ 实际上是一种视图,并且在您要求之前不会实现任何东西。
  • @John 这让我很困惑。如果它们只是视图,则 list1、ilst2 和 list3 应该只是对相同 2 个对象的不同视图。但是在调试时,一共创建了 4 个对象。
  • 它们都是"a,b".Split(',')产生的数组的视图,而不是list1的视图。
  • 我打算将其纳入答案,但乔恩击败了我。这是一个example - 它表明在实现结果之前实际上没有任何评估。还有another example,我们在做其他事情之前实现list1
  • @John 谢谢你们。这不是他第一次帮助我。你纠正了我深信自己理解的误解之一。

标签: c# linq


【解决方案1】:

问题在于您的列表并不是真正的列表 - 它们是延迟评估的序列。当这段代码执行时:

var list1 = "a,b"
    .Split(',')
    .Select(x => new Obj(x));

... 立即调用Split,然后调用Where 以在该数组上建立一个惰性求值序列。如果您根本不迭代list1,则不会创建Obj 的实例。如果你多次迭代list1,你每次都会得到新的对象。

要让代码正常工作,您需要做的就是实现查询,方法是转换为列表(或者数组也可以):

var list1 = "a,b"
    .Split(',')
    .Select(x => new Obj(x))
    .ToList();

或者,您可以在Obj 中覆盖EqualsGetHashCode,以便Except 可以适当地考虑不同但相等的对象。

【讨论】:

    【解决方案2】:

    如果你使用 foreach ,它会调用 IEnumerator.MoveNext() 并且它会新的对象

    public class Obj
        {
            public string Name;
            public Obj(string name)
            {
                Debug.LogFormat("HI");
                this.Name = name;
            }
        }
            var list1 = "a,b"
                   .Split(',')
                   .Select(x => new Obj(x));
            foreach (var v in list1)
            { }
    

    输出:嗨嗨

    如果你运行两个 foreach ,它将 Inovke Double IEnumerator.MoveNext()

    foreach (var v in list1)
                { }
    foreach (var v in list1)
                { }
    

    输出:HI HI HI HI

    所以和 use except & toList 一样,也和 foreach list1 一样

    var list2 = list1.Where(x => x.Name == "b");
    var list3 = list1.Except(list2);// output HI HI
    list3.ToList();// output HIHI
    

    【讨论】:

      猜你喜欢
      • 2019-10-05
      • 1970-01-01
      • 2021-08-20
      • 1970-01-01
      • 1970-01-01
      • 2012-07-27
      • 2015-10-19
      • 1970-01-01
      • 2012-08-11
      相关资源
      最近更新 更多