#tl;dr
macOSでC#の逆コンパイルの記事の
$ git clone https://github.com/icsharpcode/ILSpy.git
を
$ git clone https://github.com/tatsumack/ILSpy.git
$ cd ILSpy
$ git checkout -t origin/visible_async_await_decompile
$ cd ../
に置き換えて、ILSpyのインストールを行う。
#背景
Unite2018の講演「さては非同期だなオメー!async/await完全に理解しよう」でasync/awaitのコードを逆コンパイルしていたので、実際に自分もやってみようと思った。
手元にあるPCがmacOSだったので、オープンソースのデコンパイラであるILSpyを利用した逆コンパイル方法を調べ、macOSでC#の逆コンパイルの記事にまとめた。
しかし、ILSpyのCLIツールでは、なぜかasync/awaitの逆コンパイル結果が表示されず。。
ILSpyのコードを読んでみると、CLIツールではasync/awaitの逆コンパイル結果が表示されないようなオプションとなっていることが分かった。
ということで、
static CSharpDecompiler GetDecompiler(string assemblyFileName)
{
- return new CSharpDecompiler(assemblyFileName, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false });
+ return new CSharpDecompiler(assemblyFileName, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false, AsyncAwait = false });
}
と修正を行ったところ、async/awaitの逆コンパイル結果が表示されるようになった
元レポジトリをforkして修正したブランチはこちら
https://github.com/tatsumack/ILSpy/tree/visible_async_await_decompile
#逆コンパイル結果
async/awaitを逆コンパイルしてみると、state machineが内部に生成されていることが分かる。
コンパイル前
using System;
using System.Threading;
using System.Threading.Tasks;
class HelloWorld {
public static int Main( )
{
var result = AsyncTest().Result;
Console.WriteLine(result);
return 0;
}
static async Task<string> AsyncTest() {
var before = DateTime.Now;
Console.WriteLine(before);
await Task.Delay(5000);
var after = DateTime.Now;
Console.WriteLine(after);
return before + "-" + after;
}
}
逆コンパイル後
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
internal class HelloWorld
{
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <AsyncTest>c__async0 : IAsyncStateMachine
{
internal DateTime <before>__0;
internal DateTime <after>__0;
internal AsyncTaskMethodBuilder<string> $builder;
internal int $PC;
private TaskAwaiter $awaiter0;
public void MoveNext()
{
uint num = (uint)$PC;
$PC = -1;
string result;
try
{
switch (num)
{
default:
return;
case 0u:
<before>__0 = DateTime.Now;
Console.WriteLine((object)<before>__0);
$awaiter0 = Task.Delay(5000).GetAwaiter();
if ($awaiter0.IsCompleted)
{
break;
}
$PC = 1;
$builder.AwaitUnsafeOnCompleted(ref $awaiter0, ref this);
return;
case 1u:
break;
}
$awaiter0.GetResult();
<after>__0 = DateTime.Now;
Console.WriteLine((object)<after>__0);
result = <before>__0 + "-" + <after>__0;
}
catch (Exception exception)
{
$PC = -1;
$builder.SetException(exception);
return;
}
$PC = -1;
$builder.SetResult(result);
}
[DebuggerHidden]
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
$builder.SetStateMachine(stateMachine);
}
}
public static int Main()
{
string result = AsyncTest().Result;
Console.WriteLine(result);
return 0;
}
[DebuggerStepThrough]
[AsyncStateMachine(typeof(<AsyncTest>c__async0))]
private static Task<string> AsyncTest()
{
<AsyncTest>c__async0 <AsyncTest>c__async = default(<AsyncTest>c__async0);
<AsyncTest>c__async.$builder = AsyncTaskMethodBuilder<string>.Create();
ref AsyncTaskMethodBuilder<string> $builder = ref <AsyncTest>c__async.$builder;
$builder.Start(ref <AsyncTest>c__async);
return $builder.Task;
}
}
解説は下記ページが参考になると思います。
https://www.slideshare.net/UnityTechnologiesJapan/unite-tokyo-2018asyncawait
http://blog.xin9le.net/entry/2012/08/06/123916