令人惊讶的linq,除了行为

Xia*_* Ge 2 c# linq

除了行为,我对以下的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();
    }
}  
Run Code Online (Sandbox Code Playgroud)

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

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

Jon*_*eet 7

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

var list1 = "a,b"
    .Split(',')
    .Select(x => new Obj(x));
Run Code Online (Sandbox Code Playgroud)

... Split立即被调用,然后Where被调用以在该数组上建立延迟评估的序列。如果您根本不进行迭代list1Obj则不会创建的实例。如果迭代list1多次,则每次都会获得新对象。

所有你需要做的就是你的代码工作是兑现通过转换为一个列表(或阵列将工作太)查询:

var list1 = "a,b"
    .Split(',')
    .Select(x => new Obj(x))
    .ToList();
Run Code Online (Sandbox Code Playgroud)

另外,您可以覆盖EqualsGetHashCode中的Obj,这样就Except可以适当地考虑不同但相等的对象。