Переменные, заканчивающиеся на "1", имеют "1", удаленные в ILSpy. Зачем?
В попытке выяснить, как компилятор С# оптимизирует код, я создал простое тестовое приложение. С каждым изменением теста я скомпилировал приложение, а затем открыл двоичный файл в ILSpy.
Я только что заметил что-то, что для меня странно. Очевидно, что это намеренно, однако, я не могу придумать, почему компилятор сделал это.
Рассмотрим следующий код:
static void Main(string[] args)
{
int test_1 = 1;
int test_2 = 0;
int test_3 = 0;
if (test_1 == 1) Console.Write(1);
else if (test_2 == 1) Console.Write(1);
else if (test_3 == 1) Console.Write(2);
else Console.Write("x");
}
Беспредметный код, но я написал это, чтобы увидеть, как ILSpy интерпретирует операторы if
.
Однако, когда я скомпилировал/декомпилировал этот код, я заметил что-то, что заставило меня почесывать голову. Моя первая переменная test_1
была оптимизирована до test_
! Есть ли веская причина, почему компилятор С# будет делать это?
Для полной проверки это вывод Main()
, который я вижу в ILSpy.
private static void Main(string[] args)
{
int test_ = 1; //Where did the "1" go at the end of the variable name???
int test_2 = 0;
int test_3 = 0;
if (test_ == 1)
{
Console.Write(1);
}
else
{
if (test_2 == 1)
{
Console.Write(1);
}
else
{
if (test_3 == 1)
{
Console.Write(2);
}
else
{
Console.Write("x");
}
}
}
}
UPDATE
По-видимому, после проверки IL, это проблема с ILSpy, а не с компилятором С#. Евгений Подскаль дал хороший ответ на мои первоначальные комментарии и наблюдения. Тем не менее, мне интересно узнать, является ли это скорее ошибкой в ILSpy или если это преднамеренная функциональность.
Ответы
Ответ 1
Ну, это ошибка. Не так много ошибок, маловероятно, чтобы кто-нибудь когда-либо подавал отчет об ошибке. Обратите внимание, что ответ Евгения очень вводит в заблуждение. ildasm.exe достаточно умен, чтобы знать, как найти файл PDB для сборки и получить информацию об отладке для сборки. Который включает имена локальных переменных.
Это обычно не роскошь, доступная для дизассемблера. Эти имена фактически не присутствуют в самой сборке, и они обязательно должны делать без PDB. Что-то, что вы можете увидеть в файле ildasm.exe, просто удалите файлы .pdb в каталогах obj\Release и bin\Release, и теперь он выглядит так:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 50 (0x32)
.maxstack 2
.locals init (int32 V_0,
int32 V_1,
int32 V_2)
IL_0000: ldc.i4.1
// etc...
Имена, подобные V_0
, V_1
и т.д., конечно, невелики, дизассемблер обычно придумывает что-то лучшее. Что-то вроде "num".
Итак, как бы там ни было обнаружено сообщение об ошибке в ILSpy, он также считывает файл PDB, но искажает полученный символ. Вы можете указать ошибку с продавцом, но вряд ли они будут относиться к ней как к высокоприоритетной ошибке.
Ответ 2
Вероятно, это проблема с декомпилятором. Поскольку IL корректен для .NET 4.5 VS2013:
.entrypoint
// Code size 79 (0x4f)
.maxstack 2
.locals init ([0] int32 test_1,
[1] int32 test_2,
[2] int32 test_3,
[3] bool CS$4$0000)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
edit: он использует данные из файла .pdb(см. этот ответ), чтобы получить правильные переменные имен. Без pdb он будет иметь переменные в форме V_0, V_1, V_2
.
EDIT:
Изменяется имя в файле NameVariables.cs в методе:
public string GetAlternativeName(string oldVariableName)
{
if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) {
for (char c = 'i'; c <= maxLoopVariableName; c++) {
if (!typeNames.ContainsKey(c.ToString())) {
typeNames.Add(c.ToString(), 1);
return c.ToString();
}
}
}
int number;
string nameWithoutDigits = SplitName(oldVariableName, out number);
if (!typeNames.ContainsKey(nameWithoutDigits)) {
typeNames.Add(nameWithoutDigits, number - 1);
}
int count = ++typeNames[nameWithoutDigits];
if (count != 1) {
return nameWithoutDigits + count.ToString();
} else {
return nameWithoutDigits;
}
}
NameVariables
класс использует словарь this.typeNames
для хранения имен переменных без конечного числа (такие переменные означают что-то особенное для ILSpy или, возможно, даже для IL, но я действительно сомневаюсь в этом), связанный с счетчиком их явлений в методе декомпилировать.
Это означает, что все переменные (test_1, test_2, test_3
) завершатся в одном слоте ( "test_" ), а для первого count
var будет одним, что приведет к выполнению:
else {
return nameWithoutDigits;
}
где nameWithoutDigits
есть test_
EDIT
Во-первых, спасибо @HansPassant и его ответ за указание на ошибку в этом сообщении.
Итак, источник проблемы:
ILSpy такой же умный, как и ildasm, потому что он также использует данные .pdb(или как еще он получает имена test_1, test_2
вообще). Но его внутренняя работа оптимизирована для использования с сборками без какой-либо информации, связанной с отладкой, поэтому ее оптимизация, связанная с работой с переменными V_0, V_1, V_2
, работает непоследовательно с богатством метаданных из файла .pdb.
Как я понимаю, виновником является оптимизация для удаления _0
из одиночных переменных.
Фиксация, вероятно, потребует распространения факта использования данных .pdb в код поколения имен переменных.