Разделение.NET-декомпилятора между "использованием" и "попробуйте... наконец"
Учитывая следующий код С#, в котором метод Dispose вызывается двумя разными способами:
class Disposable : IDisposable
{
public void Dispose()
{
}
}
class Program
{
static void Main(string[] args)
{
using (var disposable1 = new Disposable())
{
Console.WriteLine("using");
}
var disposable2 = new Disposable();
try
{
Console.WriteLine("try");
}
finally
{
if (disposable2 != null)
((IDisposable)disposable2).Dispose();
}
}
}
После компиляции с использованием конфигурации выпуска, затем разобранной с помощью ildasm, MSIL выглядит следующим образом:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 57 (0x39)
.maxstack 1
.locals init ([0] class ConsoleApplication9.Disposable disposable2,
[1] class ConsoleApplication9.Disposable disposable1)
IL_0000: newobj instance void ConsoleApplication9.Disposable::.ctor()
IL_0005: stloc.1
.try
{
IL_0006: ldstr "using"
IL_000b: call void [mscorlib]System.Console::WriteLine(string)
IL_0010: leave.s IL_001c
} // end .try
finally
{
IL_0012: ldloc.1
IL_0013: brfalse.s IL_001b
IL_0015: ldloc.1
IL_0016: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_001b: endfinally
} // end handler
IL_001c: newobj instance void ConsoleApplication9.Disposable::.ctor()
IL_0021: stloc.0
.try
{
IL_0022: ldstr "try"
IL_0027: call void [mscorlib]System.Console::WriteLine(string)
IL_002c: leave.s IL_0038
} // end .try
finally
{
IL_002e: ldloc.0
IL_002f: brfalse.s IL_0037
IL_0031: ldloc.0
IL_0032: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0037: endfinally
} // end handler
IL_0038: ret
} // end of method Program::Main
Как декомпилятор.NET, такой как DotPeek или JustDecompile, делает разницу между использованием и попыткой... наконец?
Ответы
Ответ 1
На самом деле это не имеет значения. Как говорит Марк в комментариях - если вы пишете тот же код, что и компилятор, сгенерированный для using
- декомпилятор не сможет изменить ситуацию.
Однако многие декомпиляторы, в том числе DotPeek, могут фактически использовать файлы отладочных символов (.pdb), чтобы найти фактический исходный код, а затем использовать фактический исходный код, чтобы вообще не происходило декомпиляции. Кроме того, компиляция в режиме отладки также может повлиять на шаблон (т.е. Ваша попытка подражать using
оператора может иметь разные результаты IL в debug vs release компиляции).
Чтобы предотвратить использование DotPeek ваших реальных файлов исходного кода, откройте "Инструменты"> "Параметры"> "Декомпилятор" и снимите флажок "Использовать источники из файлов символов, когда они доступны". Затем скомпилируйте свой код в Release и обратите внимание, что DotPeek будет декомпилировать оба оператора как using
.
Ответ 2
Как декомпилятор.NET, такой как DotPeek или JustDecompile, делает разницу между использованием и попыткой... наконец?
Декомпиляторы в основном работают над сопоставлением шаблонов. Как правило, IL преобразуется в простейшее эквивалентное представление, возможное на целевом языке (в данном случае С#). Затем эта модель кода проходит через ряд преобразований, которые пытаются сопоставить кодовые последовательности с хорошо известными шаблонами. С помощью отладочной сборки ILSpy вы можете просматривать результаты на разных этапах этого конвейера.
Конвейер декомпилятора может включать в себя преобразования, такие как перезаписывающий цикл. Репитер цикла может воссоздавать for
циклов, ища в while
циклы, которым предшествуют инициализаторы переменных, и которые также содержат общие инструкции итерации перед каждым задним фронтом. Когда такой контур обнаружен, он получает переписать в виде более краткой for
цикла. Он не знает, что исходный код фактически содержит цикл for
; он просто пытается найти наиболее сжатый способ представления кода при сохранении правильности.
Аналогичным образом, using
перезаписывающего устройства будет искать блоки try/finally
которых, finally
содержится простая проверка нуля и вызов Dispose()
, а затем переписываются с using
блоков, которые являются более краткими, но при этом остаются правильными в соответствии со спецификацией языка. Декомпилятор не знает, что код содержит блок using
, но поскольку почти никто не использует явную форму try/finally
, результаты, как правило, согласуются с исходным источником.