Итерация foreach () выполняется по ссылке? - PullRequest
63 голосов
/ 08 октября 2009

Учтите это:

List<MyClass> obj_list = get_the_list();
foreach( MyClass obj in obj_list )
{
    obj.property = 42;
}

Является ли 'obj' ссылкой на соответствующий объект в списке, чтобы при изменении свойства это изменение сохранялось в экземпляре объекта, когда-то созданном?

Ответы [ 10 ]

60 голосов
/ 08 октября 2009

Да, obj - это ссылка на текущий объект в коллекции (при условии, что MyClass на самом деле является классом). Если вы изменяете какие-либо свойства с помощью ссылки, вы меняете объект, как и следовало ожидать.

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

Состояния спецификации языка C # (8.8.4)

"Переменная итерации соответствует доступная только для чтения локальная переменная с объем, который распространяется на встроенный заявление. "

21 голосов
/ 03 декабря 2012

Да, пока вы не измените универсальный тип с List на IEnumerable ..

19 голосов
/ 08 октября 2009

Вы задали 2 разных вопроса, давайте рассмотрим их по порядку.

Повторяется ли цикл foreach по ссылке?

Если вы имеете в виду в том же смысле, что и цикл C ++ для ссылки, то нет. C # не имеет ссылок на локальные переменные в том же смысле, что и C ++, и, следовательно, не поддерживает этот тип итерации.

Изменения будут сохранены

Предполагая, что MyClass является ссылочным типом, ответ - да. Класс является ссылочным типом в .Net, и, следовательно, переменная итерации является ссылкой на одну переменную, а не копией. Это не будет верно для типа значения.

15 голосов
/ 05 сентября 2011

Что ж, со мной случилось, что мои изменения не были обновлены в цикле foreach, когда я перебрал коллекцию var:

var players = this.GetAllPlayers();
foreach (Player player in players)
{
    player.Position = 1;
}

Когда я изменил var на List, он начал работать.

6 голосов
/ 28 октября 2015

В этом случае вы можете (используя List<T>), но если вы будете перебирать универсальный IEnumerable<T>, то это становится зависимым от его реализации.

Если бы это был, например, List<T> или T[], все работало бы так, как ожидалось. Большая ошибка возникает, когда вы работаете с IEnumerable<T>, который был построен с использованием yield. В этом случае вы больше не можете изменять свойства T внутри итерации и ожидать, что они будут присутствовать, если вы повторите итерацию того же IEnumerable<T>.

3 голосов
/ 08 июня 2010

это верно, если это не структура.

2 голосов
/ 03 сентября 2018

Может быть, вам интересно полагать, что в версии C # 7.3 можно изменять значения по ссылке, при условии, что свойство Current перечислителя возвращает ссылочный тип. Следующее будет действительным (дословная копия из документов MS):

Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
    item = num++;
}

Подробнее об этой новой функции читайте на C # foreach Statement | Документы Microsoft .

2 голосов
/ 08 октября 2009

Что ж, без точного понимания того, что вы подразумеваете под «итерацией по ссылке», я не могу ответить точно, да или нет, но могу сказать, что под поверхностью происходит то, что .net Framework создает «перечислитель» «класс для каждого раза, когда клиентский код вызывает foreach, для жизни foreach, который поддерживает ссылочный указатель в итерируемую коллекцию, и каждый раз, когда ваш foreach повторяется, ir« доставляет »один элемент и« увеличивает »указатель ссылка в перечислителе на следующий элемент ...

Это происходит независимо от того, являются ли элементы в коллекции, для которой вы выполняете итерацию, типами значений или ссылочными типами.

0 голосов
/ 08 октября 2009

Да, именно поэтому вы не можете изменить перечисляемый объект в контексте оператора foreach.

0 голосов
/ 08 октября 2009

obj - это ссылка на элемент в списке, поэтому, если вы измените его значение, оно сохранится. Теперь вас должно беспокоить, является ли get_the_list (); делает глубокую копию списка или возвращает тот же экземпляр.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...