LoginSignup
4
4

More than 5 years have passed since last update.

Genericsメソッドとparamsの優先順位

Posted at

例えば

渡された配列なりListなりの中身を標準出力に全部出力するDebug用メソッドを用意したとして、とりあえずはこうなると思うんですよね

    public static class DebugUtil
    {
        public static void ListDump<T>(IEnumerable<T> datas)
        {
            foreach (var data in datas)
            {
                Console.WriteLine(data);
            }
        }
    }

引数をIEnumerable<T>にしておけば

        static void Main(string[] args)
        {
            var data = new[] {1, 2, 3, 4, 5};

            var list = new List<string>();
            list.Add("Hello");
            list.Add("Nice");
            list.Add("Too");
            list.Add("Meet");
            list.Add("You");

            DebugUtil.ListDump(data);
            DebugUtil.ListDump(list);
        }

配列でもListでも対応できるって寸法です。

ここで辞めておけばよかったんですが、さらに欲をかいて可変長引数対応もしたいな、ってなるわけです。
ちょっと追加してこんな感じ

    public static class DebugUtil
    {
        public static void ListDump<T>(params T[] datas)
        {
            ListDump((IEnumerable<T>)datas);//キャストすることで、もう一つのListDumpを呼び出し
        }

        public static void ListDump<T>(IEnumerable<T> datas)
        {
            foreach (var data in datas)
            {
                Console.WriteLine(data);
            }
        }
    }

こうしておけば

            DebugUtil.ListDump("ほげ","ふが");

みたいな感じ可変長引数も対応! 便利!

んなこたない

ところが、これには罠が。

        static void Main(string[] args)
        {
            var data = new[] {1, 2, 3, 4, 5};

            var list = new List<string>();
            list.Add("Hello");
            list.Add("Nice");
            list.Add("Too");
            list.Add("Meet");
            list.Add("You");

            DebugUtil.ListDump(data);//OK
            DebugUtil.ListDump(list);//NG
            DebugUtil.ListDump("ほげ","ふが");//OK
        }

このDebugUtil.ListDump(list);

System.Collections.Generic.List`1[System.String]

と出力されてしまうようになってしまいました。

問題はオーバーロードされたメソッドの処理順

まぁ、当たり前っちゃぁ当たり前なのかもですが、params T[]を引数に取るListDumpの方が先に処理されちゃったわけです。

すると、T=List<string> という事になり、List<string>の配列 がdatasに入った形に。
ぐぬぬー。

というわけで、目的通りの処理にするためには

DebugUtil.ListDump((IEnumerable<string>)list);

のように、明示的にキャストしてあげる必要がある。というわけでした。

めでたし・めでたくもなし。

おまけ

Genericsを辞めて、

    public static class DebugUtil
    {
        public static void ListDump(params object[] datas)
        {
            ListDump((IEnumerable) datas);
        }

        public static void ListDump(IEnumerable datas)
        {
            foreach (var data in datas)
            {
                Console.WriteLine(data);
            }
        }
    }

こんな感じで作ると、また挙動が変わって、DebugUtil.ListDump(list);ListDump(IEnumerable datas)に処理されるので思った通りの挙動になったりします。

これはobjectへのキャストよりもIEnumrableへのアップキャストの方が階層が浅い(って言っていいのかな?)からだと思われます。

おまけのおまけ

    public static class DebugUtil
    {
        public static void ListDump(params object[] datas)
        {
            if (datas.Length == 0) return;
            if (datas.Length == 1)
            {
                var data = datas[0] as ICollection;
                if (data != null)
                {
                    ListDump(data);
                    return;
                }
                Console.WriteLine(datas[0]);
                return;
            }

            ListDump((IEnumerable) datas);
        }

        public static void ListDump(IEnumerable datas)
        {
            foreach (var data in datas)
            {
                ListDump(data);
            }
        }
    }

さらに、なんとなく適当に作ってみましたが、こんな感じで再起で行ったり来たりすると、配列の配列の配列のList みたいな変態的な構造も中身が全部表示される…かも。(あまりテストしてないです)
にしたって、もうちょっとマシな方法があるはず・・・。

4
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4