Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

Где найти старый компилятор для C# 4.0

821  1 2 3 все
AlexNek патриот15.03.19 14:53
AlexNek
NEW 15.03.19 14:53 
Последний раз изменено 15.03.19 14:59 (AlexNek)

где найти старый компилятор для C# 4.0 желательно онлайн. Просто интересно для проверки closure. В старом по идее не должно быть автовключения строки 13

https://paiza.io/projects/e/MSCBn5zG7GL-FfyOJquZMg?theme=t...

Все что перепробовал добавляют и получается "правильный" вывод.

#1 
MrSanders старожил15.03.19 16:37
15.03.19 16:37 
в ответ AlexNek 15.03.19 14:53

Исключительно для расширения кругозора - а почему присваивание
int tmp = i;

сделает код "правильным"?

#2 
AlexNek патриот15.03.19 16:42
AlexNek
NEW 15.03.19 16:42 
в ответ MrSanders 15.03.19 16:37, Последний раз изменено 15.03.19 16:44 (AlexNek)

здесь

https://habr.com/en/post/141270/

вроде понятно описано, но в IL не вижу этого. Тоже хочу разобраться.


ну и присваивание немного другое должно быть. Я просто мои тесты не убрал полностью что бы идея был понятна.

#3 
AlexNek патриот15.03.19 16:51
AlexNek
NEW 15.03.19 16:51 
в ответ MrSanders 15.03.19 16:37

вот что генерится

List<Action> actionList = new List<Action>();
foreach (int num in Enumerable.Range(0, 10))
{
        int i = num;
        actionList.Add((Action) (() => Console.WriteLine(i)));
}
foreach (Action action in actionList)
        action();
#4 
Murr патриот15.03.19 17:23
Murr
NEW 15.03.19 17:23 
в ответ AlexNek 15.03.19 14:53
где найти старый компилятор для C# 4.0 желательно онлайн.

-----

С какой версией студии он шел? У меня вроде 2005 самая старая из проинсталленых... остальное надо рыть в дистрибутивах.

#5 
AlexNek патриот15.03.19 17:43
AlexNek
NEW 15.03.19 17:43 
в ответ Murr 15.03.19 17:23, Последний раз изменено 15.03.19 17:47 (AlexNek)

до 2010 включительно

https://www.tutorialsteacher.com/csharp/csharp-version-his...

можешь код компильнуть что по ссылке на 2005 или 2010, что будет в результате? Если 10,10,... то интересно экзешник поиметь и IL глянуть. А то новые декомпиляторы всю картину "портят"

Только это совершенно неважно, если есть какие то проблемы то не нужно.

#6 
AlexNek патриот15.03.19 19:04
AlexNek
NEW 15.03.19 19:04 
в ответ MrSanders 15.03.19 16:37

Вроде кажется дошло, поправьте если неправильно.

Для использования локальных переменных в лямбде/анонимной функции делается как бы снимок всех требуемых переменных в специальном 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);
}
#7 
dymanoid местный житель15.03.19 19:21
dymanoid
NEW 15.03.19 19:21 
в ответ AlexNek 15.03.19 14:53

Не надо его нигде искать. Он на компе есть уже - у каждого.

Открываем 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.

#8 
AlexNek патриот15.03.19 20:03
AlexNek
NEW 15.03.19 20:03 
в ответ dymanoid 15.03.19 19:21

Да иногда думать *нихами полезно. Спасибки

Действительно, не обманули, для 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

#9 
MrSanders старожил15.03.19 20:24
NEW 15.03.19 20:24 
в ответ AlexNek 15.03.19 19:04

Эт феерия какая-то. Т.е. они делали closure (замыкание) по значениям, не ссылкам, но "соптимизировали" его, типа а чо, не меняется же ничего, переменные всё те же, можно новый не создавать. Здорово, память сэкономили, но блин, ведь значения поменялись, а closure у них по значениям, а?

Я так понял что в 5-м микрософт поправил поведение в циклах. А что он сейчас скомпилирует если написать (без цикла)

int i = 1;
actions.Add(() => Console.WriteLine(i));
i = 2;
actions.Add(() => Console.WriteLine(i));

Опять один closure (по значениям!) в котором будет только последняя 2-ка висеть и при отработке Action-ов выдаст нам "2 2"?

