【LINQ】Cast<T>は使わない方が良い?OfType<T>を使おう

スポンサーリンク

Cast<T>は使う必要なし。OfType<T>を使おう

こんにちは、働くC#プログラマーのさんさめです。

今回はLINQの中でも特に違いが分かりにくい、
OfType<T>とCast<T>の違いについて見ていきたいと思います。

どちらも、基本的には列挙の中の要素を指定したT型にキャストするメソッドです。

さんさめ自身も最初の頃はどっちを使えば良いのかよく分からなくて、
その日の気分によってOfType<T>を使ったりCast<T>を使ったりしてました。

結論から言ってしまうと、Cast<T>を使う機会はほぼありません。
さんさめはC#を業務で数年使っていますが、
Cast<T>を使っていたのは何も知らなかった時だけです。

なんなら、Cast<T>は
Firstと同じ罠メソッドと言ってしまっても差し支えないでしょう…。

Firstが罠メソッドである理由と、
具体的には何を使えば良いのかについては、
別の記事(↓)がありますのでそちらもご覧ください。
【LINQ】Firstは使ってはいけない!FirstOrDefaultを使おう

さて、Cast<T>が必要ない理由は実はFirstとほとんど同じです。
それは、「キャスト失敗時に例外になる」ことです。

Cast<T>はT型にキャストできなかった時に例外になる

たとえば、以下のコード、同一のobjectの配列に対して、
int にキャストしてコンソール出力しているだけの簡単なコードです。

OfType<T>はキャストできたものだけ返しますが、
Cast<T>はキャストできない要素が出てきた瞬間
InvalidCastExceptionという例外を投げます。

static void Main(string[] args)
{
    object[] hogeArray = new object[] { 3, "apple", 42 };

    foreach (var item in hogeArray.OfType<int>())
    {
        // キャストできたものだけforeachの中にくるので、
        // 3 と 42 だけが出力される
        Console.WriteLine(item);
    }

    // 2個目の要素を列挙しようとした時に例外発生
    foreach (var item in hogeArray.Cast<int>())
    {
        // 3 はintにキャストできるので出力されるが、
        // "apple"をキャストしようとしたタイミングで例外発生!
        Console.WriteLine(item);
    }
}

列挙の中に1つでもキャストできない要素があったら例外になるということです。

メソッドを呼び出す列挙の全ての要素が確実にT型にキャストできると
あらかじめわかっている時だけ、
Cast<T>を使っても良い、ということになります。

が、普通にC#を使ってアプリケーションを作る上で
そんな状況は発生しません。断言できます

そもそも列挙に対して一律でキャストを行いたいとき、
キャストできなかった要素はどうでもいいことがほとんどです。

OfType<T>であれば、キャストできるものだけをキャストした状態で返し、
キャストできなかった要素は除外されます。

Cast<T>はnullが混じっているときに挙動が不安定

その他の細かい違いとして、
Cast<T>は要素にnullが混じっていると挙動が不安定になります。

具体的には以下の通りです。

  • Tが値型の時はNullReferenceExceptionになる
  • Tが参照型の時はキャストが成功したとみなす

古い記事だと「Cast<T>はnullを通す」とだけ書かれていますが、
stringやクラスなどでしか試していないのでしょう。

intや構造体で試すとすぐに例外になります。

        struct MyStruct
        {
            public int a;
        }

        struct MyClass
        {
            public int b;
        }

        static void Main(string[] args)
        {
            var hogeArray = new object[]
            {
                new MyClass { b = 3 },
                null,
            };

            // 2 と出力される
            Console.WriteLine(hogeArray.OfType<MyClass>().Count());

            hogeArray = new object[]
            {
                new MyStruct { a = 3 },
                null,
            };

            // NullReferenceException !
            Console.WriteLine(hogeArray.OfType<MyStruct>().Count());
        }

じゃあ、参照型でキャストする使い方なら、
Cast<T>にも使いどころがあるのかというと…。

nullをそのまま通されても結局扱いに困ることが多いため、
やはりOfType<T>を使っておけば良いと言えます。

まとめ

OfType<T>とCast<T>は挙動がよく似たメソッドですが、
以下の違いがあります。

OfType<T>Cast<T>
キャストできなかった時要素はフィルタリングされる例外になる
要素がnullだった時要素はフィルタリングされる例外になる or
要素はフィルタリングされない

OfType<T>は挙動の一貫性があり、
予想外の値が来た時も安定してフィルタリングしてくれますね。

以上の点から、Cast<T>を使う必要はほぼなく、
OfType<T>を使うことを強くお勧めします。

コメント

タイトルとURLをコピーしました