思い切りハマったのでメモ。

JavaとC#はどちらもジェネリクスの機能を持っており、厳密な型の定義をしつつ抽象化したクラス設計が可能になっている。
ただ、Javaを使ってからC#のジェネリクスを使うと、Javaで通用していたクラス設計ではコンパイルが通らなくなってしまうことがある。

具体的には、下記のような場合。
(基底クラスに共通の処理を実装したい場合はかなり使うパターンだと思う


// Parent基底クラス。
// 型パラメータにChildAbstractを継承したクラスを受け取る。
abstract public class ParentAbstract<T> where T : ChildAbstract
{
    protected T child;
    
    public ParentAbstract(string firstName)
    {
        child = new T(firstName);
    }
    
    abstract public string GetInfo();
}

// Child基底クラス。
abstract public class ChildAbstract
{
    protected string firstName;
    
    public ChildAbstract(string firstName)
    {
        this.firstName = firstName;
    }

    abstract public string GetName();
}

// AとB、2種類のChildクラスを定義。
public class ChildA : ChildAbstract
{
    public override string GetName()
    {
        return "山田" + firstName;
    }
}

public class ChildB : ChildAbstract
{
    public override string GetName()
    {
        return "鈴木" + firstName;
    }
}

// AとB、2種類のParentクラスを定義。それぞれ特定のChildのみを扱う。
public class ParentA : ParentAbstract<ChildA>
{
    public ParentA(string firstName) : base(firstName)
    {
    }

    public override string GetInfo()
    {
        return string.Format("あちきの名前は{0}でげす。", child.GetName());
    }
}

public class ParentB : ParentAbstract<ChildB>
{
    public ParentB(string firstName) : base(firstName)
    {
    }
    
    public override string GetInfo()
    {
        return string.Format("おいどんの名前は{0}でごわす。", child.GetName());
    }
}

// 基底クラスをハンドリングするクラス
public class Handler 
{
    public ParentAbstract<ChildAbstract> Parent {set; get;}

    public void Say()
    {
        if (null != Parent) {
            Console.WriteLine(printParent.GetInfo());
        } else {
            Console.WriteLine("誰もセットされとらんです。");
        }
    }
}

// 実行メソッド
public static void Run()
{
    var handler = new Handler();
    
    // 誰もセットされとらんです。と言わせたい
    handler.Say();
    
    // あちきの名前は山田権左衛門でげす。と言わせたい
    handler.Parent = new ParentA("権左衛門");
    handler.Say();

    // おいどんの名前は鈴木田吾作でごわす。と言わせたい
    handler.Parent = new ParentB("田吾作");
    handler.Say();
}

さて、上記のクラス設計だとコンパイルが通らない。
問題点は2箇所。

1.C#のジェネリクスでは、型パラメータで渡されたクラスの、引数が必要なコンストラクタを呼ぶことが出来ない
これはParentAbstractの new T(firstName); が実行出来ないということ。
リフレクションでコンストラクタを呼び出せば回避可能な模様。

2.C#のジェネリクスでは、型パラメータが異なるとキャスト出来ない
つまりは handler.Parent = new ParentA(“権左衛門”); が問題だということ。
handler.Parent は ParentAbstract<ChildAbstract> だが、 new ParentA(“権左衛門”) は ParentA<ChildA> 。
型パラメータに差異があると、それが例え基底クラスであってもキャスト出来ない。
この縛りのせいで複雑な構造が汎用的に書けない・・・。

ちなみに、クラスではなくインターフェースの場合はout宣言を付けるとかでこの問題を回避可能らしい。
が、こんな複雑な構造使うのは基底クラスにガチャガチャ処理を書きたい場合くらいしか思い当たらない。。

JavaとC#のジェネリクスの違いについては下記サイトで非常に詳しくまとめられていて、自分も大変お世話になった。
ひと通り書いてから「この設計だとコンパイル通らないじゃん!ギャース」という悲劇を避けるためにも、ご一読することをオススメする。
http://homepage2.nifty.com/magicant/programmingmemo/genericsjavavscs.html

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Name *
Email *
Website

*