Является ли порядок исполнения Linq причиной этого улова?

1

У меня есть эта функция, чтобы повторить последовательность:

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 не выполняет операцию выбора сначала, а затем конкатенацию (конечно, во время выполнения)?

Теги:
linq

3 ответа

4
Лучший ответ

Проблема заключается в том, что если вы не вызываете 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();
}
  • 0
    Хорошо, это единственное возможное объяснение этому, о котором я думал. Но, в конце концов, вы не думаете, что это ошибка! Я чувствую себя так :)
  • 0
    @nawfal Нет, это определенно особенность. LINQ позволяет вам контролировать перечисление ваших последовательностей, а не захватывать этот контроль для себя. С точки зрения памяти существует «материализация» последовательности; LINQ позволяет вам решить заплатить или использовать чуть больше процессорных циклов.
Показать ещё 6 комментариев
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, чтобы исправить эту проблему.

0

Каждый объект Person является отдельным объектом. Все 1000 отличаются.

Каково определение равенства для типа Person? Если вы не отмените его, это определение будет ссылочным равенством, что означает, что все 1000 объектов различны.

  • 0
    Смотрите мое редактирование. Я это понимаю. Мой вопрос, почему это происходит. Это определенно связано с исполнением вещи в Linq
  • 0
    Ссылочное равенство - это то, что проверяет функция, так как я ничего не переопределил. Но не странно ли, что Linq получает это как 1000 различных объектов? : о

Ещё вопросы

Сообщество Overcoder
Наверх
Меню