#10 
dymanoid местный житель15.03.19 20:38
dymanoid
NEW 15.03.19 20:38 
в ответ MrSanders 15.03.19 20:24, Последний раз изменено 15.03.19 20:39 (dymanoid)

Замыкание в C# делается всегда на переменную, а не на значение.


Прикол был только в версии C# до 4.0 включительно и только с циклом foreach. Связано это с тем, как цикл foreach был специфицирован - во что он развёртывался. Там при развёртывании переменная цикла foreach была задекланирована вовне цикла while. С замыканиями это вообще никак не связано было, просто это проявилось после введения замыканий. В 5 версии изменили спецификацию foreach, а не замыканий.


Не нужно устраивать истерику, не разбираясь в материи.

#11 
  moose старожил15.03.19 20:50
NEW 15.03.19 20:50 
в ответ AlexNek 15.03.19 14:53
Просто интересно для проверки closure.


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


#12 
AlexNek патриот15.03.19 20:58
AlexNek
NEW 15.03.19 20:58 
в ответ MrSanders 15.03.19 20:24

Да результат 2 2

https://paiza.io/projects/62VDITV6xh1IxfbdmnGF2g?language=...

Нефиг подобные лямбды как action пользовать

#13 
MrSanders старожил15.03.19 21:10
NEW 15.03.19 21:10 
в ответ dymanoid 15.03.19 20:38, Последний раз изменено 15.03.19 21:12 (MrSanders)

Давайте всё же как-то общепонятную терминологию использовать. "делается всегда на переменную, а не на значение." это что? Вы хотите сказать что в шарпе closure (не люблю термин "замыкание") хранит не значения, а ссылки на "захваченые" переменные (free variables, определенные снаружи функции), так?

#14 
dymanoid местный житель15.03.19 21:24
dymanoid
NEW 15.03.19 21:24 
в ответ MrSanders 15.03.19 21:10, Последний раз изменено 15.03.19 21:26 (dymanoid)

Я хочу сказать, что в C# замыкание делается на переменную, а не на значение этой переменной в момент создания замыкания.

Так понятнее?

Техническая реализация (ссылка, не ссылка) - это другой вопрос. Для ссылочных типов там, понятно, ссылка хранится. Для значимых типов - значение.


Вот этот пример как раз отлично это иллюстрирует:


int i = 1;
actions.Add(() => Console.WriteLine(i));
i = 2;
actions.Add(() => Console.WriteLine(i));

// 2
// 2
#15 
dymanoid местный житель15.03.19 21:33
dymanoid
NEW 15.03.19 21:33 
в ответ moose 15.03.19 20:50
даже не стрелять - вешать нужно на площадях тех, кто подобной херней балуется, думая, что он при этом умнеет. он болванеет, этого не замечая.

Не согласен. Детальное понимание орудия труда (фич языка программирования) приводит в порядок ум, позволяет решать задачи эффективно и поднимает человека с уровня джуниор-миддл до уровня сениор.

#16 
AlexNek патриот15.03.19 21:35
AlexNek
NEW 15.03.19 21:35 
в ответ moose 15.03.19 20:50
кто подобной херней балуется

А отчего именно для баловства? Никогда не пользовал, не собираюсь и другим постараюсь не дать. Но что бы объяснить, почему это плохо нужно вначале разобраться. А то раз прочел и забыл.

#17 
MrSanders старожил15.03.19 21:35
NEW 15.03.19 21:35 
в ответ AlexNek 15.03.19 20:58, Последний раз изменено 15.03.19 21:54 (MrSanders)

Да, в ставке мелкомягких все малохольные.

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).

#18 
MrSanders старожил15.03.19 21:51
NEW 15.03.19 21:51 
в ответ dymanoid 15.03.19 21:24
Я хочу сказать, что в 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 (замыкание со ссылками).

#19 
AlexNek патриот15.03.19 23:35
AlexNek
NEW 15.03.19 23:35 
в ответ MrSanders 15.03.19 21:51
Тут или шарп...

В результате эксперимента+ILSPY получилось следующее

  • Closure создается для каждой функции.
  • Action создается для каждой лямбды
  • переменные создаются которые нужны
  • перед созданием делегата вызывается Closure.j = 20; - это я просто добавил для теста

IL_0042: ldc.i4.s 20 // 0x14

IL_0044: stfld int32 ClosureTest.Program/'<>c__DisplayClass0_0'::j

#20 
1 2 3 все