はじめに
Rider 2022.3、そしてReSharper 2022.3からIL Viewerで、ILをデコンパイルしたC#が閲覧できるようになりました。
2形式でデコンパイル可能で、
- high-level(最新の言語機能を使用した記述)
- low-level(シンタックスシュガーな構文を使わない単純な記述)
なC#として閲覧できます。
WebサービスSharplabで可能だったことが、Rider・ReSharperでもできるようになりましたね。
何が嬉しいのか
「C#をコンパイルしたILを読めるのが嬉しいのはわかる。だがそれを再度C#にデコンパイルして何が嬉しいんだ」
と思うかもしれません。
C#はシンタックスシュガー(糖衣構文)が多い言語です。コンパイル結果のILをデコンパイルしたC#を読むことで、シンタックスシュガーを解いた単純なC#を読むことができます。それにより、C#のシンタックスシュガーの内部で、どういうことがおこなわれているか理解できるのが、嬉しいポイントです。
機能追加バージョン
機能追加バージョン
- Rider 2022.3から
- ReSharper 2022.3から
どのようにデコンパイルされるか
この節では、
- C# 11
- .NET 7.0.100
- Mac版のRider 2022.3
でのどのようにデコンパイルされたかをスクリーンショットで示します。実際のコードは、本投稿の最後に掲載します。
次のようなレコードがあります。
次のようなメソッドのILを見てみましょう。
メニューから、Tools > IL Viewerを選択すると、IL Viewerが開きC#ファイルのILが閲覧できます。先ほどのメソッドのILは次のようになります。雰囲気はわかりますが、難しいですね。私は読めません。
IL Viewer Windowの上部のドロップボックスを選択すると
- IL
- Low-Level C#
- High-Level C#
という選択肢が出てきます。
「Low-Level C#」を選ぶと該当メソッドは次のようになります。List型のAddメソッドを呼び出していることに注目してください。「Low-Level C#」のコードを読むことで、「コレクション初期化子が内部でAddメソッドを読み出すこと」をコードを見ながら理解することができます。
「High-Level C#」を選ぶと次のようにな理ます。デコンパイルした結果では実際のコードと異なり、式形式のメソッドが使われていることに注目してください。
次の例は、補完文字列とrecordの分解がどのようにILになり、デコンパイルされるかをみてみましょう。C#のオリジナルコードはこちらです。
IL(の途中まで)はこちら。長いのでメソッド全ては掲載していません。
Low-Level C#(の途中まで)はこちら。分解ではDeconstructメソッドを呼び出していたり、補完文字列にDefaultInterpolatedStringHandlerが使われていることがわかりますね。
High-Level C#はこちら。補完文字列がDefaultInterpolatedStringHandlerのままになっています。(雑な予想ですが、デコンパイルが難しいのかもしれません。)
最後にrecordが内部でどのようなメソッド・コンストラクター・バッキングフィールド・プロパティーが生成されているのか確認してみましょう。
C#のオリジナルコードはこちら。
IL(の途中まで)はこちら。長いのでメンバ全ては掲載していません。
Low-Level C#(の途中まで)はこちら。長いのでメンバ全ては掲載していません。デコンパイルされたC#を見ると、多くのメンバが生成されていることがわかります。
High-Level C#はこちら。
まとめ
C#はシンタックスシュガー(糖衣構文)が多い言語です。Rider・ReShaperのIL Viewerの「ILをC#にデコンパイルする」新機能を使い、C#のシンタックスシュガーの内部でどういうことがおこなわれているか理解することができます。
「シンタックスシュガーの内部など、そんなことは知っている」という熟練のC#erも、チームメンバーの教育に役立てることができるかと思います。
Riderはいいぞ!ReSharperもいいぞ!!!
公式リンク
今回使ったデコンパイルを試したコード
using System;
using System.Collections.Generic;
record Person(
String Name,
long Id
);
class Program
{
static void Main(string[] args)
{
var list = new List<Person>
{
new("taro", 1L),
new("jiro", 2L),
new("saburo", 3L),
};
foreach (var person in list)
{
ShowPerson(person);
}
}
static void ListSample()
{
var list = new List<Person>
{
new("taro", 1L),
new("jiro", 2L),
new("saburo", 3L),
};
Console.WriteLine(list.Count);
}
static void ShowPerson(Person person)
{
var (name, id) = person;
var message = $"id: {id} {name}";
Console.WriteLine(message);
}
}