C#で手軽にILや内部を確認するなら「SharpLab」!


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を開きましょ!

関連・参考