意外的linq,除了行为

时间:2019-08-20 05:43:39

标签: c# linq

除了行为,我对以下的linq感到非常惊讶,有人能解释为什么吗?我已经列出了我对linq的工作方式的理解/假设。至少其中之一是错误的。

  1. 第一行定义list1,在评估时将产生Obj('a')和Obj('b')。
  2. 第二行定义的list2产生Obj('b'),它应该与从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次。

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

2 个答案:

答案 0 :(得分:7)

问题在于您的列表不是真正的列表-它们是延迟计算的序列。该代码执行时:

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();

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

答案 1 :(得分:1)

如果您使用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)
        { }

输出:HI HI

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

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

输出:HI HI HI HI

所以与使用Except&toList一样,它也像foreach list1

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