SharpLabは、.NETのWeb Playgroundです。実行するだけでなく、ILやそのILをデコンパイルしたC#を確認することができます。C#だけじゃなくて、VB.NETやF#にも対応しています。
リンクはこちら : https://sharplab.io/
製作者さんはこちら : @ashmind https://twitter.com/ashmind
ソースコードはこちら : https://github.com/ashmind/SharpLab
C#の言語機能の内のいくつかはシンタックスシュガーです。SharpLabを使いILを確認することで、シンタックスシュガーの実現方法を確認することができます。ILだと読むのも一苦労ですね。SharpLabではILに加え、ILをデコンパイルしたC#を確認することができます。
また、「SharpLab」は
- コード共有
- Syntax Tree
- HeapやStack
- JIT Asm
などもできます。
例
C# 7.0から追加されたローカル関数を使ったコードは次の通りです。
このコード例は次のURLに!
using System;
public class Program {
public void Main() {
int Square(int num) => num * num;
int time = 2;
int Times(int num) => time * num;
Console.WriteLine(Square(2));
Console.WriteLine(Times(3));
}
}
このコードをSharpLabを使って、「IL」と「ILを経由してデコンパイルしたC#」を確認し、内部実装をみてみましょう。
これのILは次の通りです。
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
// Nested Types
.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0'
extends [mscorlib]System.ValueType
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Fields
.field public int32 time
} // end of class <>c__DisplayClass0_0
// Methods
.method public hidebysig
instance void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 38 (0x26)
.maxstack 2
.locals init (
[0] valuetype Program/'<>c__DisplayClass0_0'
)
IL_0000: nop
IL_0001: nop
IL_0002: ldloca.s 0
IL_0004: ldc.i4.2
IL_0005: stfld int32 Program/'<>c__DisplayClass0_0'::time
IL_000a: nop
IL_000b: ldc.i4.2
IL_000c: call int32 Program::'<Main>g__Square|0_0'(int32)
IL_0011: call void [mscorlib]System.Console::WriteLine(int32)
IL_0016: nop
IL_0017: ldc.i4.3
IL_0018: ldloca.s 0
IL_001a: call int32 Program::'<Main>g__Times|0_1'(int32, valuetype Program/'<>c__DisplayClass0_0'&)
IL_001f: call void [mscorlib]System.Console::WriteLine(int32)
IL_0024: nop
IL_0025: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2082
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
.method assembly hidebysig static
int32 '<Main>g__Square|0_0' (
int32 num
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x208b
// Code size 4 (0x4)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.0
IL_0002: mul
IL_0003: ret
} // end of method Program::'<Main>g__Square|0_0'
.method assembly hidebysig static
int32 '<Main>g__Times|0_1' (
int32 num,
valuetype Program/'<>c__DisplayClass0_0'& ''
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2090
// Code size 9 (0x9)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldfld int32 Program/'<>c__DisplayClass0_0'::time
IL_0006: ldarg.0
IL_0007: mul
IL_0008: ret
} // end of method Program::'<Main>g__Times|0_1'
} // end of class Program
ちょと頑張らないと読めませんね。これをデコンパイルしたC#はこちら。
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class Program
{
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <>c__DisplayClass0_0
{
public int time;
}
public void Main()
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = default(<>c__DisplayClass0_0);
<>c__DisplayClass0_.time = 2;
Console.WriteLine(<Main>g__Square|0_0(2));
Console.WriteLine(<Main>g__Times|0_1(3, ref <>c__DisplayClass0_));
}
[CompilerGenerated]
internal static int <Main>g__Square|0_0(int num)
{
return num * num;
}
[CompilerGenerated]
internal static int <Main>g__Times|0_1(int num, ref <>c__DisplayClass0_0 P_1)
{
return P_1.time * num;
}
}
デコンパイルしたC#をみると、ローカル関数の実現の仕方がよくわかりますね!
まとめ
「これILや中身どうやっているんだ?」と思ったら、とりあえずSharpLabを開きましょ!
関連・参考
- https://sharplab.io/
- https://github.com/ashmind/SharpLab
- https://blog.meilcli.net/2018/08/csharplabcil.html
- https://www.atmarkit.co.jp/ait/articles/1802/13/news022.html
- http://tsubakit1.hateblo.jp/entry/2018/05/09/235705
- https://tech.guitarrapc.com/entry/2019/02/14/055953
- https://ufcpp.net/blog/2017/5/roslynjitasm/