C#のキャスト、isとas


参照型のキャスト

参照型のキャストに悩む必要はない。

//キャストできればその結果が、できなければnullがresultに格納される

TargetType result = obj as TargetType;

//キャストできればtrueが、できなければfalseがisTargetTypeに格納される
bool isTargetType = obj is TargetType;

C#7.0からはisの結果を変数に受けられるようになったので次のような書き方ができる。

//isTargetTypeにキャスト可否が、resultにキャスト結果が格納される

bool isTargetType = obj is TargetType result;

よく使うイディオムはこんな感じだろう。

//目的の型にキャストできたときのみ処理を実行する。

//昔ならこう書いていた
var result1 = obj as TargetType;
if (result1 != null)
{
result1.SomeMethod();
}

//手を抜けばこれでもよかった
(obj as TargetType)?.SomeMethod();

//C#7.0以降ならこう書ける
if (obj is TargetType result2)
{
result2.SomeMethod();
}
//ちなみにresult2のブロックはifブロックの外側なのでここでも参照できる
result2 = null;


値型のキャスト

値型のキャストはそうはいかなかった。

object obj = 1;

//NG:asは値型では使えない
int i = obj as int;

//OK
int j = (int)obj;

struct TargetStruct {}
//構文上OK、ただしInvalidCastExceptionが飛ぶ。
TargetStruct target = (TargetStruct)obj;

見ての通りasが使えない。

考えてみれば当然の話で、値型では失敗時のデフォルト値ゼロと成功時の値ゼロが区別できないのだ。

じゃあ安全なキャストにはどうすればいいのか。

//isでの判定はできるので昔ならこう

if (obj is int)
{
SomeMethod((int)obj);
}

//C#7.0以降ならisの結果を受け取れるので
if (obj is int i)
{
SomeMethod(i);
}

じゃあasはisの下位互換なのかというと別にそんなわけではない。

従来から

//resultはNullable<int>型

//resultにはキャストに成功すればその値が、失敗すればnullが入る
var result = obj as int?;
if (result != null)
{
SomeMethod(i.Value);
}

Nullable<T>にはasを使って安全にキャストできていたので参照型同様の手も使えた。

でもわざわざNullable<T>を使うのも面倒なので今後は積極的にisを使っていきたい。


結論

C#7.0以降なら参照型・値型を問わずisを使うと統一した方法でキャストができる。

それ以前の環境で無理をしてでも統一した扱いにしたいならNullable<T>へのキャストを、そこまで頑張りたくなければあきらめて処理を分けよう。


注意点

今回それぞれの方法での処理時間は計測していないので注意。

Nullable<T>へのキャストはボクシングがかかってやや遅くなりそうな気がする。

訂正:Nullable<T>はstructだった。

今回この記事を書こうと思った理由はIValueConverterの実装だったのでそのくらいのコストは無視していいのだけど。


ところで

残念なことに現在参加中のプロジェクトはC#6.0だった。

一刻も早いC#8.0への移行を強く訴えていきたい。