У меня есть эта функция, чтобы повторить последовательность:
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 не должен быть достаточно умным, чтобы понять, что он первым выбрал 100 человек, которые мне нужно объединить с переменной ret
? У меня возникает ощущение, что здесь Concat
предоставляется предпочтение, когда оно используется с Select
, когда оно выполняется в ret.ToList()
..
Edit:
Если я сделаю это, я получаю правильный результат, как ожидалось:
var f = d.Select(t => new Person()).ToList().Repeat(10);
int i = f.Distinct().Count(); //prints 100
Изменить еще раз:
Я не переопределил Equals
. Я просто пытаюсь получить 100 уникальных людей (по ссылке, конечно). Мой вопрос: может кто-нибудь объяснить мне, почему Linq не выполняет операцию выбора сначала, а затем конкатенацию (конечно, во время выполнения)?
Проблема заключается в том, что если вы не вызываете ToList
, d.Select(t => new Person())
переучитывается каждый раз, когда Repeat
проходит через список, создавая дубликаты Person
s. Этот метод известен как отсроченное исполнение.
В общем случае 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();
}
Я мог бы разбить мою проблему на нечто менее тривиальное:
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, чтобы исправить эту проблему.
Каждый объект Person
является отдельным объектом. Все 1000 отличаются.
Каково определение равенства для типа Person
? Если вы не отмените его, это определение будет ссылочным равенством, что означает, что все 1000 объектов различны.