読者です 読者をやめる 読者になる 読者になる

【C#】引数として渡したいTypeに制約を設ける

AttributeからリフレクションをキメるためにTypeを渡すようなメソッドを作ることは割とよくあることなんですが、渡すTypeにジェネリクスっぽい制約(境界)を設けたくなることもあると思います。

まぁそもそも「あるインターフェースを実装している場合は特定のAttributeをつけないとビルドできない」みたいな機能があるわけではないので、外道と言えば外道な処理です。とは言え、普通に欲しいので考えてみます。

まちがい:Typeから無理矢理調べる

Typeクラスのドキュメントを眺めているとFindInterfacesメソッドなんてものがあります。

これ、ちょっと使い方がわかりにくいんですが、こんな感じです。

// tには「IHoge」と言うインターフェースが実装されていると想定
public void Piyo(Type t)
{
    var types = t.FindInterfaces(new TypeFilter((x, y) => x == y), typeof(IHoge));

    if(!types.Any())
    {
        throw new ApplicationException(string.Format("{0} is not IHoge.", t.Name));
    }
}

TypeFilterのデリゲートでやってくるObject(今回だとy)が、FindInterfacesの第二引数で渡したObject(typeof(IHoge))になります。

勿論Objectが渡せるので、配列を渡してやってもいいです。

対象のType(t)が該当するインターフェース(IHoge)を持っていればtypesに何らかの値が入るので、後はAnyで調べればOK、と言う寸法です。

でもこんなのクールじゃないですよね。コンパイルは通ってしまいますし。ジェネリクスならコンパイルの時点ではじいてくれますよ。ジェネリクスなら…。

せいかい:ジェネリクスで制限する

最近Javaばっかりやってたのですっかり忘れてたんですが、C#ジェネリクスは型パラメータからtypeofでTypeを取得できるのでした。

これと境界を組み合わせれば何の問題もありません。

// THogeはIHogeを継承 or 実装してないとそもそもコンパイルできない
public void Piyo<THoge>() where THoge : IHoge
{
    var t = typeof(THoge);
}

ね?簡単でしょ?

まとめ

と言う話を新人にしたら何か気持ち悪がられたのでメモしておきます。