除了行为,我对以下的linq感到非常惊讶,有人能解释为什么吗?我已经列出了我对linq的工作方式的理解/假设。至少其中之一是错误的。
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?是谁在创建对象副本?
答案 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
中的GetHashCode
和Obj
,以便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