🙆
C# 9.0の範囲パターンのswitchはわりと賢くコンパイルされる
範囲パターンを使ったswitch式
C# 9.0 で switch式に比較演算を指定できるようになりました。
元のコード
public class C
{
public int Expr(int num) => num switch
{
<=0 => -1,
< 10 => 0,
< 20 => 1,
< 30 => 2,
< 40 => 3,
< 50 => 4,
< 60 => 5,
_ => -1,
};
}
上記のコードをコンパイル結果(のILをC#にデコンパイルしたもの)はこちら。
コンパイル結果
public class C
{
public int Expr(int num)
{
if (num < 30)
{
if (num < 10)
{
if (num <= 0)
{
return -1;
}
return 0;
}
if (num < 20)
{
return 1;
}
return 2;
}
if (num < 50)
{
if (num < 40)
{
return 3;
}
return 4;
}
if (num < 60)
{
return 5;
}
return -1;
}
}
なんと二分探索になっています。
このくらいならばif文を列挙しても大差ないかとは思いますが、二分探索になることで if文が実行される回数が
ちなみに、switch文でもジャンプテーブルにできないような場合は二分探索に変換されるようです。
追記
「デメリットなしで」と書きましたが、「殆どの場合は 0 <= num && num < 10
である」というようにマッチする頻度が非常に高いものがあるならマッチする頻度が高い順にif文を羅列するほうが二分探索より高速そうです。。
orを使う場合
or
も使ってみます。
元のコード
public class C
{
public int Expr(int num) => num switch
{
<=0 => -1,
< 10 or > 600 => 0,
< 20 or > 500 => 1,
< 30 or > 400 => 2,
< 40 or > 300 => 3,
< 50 or > 200 => 4,
< 60 or > 100 => 5,
_ => -1,
};
}
コンパイル結果はこちら。
コンパイル結果
public class C
{
public int Expr(int num)
{
if (num > 100)
{
if (num > 300)
{
if (num > 500)
{
if (num > 600)
{
goto IL_0061;
}
goto IL_0065;
}
if (num > 400)
{
goto IL_0069;
}
goto IL_006d;
}
if (num > 200)
{
goto IL_0071;
}
}
else
{
if (num < 30)
{
if (num < 10)
{
if (num > 0)
{
goto IL_0061;
}
return -1;
}
if (num < 20)
{
goto IL_0065;
}
goto IL_0069;
}
if (num < 50)
{
if (num < 40)
{
goto IL_006d;
}
goto IL_0071;
}
if (num >= 60)
{
return -1;
}
}
return 5;
IL_0065:
return 1;
IL_0061:
return 0;
IL_0071:
return 4;
IL_0069:
return 2;
IL_006d:
return 3;
}
}
goto
でスパゲッティ気味になっていますが、これも二分探索になっています。
結論
範囲チェックを列挙するときは switch
式だと効率的!
Discussion