[C#] 参照型における()キャストとis, asキーワードによる型変換の速さの違い

# はじめに
C#で型変換に使う主な5つの方法の速さを比べました。

# 結論
()キャストを使う場合、例外が発生すると極端に遅くなるが、全く例外が発生しないケースでは最速でした。
is, asの型変換はどんなケースでもそれなりの速さが出るようです。

# 環境
Windows 10 Home, AMD Ryzen 5 3500 6-Core Processor(3.59 GHz), 16GB Memory, Visual Studio 2019, .Net Core 3.1

# 計測コード

```c#
class A
{
    public A(string s)
    {
        member = s;
    }

    string member;
}

class B
{
    public B(string s)
    {
        member = s;
    }

    string member;
}

//tryとcatchで囲って()キャストする方法
static void Cast(object[] vs)
{
    foreach (object o in vs)
    {
        try
        {
            A v = (A)o;
        }
        catch
        {

        }
    }
}

//asキーワードで変換してからnullチェックする方法
static void AsIf(object[] vs)
{
    foreach (object o in vs)
    {
        A v = o as A;
        if (v == null) { }
    }
}

//isキーワードで変換できるかをチェックし、そのまま変換する方法
static void IfIs(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A v) { }
    }
}

//isキーワードでチェックしてから()キャストする方法
static void IfIsCast(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A)
        {
            A v = (A)o;
        }
    }
}

//isキーワードでチェックしてからasキーワードで変換する方法
static void IfIsAs(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A)
        {
            A v = o as A;
        }
    }
}

//５つの変換方法を試す
static void TryMethods(int percentageOfA)
{
    int separator = (int)(percentageOfA / 100f * ss.Length);
    object[] vs = ss.Select((s, i) => i < separator ? (object)new A(s) : new B(s)).ToArray();

    Console.WriteLine($"{percentageOfA}%");
    Console.WriteLine($"Cast:\t\t{Measure(count, () => Cast(vs))}");
    Console.WriteLine($"AsIf:\t\t{Measure(count, () => AsIf(vs))}");
    Console.WriteLine($"IfIs:\t\t{Measure(count, () => IfIs(vs))}");
    Console.WriteLine($"IfIsCast:\t{Measure(count, () => IfIsCast(vs))}");
    Console.WriteLine($"IfIsAs:\t\t{Measure(count, () => IfIsAs(vs))}");
    Console.WriteLine();
}

static int count = 3000;
static string[] ss = new string[10000000];

//エントリポイント
public static void Entry()
{
    Random random = new Random();
    for (int i = 0; i < ss.Length; i++)
    {
        int length = random.Next(10);
        for (int j = 0; j < length; j++)
        {
            ss[i] += (char)random.Next('A', 'z');
        }
    }

    Console.WriteLine($"count:\t{count}");
    Console.WriteLine($"length:\t{ss.Length}");
    Console.WriteLine();

    TryMethods(0);
    TryMethods(20);
    TryMethods(40);
    TryMethods(60);
    TryMethods(80);
    TryMethods(100);
}

//関数の処理にかかる時間を計測する
public static TimeSpan Measure(int count, Action action)
{
    DateTime time = DateTime.Now;
    for (int i = 0; i < count; i++)
    {
        action();
    }
    return DateTime.Now - time;
}
```

# 結果
##型変換に失敗する参照が含まれている場合
例外処理が重すぎたため、以下の数値設定でひとまず測りました。
count = 1
length = 10000
表の左上の値は型変換に成功する参照が含まれている割合です。
時間の単位は秒です。

| 0% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |71.1655179|71.8009120|71.6555776|71.5406692|
| AsIf     | 0.0002877| 0.0003214| 0.0002501| 0.0002864|
| IfIs     | 0.0002317| 0.0001495| 0.0001622| 0.0001811|
| IfIsCast | 0.0001465| 0.0001404| 0.0002372| 0.0001747|
| IfIsAs   | 0.0001366| 0.0001362| 0.0001423| 0.0001384|


| 20% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |56.6513413|56.5845480|57.0564668|56.7641187|
| AsIf     | 0.0000406| 0.0000440| 0.0000363| 0.0000403|
| IfIs     | 0.0000344| 0.0000424| 0.0000406| 0.0000391|
| IfIsCast | 0.0000363| 0.0000399| 0.0000358| 0.0000373|
| IfIsAs   | 0.0000407| 0.0000351| 0.0000353| 0.0000370|

| 40% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |42.7042395|42.6855280|42.9887469|42.7928381|
| AsIf     | 0.0000365| 0.0000365| 0.0000361| 0.0000364|
| IfIs     | 0.0000364| 0.0000376| 0.0000324| 0.0000355|
| IfIsCast | 0.0000342| 0.0000369| 0.0000368| 0.0000360|
| IfIsAs   | 0.0000425| 0.0000392| 0.0000337| 0.0000385|

| 60% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |28.6295313|28.4973504|28.7604727|28.6291181|
| AsIf     | 0.0000340| 0.0000340| 0.0000343| 0.0000341|
| IfIs     | 0.0000317| 0.0000315| 0.0000325| 0.0000319|
| IfIsCast | 0.0000490| 0.0000369| 0.0000372| 0.0000410|
| IfIsAs   | 0.0000315| 0.0000306| 0.0000311| 0.0000311|

| 80% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |14.4235740|14.4582490|14.4788299|14.4535510|
| AsIf     | 0.0000315| 0.0000325| 0.0000309| 0.0000316|
| IfIs     | 0.0000273| 0.0000329| 0.0000315| 0.0000306|
| IfIsCast | 0.0000304| 0.0000342| 0.0000342| 0.0000329|
| IfIsAs   | 0.0000322| 0.0000363| 0.0000304| 0.0000330|

| 100% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     | 0.0000244| 0.0000241| 0.0000264| 0.0000250|
| AsIf     | 0.0000286| 0.0000272| 0.0000259| 0.0000272|
| IfIs     | 0.0000263| 0.0000262| 0.0000261| 0.0000262|
| IfIsCast | 0.0000303| 0.0000358| 0.0000300| 0.0000320|
| IfIsAs   | 0.0000303| 0.0000302| 0.0000301| 0.0000302|


## 型変換に失敗する参照が含まれていない場合
上の結果でいう100%だけを以下の数値設定で測りました。
count = 3000
length = 10000000

| 100% | １回目 | ２回目 | ３回目 | 平均 |
|:---------|---------:|---------:|---------:|---------:|
| Cast     |73.9517405|74.0026143|74.1104895|74.0216148|
| AsIf     |81.3793428|81.5171491|81.4947477|81.4637465|
| IfIs     |81.9090815|82.2008835|81.9648811|82.0249487|
| IfIsCast |93.9863807|94.0810441|87.0868509|91.7180919|
| IfIsAs   |93.9113493|94.2150005|87.0688733|91.7317410|

()キャストは最速といっても気持ち速い程度のようです。
IfIsCastとIfIsAsは値が比較的安定していないので、実行状況によって速さが変わりやすいのかもしれません。