То, что я показываю ниже, это скорее теоретический вопрос. Но меня интересует, как новый компилятор С# 7 работает и разрешает локальные функции.
В С# 7 я могу использовать локальные функции. Например (вы можете попробовать эти примеры в бета-версии LinqPad):
Пример 1: Вложенный Main()
void Main()
{
void Main()
{
Console.WriteLine("Hello!");
}
Main();
}
Вместо того, чтобы вызывать Main()
рекурсивным способом, локальная функция Main()
вызывается один раз, поэтому результат этого:
Здравствуйте!
Компилятор принимает это без предупреждений и ошибок.
Пример 2: Здесь я иду на один уровень глубже, например:
В этом случае я бы также ожидал того же результата, потому что вызывается самая внутренняя локальная функция, затем на один уровень выше, Main()
- это просто еще одна локальная функция с локальной областью действия, поэтому она не должна сильно отличаться от первого примера.
Но здесь, к моему удивлению, я получаю ошибку:
CS0136 Локальный или параметр с именем "Main" не может быть объявлен в этой области, поскольку это имя используется во внешней локальной области для определения локального или параметра
Вопрос: Можете ли вы объяснить, почему эта ошибка происходит в Примере 2, но не в Примере 1?
Я думал, что каждый внутренний Main()
будет иметь локальную область видимости и будет скрыт снаружи.
Обновление: спасибо всем, кто до сих пор вносил дополнения (ответы или комментарии), очень важно, что вы написали, чтобы понять поведение компилятора С#.
Из того, что я прочитал, и после рассмотрения возможностей, я выяснил с вашей помощью, что это может быть либо ошибка компилятора, либо поведение по своему замыслу.
Напомним, что у С# были некоторые цели разработки, которые отличают его от языков, подобных C++.
Если вам интересно, что я сделал для дальнейшего исследования: я переименовал внутреннюю функцию в MainL
например:
Пример 2б:
void Main()
{
void Main()
{
void MainL()
{
Console.WriteLine("Hello!");
}
MainL();
}
Main();
}
Этот модифицированный пример компилируется и запускается успешно.
Теперь, когда вы скомпилируете это с помощью LinqPad, а затем переключитесь на вкладку IL, вы увидите, что сделал компилятор:
Он создал внутреннюю функцию MainL
как g__MainL0_1
, MainL
функция Main
имеет метку g__Main0_0
.
Это означает, что если вы удалите L
из MainL
вы заметите, что компилятор уже переименовывает его уникальным способом, потому что тогда код выглядит так:
IL_0000: call UserQuery.<Main>g__Main0_0
IL_0005: ret
<Main>g__Main0_0:
IL_0000: call UserQuery.<Main>g__Main0_1
IL_0005: ret
<Main>g__Main0_1:
IL_0000: ldstr "Hello!"
IL_0005: call System.Console.WriteLine
IL_000A: ret
который по-прежнему решает правильно. Поскольку в примере 2 код выглядит не так, поскольку компилятор останавливается с ошибкой, я теперь предполагаю, что поведение является заданным, это, скорее всего, ошибка компилятора.
Вывод: некоторые из вас писали, что в C++ рекурсивное разрешение локальных функций может привести к проблемам с рефакторингом, а другие писали, что этот тип поведения в С# - это то, что компилятор делает с локальными переменными (обратите внимание, что сообщение об ошибке совпадает) - все это даже подтверждает, что я думал, что это было сделано по замыслу и не является ошибкой.