Переменные, заканчивающиеся на "1", удаляют "1" в ILSpy. Зачем?
Чтобы понять, как компилятор C# оптимизирует код, я создал простое тестовое приложение. С каждым изменением теста я компилировал приложение и затем открывал бинарный файл в 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_
! Есть ли веская причина, почему компилятор C# сделал бы это?
Для полной проверки это вывод 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");
}
}
}
}
ОБНОВИТЬ
По-видимому, после проверки IL, это проблема ILSpy, а не компилятора C#. Евгений Подскал дал хороший ответ на мои первоначальные комментарии и наблюдения. Тем не менее, мне интересно знать, является ли это скорее ошибкой в ILSpy или это намеренная функциональность.
2 ответа
Ну, это ошибка. Не большая ошибка, довольно маловероятно, что кто-либо когда-либо подал отчет об ошибке для него. Обратите внимание, что ответ Евгения очень вводит в заблуждение. 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, но шарит в найденном символе. Вы можете сообщить об ошибке поставщику, но вряд ли они отнесутся к ней как к высокоприоритетной.
Возможно, это какая-то проблема с декомпилятором. Потому что 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
редактировать: он использует данные из файла.pdb (см. этот ответ), чтобы получить правильные имена переменных. Без pdb он будет иметь переменные в форме V_0, V_1, V_2
,
РЕДАКТИРОВАТЬ:
Имя переменной искажается в файле 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_
РЕДАКТИРОВАТЬ
Во-первых, спасибо @HansPassant и его ответу за указание на ошибку в этом посте.
Итак, источник проблемы:
ILSpy столь же умный, как и ildasm, потому что он также использует данные.pdb (или как еще он получает test_1, test_2
имена вообще). Но его внутренняя работа оптимизирована для использования со сборками без какой-либо информации, связанной с отладкой, следовательно, его оптимизация связана с работой с V_0, V_1, V_2
Переменные работают несовместимо с множеством метаданных из файла.pdb.
Как я понимаю, виновником является оптимизация по удалению _0
от одиноких переменных.
Исправление этого, вероятно, потребует распространения факта использования данных.pdb в код генерации имен переменных.