我有这个功能重复一个序列:
public static List<T> Repeat<T>(this IEnumerable<T> lst, int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException("count");
var ret = Enumerable.Empty<T>();
for (var i = 0; i < count; i++)
ret = ret.Concat(lst);
return ret.ToList();
}
现在,如果我这样做:
var d = Enumerable.Range(1, 100);
var f = d.Select(t => new Person()).Repeat(10);
int i = f.Distinct().Count();
我希望i
为100,但它给我1000!我的问题严格来说是为什么会发生这种情况? Linq难道不应该足够聪明地弄清楚它是我需要与变量ret
连接的第一个选定的100个人吗?我感觉Concat
在Select
执行时ret.ToList()
与var f = d.Select(t => new Person()).ToList().Repeat(10);
int i = f.Distinct().Count(); //prints 100
一起使用时,会优先考虑Equals
。
修改
如果我这样做,我会按预期得到正确的结果:
{{1}}
再次修改:
我没有覆盖{{1}}。我只想尝试获得100个独特的人(当然可以参考)。我的问题是有人可以向我解释为什么Linq没有先进行选择操作然后连接(当然在执行时)?
答案 0 :(得分:4)
问题在于,除非您致电ToList
,否则每次d.Select(t => new Person())
通过列表时都会重新枚举Repeat
,从而创建重复的Person
。该技术称为deferred execution。
通常,LINQ
并不假设每次枚举序列时它都会得到相同的序列,甚至是相同长度的序列。如果不希望出现这种影响,您可以随时通过立即调用Repeat
来“实现”ToList
方法中的序列,如下所示:
public static List<T> Repeat<T>(this IEnumerable<T> lstEnum, int count) {
if (count < 0)
throw new ArgumentOutOfRangeException("count");
var lst = lstEnum.ToList(); // Enumerate only once
var ret = Enumerable.Empty<T>();
for (var i = 0; i < count; i++)
ret = ret.Concat(lst);
return ret.ToList();
}
答案 1 :(得分:1)
我可以将我的问题分解为不那么简单的事情:
var d = Enumerable.Range(1, 100);
var f = d.Select(t => new Person());
现在基本上我正在这样做:
f = f.Concat(f);
请注意,查询至今尚未执行。在执行时,f
仍然是d.Select(t => new Person())
未执行。因此,执行时的最后一条陈述可以分解为:
f = f.Concat(f);
//which is
f = d.Select(t => new Person()).Concat(d.Select(t => new Person()));
显然可以创建100 + 100 = 200个新的人员实例。所以
f.Distinct().ToList(); //yields 200, not 100
这是正确的行为。
编辑:我可以重写扩展方法,就像
一样简单public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, int times)
{
source = source.ToArray();
return Enumerable.Range(0, times).SelectMany(_ => source);
}
我使用了dasblinkenlight的建议来解决问题。
答案 2 :(得分:0)
每个Person
对象都是一个单独的对象。所有1000都是截然不同的。
Person
类型的相等定义是什么?如果不覆盖它,那么该定义将是引用相等,这意味着所有1000个对象都是不同的。