Где найти старый компилятор для C# 4.0
Не хочу азы объяснять. Если интересно - читайте разницу между ссылочными типами и значимыми в C#.
На абстрактные философствования отвечать не буду.
foreach исправили отлично, кто знает язык, тот будет понимать. В первом случае переменная i одна и та же для обоих замыканий, поэтому "2 2" логично. Во тором случае, переменная хоть и называется одинаково, но в каждой итерации цикла она разная, поэтому "1 2". Всё прекрасно и логично.
Кто не согласен - можно идти лесом, а не выпендриваться про "мелкомягких". C# - один из самых красивых и логичных мультипарадигменных языков на сегодняшний день. В отличие от всяких жаб и плюсов.
Не хочу азы объяснять.
Не хочу или не могу? Есть небольшая разница.
Кто не согласен - можно идти лесом, а не выпендриваться про "мелкомягких".
Какая прелесть. Раз вы тут советы раздавать взялись, я вам тоже посоветую. Прежде чем высказывать сверхважное мнение следует немного разобраться в теме. Прочитать про замыкания и 2 (всего навсего) теоретические возможности захватывать переменные окружения, обычно не сложно. Если бы вы это сделали, то поняли бы в чем разница захватывать ссылки на переменные и значения переменных, а не пели бы про разницу ссылочных и значимых типов в шарпе (стек или куча - неважно). А там, глядишь, и самоуверенности с понтами поубавится.
В общем, получается что в шарпе closure со ссылками. Странное Отважное решение, я, честно говоря, не помню в каком еще языке такое есть. Оно было бы логично если бы и методам параметры по умолчанию по ссылке а не по значению передавались бы. А так в шарпе есть ref (привет функциональному программированию, блин), чего бы им при определении лямбд подобным не пользоваться? Хочешь по значению (без ref), хочешь по ссылке.
Проблема с захватом ссылок а не значений в том, что использование closure и присваивание значений переменным может быть сильно разнесено. Забудешь что у тебя closure была, наприсваиваешь чего переменным и всё, 100 строчками кода позже кто-то вызовет такую сохранённую функцию и порадуется непонятному результату. Удачи в дебаге!
У меня другой
вопрос - получается что сейчас поведение лямбд, определённых внутри for и foreach будут отличаться, for может работать с ранее определенной переменной, в foreach надо определять свою переменную цикла.
Можете посмотреть поведение 4-й версии с foreach? Сейчас в foreach нельзя присваивать переменной цикла других значений. (просто написать i = 5; внутри). А до 5-й версии (пока foreach не "исправили") тоже нельзя было, или можно?
В общем, получается что в шарпе closure со ссылками.
Что то я вас не пониманию. Где тут ссылки? closure.i = i; Для меня это как бы indirect usage
https://paiza.io/projects/62VDITV6xh1IxfbdmnGF2g?language=csharp
for может работать с ранее определенной переменной, в foreach надо определять свою переменную цикла.
как раз наоборот. Изменили поведение foreach. Closure теперь определяется не снаружи цикла, а внутри.
Сейчас в foreach нельзя присваивать переменной цикла других значений.
Сорри, это тоже что то не понимаю. Ни раньше ни сейчас в foreach нельзя самому менять переменную цикла. Иначе сам принцип изменится.
Тут вроде еще нормально описано
https://itvdn.com/ru/blog/article/closing-in-csharp
Это же надо еще до подобного использования додуматься.
Что то я вас не пониманию. Где тут ссылки? closure.i = i; Для меня это как бы indirect usage https://paiza.io/projects/62VDITV6xh1IxfbdmnGF2g?language=csharp
Хм. Где в вашем коде использование closure? То что вы свой класс так назвали ничего не значит. Вернитесь к примеру с лямбдами.
как раз наоборот. Изменили поведение foreach. Closure теперь определяется не снаружи цикла, а внутри.
Я имел в виду следующее:
int i = 10; for(;i<20;i++){...} // можно int j = 0; foreach(j in ...) {...} // нельзя // ^ - должна быть новая переменная
Сорри, это тоже что то не понимаю. Ни раньше ни сейчас в foreach нельзя самому менять переменную цикла. Иначе сам принцип изменится.
Почему это? в начале каждой итерации переменной присваивается значение из списка. Но что мешает мне в блоке foreach менять значение этой переменной? Код на яве: https://paiza.io/projects/e/W6I0zp-bwEBXCWdBekivdA
int[] values = new int[]{1,2,4,8}; for(int i : values){ i = i+1; // в шарпе нельзя System.out.println(i); }
Выдаст 2,3,5,9
Повторяю - в C# замыкания всегда происходят на переменную, а не на значение переменной в момент создания замыкания. Что тут непонятного? Если реально не понятно, то вперёд читать букварь. Спецификацию языка то есть. Техническая реализация в CLR остаётся за скобками, поэтому "ссылка/не ссылка" - вообще никакого значения тут не имеет.
Повыпендриваться "не хочу или не могу" советую перед джуниорами на работе, мне это слушать не интересно.
где в вашем коде использование closure?
Вот
var closure = new Closure();
....
closure.i = i;
var action = new Action(closure.Action);
actions.Add(action);
Можно было оставить DisplayClass, но это тоже ничего не меняет ведь компайлер "разворачивает" лямбду в подобный код.
Понятно, что вы смотрите на это с какой то другой стороны. Но пока не могу догнать с какой.
Я имел в виду следующее:
так вроде никак не получится.
15.8.4 The foreach statement
The foreach statement enumerates the elements of a collection, executing an embedded statement for each
element of the collection.
foreach-statement:
foreach ( type identifier in expression ) embedded-statement
я думал что так
foreach(int j in ...) {j=0; ...}
В С# 7.3 вроде поведение как то изменили, но еще не игрался
Почему это?
Отчего в Яве так не знаю. В шарпе foreach коппайлер "переписывает". Да и стандарт не позволяет The iteration variable corresponds to a read-only local variable https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web...
Вот если тут коммент убрать так даже компилится не будет
https://paiza.io/projects/tEMtl6riuHO1bb1LK97ejQ?language=...
Вот
var closure = new Closure();
тут вы просто создаёте объект вашего класса Closure. К замыканиям отношения не имеющим. Замыкание создаётся компилятором когда вы, например, определяете лямбду () => expression.
ведь компайлер "разворачивает" лямбду в подобный код.
Кто сказал? Автор статьи на хабре? Он не прав. Проверка:
int i = 5; actions.Add(() => Console.WriteLine(i)); // вот тут возникнет closure содержащее эту лямбду и ссылку на i i = 50; actions[0]();
Как по-вашему, что выдаст?
Вот если тут коммент убрать так даже компилится не будет
https://paiza.io/projects/tEMtl6riuHO1bb1LK97ejQ?language=...
Вот это мне и интересно, это так с самого начала было или с 5-й версии, а до нее не было "read-only local variable"? Просто интересно было как именно они поведение foreach поменяли.е
Кто сказал?
Декомпилятор, выложить IL? https://github.com/icsharpcode/ILSpy/releases
Только нужно сказать чтобы все показывать
Как по-вашему, что выдаст?
А не нужно гадать, можно просто глянуть
Просто интересно было как именно они поведение foreach поменяли
Достаточно просто, раньше это было вне цикла
var closure = new Closure();
А потом внутри
Копаться в коде, сгенерированном компилятором, конечно, очень интересно. Но не стоит объяснять реализацию конкретной фичи языка с помощью этого сгенерированного кода - он сильно зависит от версии компилятора и может запутать. Как раз это и происходит сейчас.
То есть, я бы сказал так. Шаг первый. Объясните человеку фичу языка "на пальцах". Логически - что происходит, какие условия должны выполняться и т.д. Когда фича понята, можно приступать к шагу второму: как это реализовано в "компиляторе версии такой-то". Шаг второй нужен, если есть желание досконально разобраться в материале или нужно писать high-perf код и требуется чёткое понимание, что там компилятор такого нагенерирует.
А то получается, объясняем async/await с помощью сгенерированных IAsyncStateMachine структур - тут 90% гуру и сеньоров будут долго репу чесать, чтобы понять всю эту магию. Не нужно так делать.
Изменение foreach элементарное, но сильно влияет на замыкания.
До 4.0 включительно, foreach разворачивался так:
foreach (V v in x) embedded-statement --> { E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
Начиная с 5.0, foreach разворачивается так:
foreach (V v in x) embedded-statement --> { E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
Единственная разница - область видимости переменной итерации v. Именно это и влияет на то, что происходит с замыканиями. В первом случае замыкание будет на одну переменную для всех итераций, во втором - множество замыканий на разные переменные в каждой итерации.
При этом сама переменная итерации v была и остаётся read-only во всех версиях языка. Это по спецификации, компилятор это гарантирует. Из кода выше этого не видно, потому что это реализовано не кодом, а компилятором. Если убрать эту проверку из компилятора, то технически переменную итерации можно изменять, код это позволяет.
Но не стоит объяснять реализацию конкретной фичи языка с помощью этого сгенерированного кода - он сильно зависит от версии компилятора и может запутать
безусловно, что зависит, но основной принцип, по идее остается неизменным.
Переменные контекста, все равно нужно как то передать. Переменных может быть много, значит нужен контейнер. При этом есть два шага: создание контейнера и копирование контекста.
Эти два шага и могут отличаться от версии к версии для конкретных применений. Это вы имели в виду под логикой или что то другое? Но вполне возможно, что я еще не все до конца понял.
А вот что код может запутать, это видимо, от человека зависит. Мне лично как раз удобнее код глянуть,чем многостраничные объяснения читать.
А то получается, объясняем async/await
Наверное нужно тоже тему сделать, чтобы точно до конца все добить. На Ютубе только одно приличное видео нашел пока, но все равно не думаю, что на абсолютно все вопросы смогу сразу ответить.
Это нужно пару лет их пользовать чтобы в "кровь вошло".
В локальных функциях замыкания технически реализованы по-другому. Но по спецификации - это те же самые замыкания, что и в лямбдах. Именно это я имел в виду. Для понимания концепции необязательно лезть в дебри сгенерированного кода и изучать внутренности CLR.
В локальных функциях
ааа... Я их как-то принципиально игнорирую. Пока во всяком случае. Хорошо это или плохо не-знаю.
Надо будет код глянуть...
Для понимания концепции необязательно лезть в ...
А какой есть еще способ - искать приличное описание?
В локальных функциях замыкания технически реализованы по-другому
А где есть описание, потому как у меня так получилось.
https://paiza.io/projects/OEtazpJ6NB7St4wW7HxA5Q?language=...
Большой разницы не вижу.
Да, просто для локальной функции получается по другому.
https://paiza.io/projects/IBYy3C_aqEty-9GayHOupg?language=...