Где найти старый компилятор для C# 4.0
где найти старый компилятор для C# 4.0 желательно онлайн. Просто интересно для проверки closure. В старом по идее не должно быть автовключения строки 13
https://paiza.io/projects/e/MSCBn5zG7GL-FfyOJquZMg?theme=t...
Все что перепробовал добавляют и получается "правильный" вывод.
здесь
https://habr.com/en/post/141270/
вроде понятно описано, но в IL не вижу этого. Тоже хочу разобраться.
ну и присваивание немного другое должно быть. Я просто мои тесты не убрал полностью что бы идея был понятна.
до 2010 включительно
https://www.tutorialsteacher.com/csharp/csharp-version-his...
можешь код компильнуть что по ссылке на 2005 или 2010, что будет в результате? Если 10,10,... то интересно экзешник поиметь и IL глянуть. А то новые декомпиляторы всю картину "портят"
Только это совершенно неважно, если есть какие то проблемы то не нужно.
Вроде кажется дошло, поправьте если неправильно.
Для использования локальных переменных в лямбде/анонимной функции делается как бы снимок всех требуемых переменных в специальном closure class который решили назвать DisplayClass
https://stackoverflow.com/questions/16401860/what-does-dis...
Если внутрь цикла вставить ненужный оператор присваивания, то компилятор создает closure class каждый раз внутри цикла и делает снимок. Без этого оператора closure class создается вне цикла и также делается снимок внутри цикла. То бишь имеем в одном случае 1 экземпляр с 10 присваиваниями в цикле , а в другом случае 10 экземпляров с одним присваиванием. closure class создается в месте "минимальной видимости" переменных.
for (int i = 0; i < 10; i++) { int tmp = i; Action action = delegate() { Console.WriteLine(tmp); }; actions.Add(action); }
Не надо его нигде искать. Он на компе есть уже - у каждого.
Открываем cmd.exe и пишем:
C:\Windows\Microsoft.NET\Framework\v3.5\csc /out:C:\v4.exe с:\path_to\Program.cs
А потом пишем:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc /out:C:\v5.exe с:\path_to\Program.cs
Затем открываем любым декомпилятором v4.exe и v5.exe и сравниваем - хоть C#, хоть IL.
Да иногда думать *нихами полезно. Спасибки
Действительно, не обманули, для 4-ки foreach работает "неправильно".
Зато какие интересные побочные эффекты можно получить
var actions = new List<Action>(); int j = 0; int i = 0; for (; i < 10; i++) { //int tmp = i; Action action = delegate() { Console.WriteLine(String.Format("{0} - {1}",i, j++)); }; actions.Add(action); } int k = 0; foreach (var action in actions) { if (k % 2 == 0) { action(); } k++; }
10 - 0
10 - 1
10 - 2
10 - 3
10 - 4
Эт феерия какая-то. Т.е. они делали closure (замыкание) по значениям, не ссылкам, но "соптимизировали" его, типа а чо, не меняется же ничего, переменные всё те же, можно новый не создавать. Здорово, память сэкономили, но блин, ведь значения поменялись, а closure у них по значениям, а?
Я так понял что в 5-м микрософт поправил поведение в циклах. А что он сейчас скомпилирует если написать (без цикла)
int i = 1; actions.Add(() => Console.WriteLine(i)); i = 2; actions.Add(() => Console.WriteLine(i));
Опять один closure (по значениям!) в котором будет только последняя 2-ка висеть и при отработке Action-ов выдаст нам "2 2"?
Замыкание в C# делается всегда на переменную, а не на значение.
Прикол был только в версии C# до 4.0 включительно и только с циклом foreach. Связано это с тем, как цикл foreach был специфицирован - во что он развёртывался. Там при развёртывании переменная цикла foreach была задекланирована вовне цикла while. С замыканиями это вообще никак не связано было, просто это проявилось после введения замыканий. В 5 версии изменили спецификацию foreach, а не замыканий.
Не нужно устраивать истерику, не разбираясь в материи.
Да результат 2 2
https://paiza.io/projects/62VDITV6xh1IxfbdmnGF2g?language=...
Нефиг подобные лямбды как action пользовать
Давайте всё же как-то общепонятную терминологию использовать. "делается всегда на переменную, а не на значение." это что? Вы хотите сказать что в шарпе closure (не люблю термин "замыкание") хранит не значения, а ссылки на "захваченые" переменные (free variables, определенные снаружи функции), так?
Я хочу сказать, что в C# замыкание делается на переменную, а не на значение этой переменной в момент создания замыкания.
Так понятнее?
Техническая реализация (ссылка, не ссылка) - это другой вопрос. Для ссылочных типов там, понятно, ссылка хранится. Для значимых типов - значение.
Вот этот пример как раз отлично это иллюстрирует:
int i = 1; actions.Add(() => Console.WriteLine(i)); i = 2; actions.Add(() => Console.WriteLine(i)); // 2 // 2
даже не стрелять - вешать нужно на площадях тех, кто подобной херней балуется, думая, что он при этом умнеет. он болванеет, этого не замечая.
Не согласен. Детальное понимание орудия труда (фич языка программирования) приводит в порядок ум, позволяет решать задачи эффективно и поднимает человека с уровня джуниор-миддл до уровня сениор.
Да, в ставке мелкомягких все малохольные.
var actions = new List<Action>(); int i = 1; actions.Add(() => Console.WriteLine(i)); i = 2; actions.Add(() => Console.WriteLine(i)); var actions2 = new List<Action>(); foreach(int j in Enumerable.Range(1,2)){ actions2.Add(() => Console.WriteLine(j)); } Console.WriteLine("1:"); foreach (var action in actions){ action();} Console.WriteLine("2:"); foreach (var action in actions2) { action();}
Радостно выдаёт (поправил, надо было копировать вывод а не перенабирать :), цикл выдаст "1 2", два вызова Add с изменением в промежутке i - "2 2")
1: 2 2 2: 1 2
От это я понимаю, исправили поведение foreach, теперь всё стало ясно и понятно. Уж лучше как в яве, запретить использовать переменные из scope, только константы (final).
Я хочу сказать, что в C# замыкание делается на переменную, а не на значение этой переменной в момент создания замыкания.
Так понятнее?
вроде бы стало понятнее, т.е. "замыкание со ссылками", т.е.
int i = 1; actions.Add(() => Console.WriteLine(i)); i = 2; // печатаем
выдаст нам не 1 а 2 (хотя не ссответствует коду Closure из статьи на хабре).
Но тут вы меня опять смутили:
Для ссылочных типов там, понятно, ссылка хранится. Для значимых типов - значение.
Это как? Ссылка, хранимая в переменной "x" ссылочного типа и есть её значение. "замыкание со ссылками" будет хранить ссылку на переменную "x" а не ссылку, хранившуюся в "x" в момент создания closure. Получается в моём примере выше, шарп должен выдать 1, ведь при создании closure он соханил в нём для значимого типа его значение "1"?
Вот этот пример как раз отлично это иллюстрирует:
Не совсем отлично. Тут или шарп создал одно-единственное closure для обеих лямбд (вторая экшен это та же первая), и при инициализации для 2-го action переписал значение i (замыкание со значениями), или он создал два closure но со ссылками на i (замыкание со ссылками).
Тут или шарп...
В результате эксперимента+ILSPY получилось следующее
- Closure создается для каждой функции.
- Action создается для каждой лямбды
- переменные создаются которые нужны
- перед созданием делегата вызывается Closure.j = 20; - это я просто добавил для теста
IL_0042: ldc.i4.s 20 // 0x14
IL_0044: stfld int32 ClosureTest.Program/'<>c__DisplayClass0_0'::j