none
ジェネリックで型<T>の関数を呼び出す方法は? RRS feed

  • 質問

  • 始めまして! Borland C++ Builder からの移行で悩んでいます。

    Borlandでは int StrToIntDef(String s,int default) みたいな関数があって、文字列が変換できない場合には default 値を戻すような関数があったのですが、同じような関数は数行書いた関数で実現は出来るのですが、型が沢山あるので、ジェネリックで書こうとしたのですが、「<T>型は指定したコンテキストでは有効ではありません」と撥ねられます。

     

    Math.ToIntDef() や TolongDef()はOKなのですが、赤で書いた部分がコンパイルエラーに成ります。

    まだC#を始めたばかりで、C#への理解が深いとは言えない状況なのですが、これはジェネリックでは出来ない実装なのでしょうか?

     

    namespace Library
    {
     public class ToDef<T>
     {
         public static T TryParse(string s, T def)
         {
             T result = default(T);

             if (T.TryParse(s, out result) == false)
             {
                 result = def;
             }
             return result;
         }
     }

     

     class Math
     {
      public static int ToIntDef(string s, int def)
      {
       int result;

       if (int.TryParse(s, out result) == false)
       {
        result = def;
       }
       return result;
      }

      public static long TolongDef(string s, long def)
      {
       long result;

       if (!long.TryParse(s, out result))
       {
        result = def;
       }
       return result;
      }
     }
    }

    2007年10月16日 9:10

回答

すべての返信

  • 基本的には、このようなシナリオでは、where 句を使って制約を宣言します。

     

    http://msdn2.microsoft.com/ja-jp/library/6b0scde8(VS.80).aspx

     

    しかし、この方法では static 関数を呼ぶことはできません。

    この例の場合は、typeof(T) から reflection を使うことで一般的に記述することができます。

    2007年10月16日 11:39
  • アドバイスありがとうございます。 typeof(T) から reflection でヘルプを探しまくって

     

     public class ToDef<T> where T : new()
     {
      public static T TryParse(string s, T def)
      {
       Type   type  = typeof(T);
       Type[] types = new Type[] { typeof(string), typeof(T).MakeByRefType() };
       MethodInfo mi = type.GetMethod("TryParse", types);

       T        result = new T();
       object[] param  = new object[] { s, result };

       if ((bool)mi.Invoke(null, param) == false)
       {
        result = def;
       }
       return (T)param[1];
      }
     }

     

    結果的に、こんな風に書けば出来ました。

    ついでに質問なのですが、param[1] に結果は入るのですが、resultには反映されないのですね。

    param の配列に参照の書き方でオブジェクトを渡す方法はあるのでしょうか?

     

    2007年10月17日 1:59
  •  
    コード ブロック
    object[] param  = new object[] { s, result };

     

     

    のところは、
     
    コード ブロック
    object[] param  = new object[] { s, null };

     

     

    が正しいです。
    上でも動作しますが、result の param[1] への代入は無意味です。
     
    2007年10月17日 14:44
  • 割り込み&脱線すいません。

    気持はわかりますが、Reflectionはなるべく使わない方がよいと思います。
    似たようなメソッドの実装でしたら素直にオーバーロードで書く方が実行速度が断然速いし、おかしな間違いをしないと思います。

    たとえば…

    コード ブロック

        class ToDef
        {
            public static int TryParse(string s, int def)
            {
                int result;
                return (int.TryParse(s, out result)) ? result : def;
            }
            public static long TryParse(string s, long def)
            {
                long result;
                return (long.TryParse(s, out result)) ? result : def;
            }
            public static decimal TryParse(string s, decimal def)
            {
                decimal result;
                return (decimal.TryParse(s, out result)) ? result : def;
            }
            //以降必要な型の分だけ書く…
        }

     

     

    2007年10月17日 15:39
  • お返事ありがとうございます。

    後者の場合には、どうやって TryParse の結果を受け取る事が出来るのでしょうか?

    2007年10月18日 0:54
  • お返事ありがとうございます。

     

    >似たようなメソッドの実装でしたら素直にオーバーロードで書く

     

    そうですね.、下手に悩むよりも、そっちの方が良い気もしてきました。

    ただどうしても、Reflection を使わざるを得ない時もありそうなので、良い勉強には成りました。

     

    C# って namespace が細かく分かれているもので、ほしい機能を絶対ある筈と思って探しているのですが、中々目的の機能を

    探すことが出来なかったり、すごく時間が掛かったりしています。言葉、用語が全然違うので捜し方が下手なのか、慣れてないのかだと思うのですが、どうも慣れないです。皆さん、どうやって必要な関数を探しているのでしょうか。とっても不思議です。

    慣れていくしかないんでしょうね。

    2007年10月18日 1:13
  • 後者の場合には、どうやって TryParse の結果を受け取る事が出来るのでしょうか?

     

    Nishizaka さんご自身もされていたように param[1] で受け取れます。

     

    TryParse の引数は (string value, out int result) といった感じですよね。

    仮に TryParse が params (可変長引数) で実装されていたとすると、

     

        bool TryParse(params object[] param)

        {

            string s = (string)param[0];

            if (s が int に変換可能か)

            {

                param[1] = s を int に変換した値;

                return true;

            }

            return false;

        }

     

    こんな感じでしょうか?

    第2引数は out なので param[1] に入っているものは参照されることはなく、単に param[1] に代入されるだけなわけです。

    2007年10月18日 1:29
  • Shinichi Aoyagi さん、ありがとうございます。

     

    >param[1] で受け取れます。

     

    成るほど、納得です。 object 配列に null で初期化していると言う意味なんですね。null を渡していると勘違いしていました。

     

    コード ブロック

     /// <summary>
     /// 文字を<T>型変換のジェネリック(デフォルト値付き)
     /// </summary>
     /// <typeparam name="T">型</typeparam>
     public class Math<T>
     {
      public static T ToDef(string s, T def)
      {
       Type     type  = typeof(T);
       Type  [] types = new Type  [] { typeof(string), typeof(T).MakeByRefType() };

       object[] param = new object[] { s, null };

       return ((bool)(type.GetMethod("TryParse", types).Invoke(null, param))) ? (T)param[1] : def;
      }
     }

     

     

    最終結果を書くと、こんな感じのジェネリックで良さそうです。(^^)
    2007年10月18日 2:04