オーバーロードとかよくわかってなかった

ちょっとメモ

今日やろうとして、ちょっとハマって結局逃げたところ。というより、ちょいちょい似たようなパターンに遭遇しては綺麗に実装できなくてハンカチかんでたパターン。
書き捨てコードで、スパゲッティだったのでいまいちうろ覚えだけど

public IEnumerable<T> hogeIsMatch<T>(IEnumerable<T> lines, IEnumerable<t> baseLines)
    where T: BaseHoge
{
    var hoged = lines.select(line => {
            var matched = baseLines.matchedLineFilter(line)

            // ~ なんかする ~
            }
    return hoged
}

元はVB.NETだったのだけれど、上記のようなコードになっていて`matchedLineFilter`の部分はT(BaseHogeを継承した型)に応じて分岐させるような感じにしたかった。
そこで書いてみたコードが

public static class HogeHelper
{
    public static BaseHoge matchedLineFilter(this IEnumerable<BaseHoge> me, BaseHoge targ)
    {
        throw new notImplementException()    
    }

    public static FooHoge matchedLineFilter(this IEnumerable<FooHoge> me, FooHoge targ)
    {
        return me.where(l => /* lの各プロパティをtargと比較 */)
                 .firstOrDefault()
    }

    // 以下、派生させた型ごとにオーバーロードした拡張メソッドを増やす

}


こんな感じ。

んでも……

僕の中では、実行時のTの型に応じたmatchedLineFilterが呼ばれるでしょーウフフフーという目論みだったわけだが、当然そうはならず。常にBaseHogeを引数とした、つまりnotImplementExceptionが吐き出されてしまう世界。

いわゆる静的な型の言語の、仮想メソッド呼び出しみたいな真似って呼び出し主の型に応じたアレしかしてくれないのね、きっと。
つまり、あまり内部実装的な部分までは僕はわからないのだが、これはジェネリック使ってようがどうしようが、静的に(コンパイル時に)決定できる型情報でのみ関数呼び出しのディスパッチが行われるのではーという推理的なものが脳裏にひらめいた。

つまり割とどうしようもないということ。多分StragegyとかVisitorパターンとかに沿う形で実装しなおせば良いのかもしれないけど、使い捨てコードだしそんな時間はない。そもそもあんま大げさなことはしたくない。型を見て自前でディスパッチとかやりたくねーし。リフレクションも書きたくない。
そこで僕は、hogeIsMatchをジェネリックを使わない形で書き直し、BaseHogeの派生型の数だけコピペして書き換えるという形で逃げた。


今回はまぁそんな感じで逃げたわけだが、これは掘り下げた方が良さそうな雰囲気なので掘り下げてみる。

たとえばScalaだとどうなんだろう。
とりあえず検証は明日やるとして

def matchedLineFilter[T](lines: List[T], baseLines: List[T]): List[T] = {
    val hoged = lines.map(line =>
            val matched = line match {
                case foo:FooHge => baseLines.matchedLineFilter(line)
                case bar:BarHoge => baseLines.matchedLineFilter(line)
                // ~~~
                case _ => throw new notImplementException()
            }

            // ~ なんかする ~

    return hoged
    }

こんな感じに……通るのかこれ……?
構文合ってるかどうかも心配だけど、まさしくただのかっこいいif文になっていて激しくいけてない。


と、今気がついた。
ようは動的言語よろしく、型の解決を実行時にやらせれば良いのだ。
VBは元々それが出来る。そしてC#にはdynamicが導入されている。なので最初の例の、baselinesだけでもdynamicでとってやれば、matchedLineFilterの引数の型の解決が実行時にまで遅延して、結果として動的な型に応じたメソッドが呼び出されるのではなかろうか。
うん、これはいかにも合法な予感がするぞ? 明日試してみよう。
ただ、相当な回数呼び出されるのでパフォーマンスが心配だ。いざとなったらメモ化みたいな感じでデリゲートをキャッシュしたりすれば良いのかしらん? いやでも、確かdynamicは動的に生成したコードのキャッシュを保持した記憶があるので、余計なことはせんでいいのかもしれない。ここのところも明日調査すべし。

逃げっぽいけど、こういう柔軟な対応が出来るのが.NETの良いところですなぁ。まだ解決するかわからねーけど。


出来れば、もう少し型安全な形で実装できる方法を探りたい。拡張メソッドで型のパターンマッチのような動作が実現出来るときいたことがあるが、やはりそういったものはどうなのかなー読みにくくなりそうだなーと不安なので、つまるところ書きなぐりであっても最初からもう少しまとまった設計で実装出来るようになったほうが良いのかもねなどととりとめもなく。今回に関していえば、ちゃんと書き直せばこういう小手先のアレをせんでも良いスクリプトなので。