白猫のメモ帳

JavaとかC#とかJavaScriptとかHTMLとか機械学習とか。

総称型メソッドの型指定

こんにちは。

新年度が始まって二週間がたちました。
新しい環境で頑張っている方はとてもお疲れのことでしょう。
新しくなくても眠たいです。春眠春眠・・・。

この前コードを書いていてふと気づいたこと。
JavaC#で総称型メソッドの挙動が違う。

総称型メソッド

まずそもそも総称型メソッドってなんだって話です。
ジェネリクスメソッドとかジェネリックメソッドとか呼んだりもします。

public static String firstOrDefault(List<String> list, String defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

例えばこんなメソッドを作ったとして(StreamAPI使えとか無粋なことは言わないこと)、
Listで同じことをしたい場合、メソッドのオーバーロードをしなくてはいけません。

public static Integer firstOrDefault(List<Integer> list, Integer defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

ひい。めんどくさい。
ということでこんな風にします。

public static <T> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

第一引数に「List<String>」、第二引数に「String」を渡せばちゃんと動きます。
第一引数に「List<Integer>」、第二引数に「Integer」を渡してもちゃんと動きます。
第一引数に「List<String>」、第二引数に「Integer」を渡したらコンパイルエラーになります。
えらい。

引数の前についてる<T>がポイント。
ここでなんかよくわからんけど、なんでも良い型「T」っていうのが定義されます。

public static <T extends Number> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

こんな風にすると、Numberを継承した何か。
になるので、IntegerはOKだけどStringはNGになります。

C#的にはこんな感じ。

public static T FirstOrDefault<T>(IList<T> list, T defaultValue) where T : struct
{
    return list != null && list.Count > 0 ? list[0] : defaultValue;
}

で、このTってコンパイラ的にはどうやって決めてるのっていうのが今日のお話。

明示的に指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

例えばこんな定義をしたとして、

Util.<String>newArrayList();

こんな風にメソッド呼び出し時に明示的にTの型を指定してあげることができます。

引数の型で指定

class Util {
    public static <T> List<T> newArrayList(Class<T> c) {
        return new ArrayList<>();
    }
}

次にこんな風に定義を変えてみると、

Util.newArrayList(String.class);

ジェネリクスの指定なしに呼び出すことができました。
これは引数の型情報からTの型がわかるからです。

戻り値の型で指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

メソッド定義を元に戻してみて、

List<String> list = Util.newArrayList();

戻り値側で型を決めてしまえば、明示的に指定しなくてもOKになりました。
やったね。

が、これをC#でやろうとするとエラーになります。

class Util
{
    public static IList<T> newList<T>()
    {
        return new List<T>();
    }
}

こんな定義をして、

IList<string> list = Util.newList<string>();  // OK
IList<string> list = Util.newList();  // NG

試してみると、戻り値側で型が判定できても明示的に指定しないとコンパイルエラーになってしまいます。

いいとこどりしたい

とまぁ、JavaからC#に移ってきた私としてはちょっと不満ではあるのですが、
C#ジェネリックにはJavaにはない素晴らしい機能があります。

class Util
{
    public static T newInstance<T>() where T : new()
    {
        return new T();
    }
}

そう、newができるのです。
これは型削除されてしまうJavaにはできない芸当です。
(可変長引数とか使って無理やり頑張るとできるとかできないとか・・・)

いいとこどりしたいなー。
できるようにならないかなー。なんて。