2

問題は次のとおりです。ジェネリック型の出力パラメーターを持つジェネリック関数が必要です。ジェネリック型をref型に限定すればもちろん問題ありません。しかし、私は完全に無制限のジェネリック型が欲しかったのです! new() や class/struct-restrictions はありません!

public class A
{ }

public class B<T> : A
{
  public T t;
  public B(T i)
  {
    this.t = t;
  }
}

public static class S
{
  public static bool Test<T>(ref A a, out T t)
  {
    C<T> c = a as C<T>;
    if(c != null)
    {
      t = c.t;
      return true;
    }
    else
      return false;
  }
}

class Run
{
  static void Main(string[] args)
  {
    C<SomeType> c = new C<SomeType>(new SomeType(...));

    SomeType thing;
    S.Test<SomeType>(c,thing);
  }
}

上記のコードは、私がやりたいことを示しています。out パラメータを設定したいのですが、描かれているものと同様の条件下でのみです。の偽のケースでは、Test(...)私は の値にまったく興味がありませんout t。しかし、上記はもちろん機能するコードではありません。上記の問題は、outパラメーターを初期化する必要があることです。しかし、おそらく初期化は高価な場合があり(のタイプによって異なりますT)、コンパイラが文句を言わないようにするためだけにダミークラスインスタンスを初期化したくありません。したがって、質問は次のようになります:不明な型をどのように初期化しますか (また、それがクラスの場合は null に初期化されていることを確認します)??

理論的には、次のようなものを書くことができるはずです

public static bool Test<T>(ref A a, out T t)
{
  //...
  else
  {
    if(typeof(T).IsValueType)
    {
      t = (T)0; //Can't cast from int to T error (Yeah, this is a little tricky...)
      //OR:
      t = new T(); //No-no, doesn't have the new()-restriction on T (But we know it's a value type... life sucks)
    }
    else
      t = null; //Error: Can't set to null! T could be valueType! (No it CAN'T... again life sucks)
    return false;
  }
}

しかし、残念ながら、それはそれほど単純ではありません。最初の問題は、T が値型の場合、それを作成できるはずですが、コンパイラが許可しないことです。2 番目の問題も同様です。「値型である可能性があります!」- いいえ、そうではないことを確認しました。動作するはずですが、動作しません。とてもうるさい。

Ok。そこで、私たちは創造性を発揮し始めます...結局のところ、Object と呼ばれるこの素晴らしいクラスがあり、C# っぽいすべてのものと特別な関係があります。

public static bool Test<T>(ref A a, out T t)
{
  //...
  else
  {
    if(typeof(T).IsValueType)
    {
      t = (T)(object)0; //Works ONLY if T is valuetype: int, otherwise you get a "must be less than infinity"-error.
    }
    else
    {
      t = (T)(object)null; //Null ref exception in the cast...
    }
    return false;
  }
}

これは少なくともコンパイルされます。しかし、それはまだゴミです。ランタイムエラーが豊富。値型の問題は、オブジェクト型が実際の型を覚えていて、何か他のものにキャストしようとすると...奇妙なことが起こることです(無限?本当に??)まあ、これはうまくいくはずです! だから、もっとクリエイティブになりましょう!

public static bool Test<T>(ref A a, out T t)
{
  //...
  else
  {
    if(typeof(T).IsValueType)
    {
      //... still rubbish here
    }
    else
    {
      object o = null;
      t = (T)o; //Tricked you stupid compiler!
    }
    return false;
  }
}

それは正しい!ばかげた取るに足らない変更に見えます...しかし、これはコンパイルされます-そして、非値型の場合、これが実行され、まさに私たちが望む結果が得られます! T が参照型の場合、null に初期化されます。値型の問題はまだあります。ややしぶしぶ創造性は、リフレクションに注意を向けます。リフレクション関連のものをランダムに掘り下げて、試してみる価値のあるものを探した後 (いいえ! 値型のコンストラクターを取得できず、null を返します)、msdn に関する小さなメモに出くわしました:

「インスタンス コンストラクターを持たない値型のインスタンスを作成するには、CreateInstance メソッドを使用します。」

入力CreateInstance<T>()- http://msdn.microsoft.com/en-us/library/0hcyx2kd.aspx

「CreateInstance ジェネリック メソッドは、型パラメーターで指定された型のインスタンス化を実装するためにコンパイラによって使用されます。」

今、私たちはどこかに到達しています!確かにそれは言う

「一般に、アプリケーション コードで CreateInstance を使用することはありません。これは、型がコンパイル時に認識されている必要があるためです。コンパイル時に型が認識されている場合は、通常のインスタンス化構文を使用できます (C# の new 演算子、Visual Basic の New 、C++ では gcnew)」。

しかし、まあ、私たちは一般的なことをまったく行っていません。私たちは創造的なモードにあり、コンパイラは私たちに対して不機嫌です。試してみることを完全に正当化します。

public static bool Test<T>(ref A a, out T t)
{
  //...
  else
  {
    if(typeof(T).IsValueType)
    {
      t = Activator.CreateInstance<T>(); //Probably not your everyday code...
    }
    else
    {
      object o = null;
      t = (T)o;
    }
    return false;
  }
}

そしてバム!それだけでした!それは完全にうまく機能します!以下は、VS2010SP1 と MonoDevelop (Unity3.4 を使用) の両方でテストおよび実行されたコードの一部です。

システムを使用する;

namespace GenericUnrestrictedOutParam
{
    class Program
    {
        class TestClass
        {
            public int i;
        }

        struct TestStruct
        {
            int i;
            TestClass thing;
        };

        public static void NullWorkaround<T>(out T anything)
        {
            if (typeof(T).IsValueType)
            {
                anything = Activator.CreateInstance<T>();
            }
            else
            {
                object o = null;
                anything = (T)o;
            }
        }

        static void Main(string[] args)
        {
            int i;
            float f;
            string s;
            TestStruct ts;
            TestClass c;

            NullWorkaround<int>(out i);
            NullWorkaround<float>(out f);
            NullWorkaround<string>(out s);
            NullWorkaround<TestStruct>(out ts);
            NullWorkaround<TestClass>(out c);
        } //Breakpoint here for value-checking
    }
}

そして輝かしい「出力」(locals-panel @ breakpoint から):

        args    {string[0]} string[]
        i   0   int
        f   0.0 float
        s   null    string
-       ts  {GenericUnrestrictedOutParam.Program.TestStruct}    GenericUnrestrictedOutParam.Program.TestStruct
          i 0   int
          thing null    GenericUnrestrictedOutParam.Program.TestClass
        c   null    GenericUnrestrictedOutParam.Program.TestClass

値とクラス型を含む構造体も美しく処理されます。値型は 0、クラス インスタンスは null です。 任務完了!

4

1 に答える 1

9

あなたの回避策は不要のようです -default(T)参照型と値型の両方で機能する必要があるだけです:

public static bool Test<T>(ref A a, out T t)
{
  t = default(T);
  return true;
}
于 2011-12-11T06:28:19.563 に答える