ようこそ。睡眠不足なプログラマのチラ裏です。

いまさら聞けない、ジェネリックでダウンキャストによる問題を回避

ジェネリックでダウンキャストによる問題を回避

まず、ダウンキャストをしている問題のコード

using System;

namespace ConsoleApplication1
{
    public abstract class Hero
    {
        public Hero GetMyself()
        {
            return this;
        }
    }

    public class Ultramen : Hero
    {
        public string Name
        {
            get { return "ウルトラマン"; }
        }
    }

    public class KamenRider : Hero
    {
        public string Name
        {
            get { return "仮面ライダー"; }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Ultramen u = new Ultramen();
            Ultramen u2 = (Ultramen)u.GetMyself();
            Console.WriteLine(u2.Name);

            KamenRider k = new KamenRider();
            KamenRider k2 = (KamenRider)k.GetMyself();
            Console.WriteLine(k2.Name);

            Console.ReadLine();
        }
    }    
}

基底クラスであるヒーロークラスから、派生クラスであるウルトラマンクラスおよび
仮面ライダークラスにキャストされているのがわかる。これがいわゆるダウンキャスト。
んで、その逆がアップキャストです。アップキャストは常に安全であることが保証されているが、
このダウンキャストの場合は必ずしも安全に行うことが出来るとは限らない。



たとえば、上記コードの一部を以下のように改変するとどうだろう。

            KamenRider k = new KamenRider();
            Ultramen k2 = (Ultramen)k.GetMyself();
            Console.WriteLine(k2.Name);

仮面ライダークラスを、ウルトラマンクラスにぶち込もうとしているのが見てわかる。
このコードはビルドは通るが、プログラムを実行すると System.InvalidCastException という
無効なキャストまたは明示的な参照変換の際にエラーが発生したときの例外がスローされる。
ウルトラマンと仮面ライダーは確かにヒーローではあるが、全く別のものであるということは
静的型付きの下でも、怒ってくんないんですよね。



ダウンキャストを行うとこのような問題が生じるので、
動的な型情報を取得するための構文として、C#では「is 演算子」および「as 演算子」が提供されている。
いやでも、ダウンキャストを行うことで安全性が損なわれる可能性があるのであれば、
そもそも「ダウンキャストそのものをできればしたくない」よねという話。
そんなときは、ジェネリックを利用して派生クラスが自身で自身を言及できるようにします。

using System;

namespace ConsoleApplication1
{
    public abstract class Hero<T> where T : Hero<T>
    {
        public T GetMyself()
        {
            return (T)this;
        }
    }

    public class Ultramen : Hero<Ultramen>
    {
        public string Name
        {
            get { return "ウルトラマン"; }
        }
    }

    public class KamenRider : Hero<KamenRider>
    {
        public string Name
        {
            get { return "仮面ライダー"; }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Ultramen u = new Ultramen();
            Ultramen u2 = u.GetMyself();
            Console.WriteLine(u2.Name);

            KamenRider k = new KamenRider();
            KamenRider k2 = k.GetMyself();
            Console.WriteLine(k2.Name);

            Console.ReadLine();
        }
    }
}

あい。これでダウンキャストによる問題を回避できますね。
めでたしめでたし(´ω`)