Aydin Homay Ответов: 1

Метод Removerange после использования метода take из списка<T> имеет неожиданное поведение


Кто-нибудь знает, почему длина после выхода из тела цикла while в приведенном ниже коде равна нулю?

static void Main(string[] args)
		{

			var list = new List<int>();
			for (int i = 0; i < 1000; i++)
			{
				list.Add(i);
			}

			var chunks = new List<IEnumerable<int>>();

			while (list.Count > 0)
			{
				chunks.Add(list.Take(10));
				list.RemoveRange(0, 10);
			}

			int length = chunks.ToList()[0].Count();
		}


Что я уже пробовал:

Я попытался разбить список на определенные сегменты.

Aydin Homay

Пожалуйста, не предоставляйте мне алгоритм чанкования, я уже знаю несколько таких алгоритмов и знаю, как решить эту проблему. Мой вопрос касается только проводного поведения RemoverRange или, возможно, Take.

1 Ответов

Рейтинг:
4

OriginalGriff

Linq - это не магия, он работает с отложенным исполнением.
Если вы используете отладчик и ставите точку останова в конце цикла while, то при первом попадании в него вы смотрите на куски и у него есть один член - расширение представления результатов дает вам не то, что вы ожидали: 10, 11, .. 18, 19 вместо 0, 1, ... 8, 9
Запустите цикл снова, и он получит еще меньше того, что вы хотели: два элемента (правильно), но разверните любой из них, и они оба одинаковы: 20, 21, 22, ... 28, 29
Почему? Потому что выполнение Take откладывается до тех пор, пока ваш код не попытается использовать результаты, и к тому времени исходная коллекция будет пуста.
Таким образом, каждая оценка, которую вы пытаетесь получить, length дает "перечисление не дало никаких результатов" и длину нуля.

Вы можете сделать это и получить желаемый результат:

chunks.Add(list.Take(10).ToList());
Потому что Толист оценивает дубль сразу.

[редактировать] Опечатки.
И вот что: вы понимаете, что на самом деле делает RemoveRange? И что вы используете его наименее эффективным способом? Взгляните на это: List<T> - действительно ли это так эффективно, как вы, вероятно, думаете?[^] и вы увидите, почему.
[/редактировать]


Aydin Homay

Насчет использования RemoveRange да, вы правы, но каково ваше предложение, если такой список важен, чтобы оставаться таким, как он есть. В любом случае у вас будет стоимость коррекции указателя.

OriginalGriff

- В любом случае вам придется заплатить за коррекцию указателя."
Списки не используют указатели...

Aydin Homay

Я думаю, что в стороне списка находится массив, а массив имеет дело с указателями.

OriginalGriff

Перейдите по ссылке - она объясняет, почему то, что вы делаете, ужасно неэффективно, независимо от использования указателя в сгенерированном IL - фактический код указателя для доступа к массиву-это две очень короткие строки кода!

Aydin Homay

Я знаю, о чем вы говорите по этой ссылке. Но мой вопрос в том, что вы предлагаете? Когда нет никаких проблем с памятью, поскольку я не разрабатываю для встроенной системы (даже C# .Net не предполагается для такого использования), и у меня есть достаточно оборудования, чтобы справиться с относительно тяжелыми операциями. Мне просто нужно сохранить порядок списка таким, как он есть, и обработать остальную функциональность.