2011-12-11 15 views
2

問題は次のとおりです。 汎用タイプのout-parameterを持つ汎用ファンクションが必要です。 ジェネリック型をref-typeに制限しますが、問題はありません。 しかし、私は完全に無制限のジェネリックタイプが欲しかった! new()やクラス/構造体制限はありません!タイプ制限なしの汎用アウトパラメータの設定方法

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番目の問題は似ています:「価値のあるタイプかもしれません! - いいえ、私はちょうどそうではないことを確認しました。それはが動作するはずですがそれはありません。とてもうるさい。

ですから、私たちは創造的になり始めます。結局のところ、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; 
    } 
} 

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

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がref型の場合は、nullに初期化されます。 値の型には依然として問題があります。やや気まぐれな創造性が、リフレクションに注目しています。私はmsdnの小さなメモを見つけました:

(英語) "インスタンス コンストラクタがない値型のインスタンスを作成するには、CreateInstanceメソッドを使用してください。"

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

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

今どこかに行ってきました。それは一般的に」

を言うんタイプはコンパイル時に知らなければならないからである。タイプはコンパイル時に知られて ある場合は、アプリケーション コード内のCreateInstanceのための使用は、通常のインスタンス化の構文は、そこができていないことを確認(C#ではnew オペレータ、Visual Basicでは新規、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; 
    } 
} 

とBAM!それだった!それは完全にsoo良い! 以下はVS2010SP1とMonoDevelop(Unity3.4)の両方でテストされ、実行されたコードです。

using System;

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 
    } 
} 

と(地元パネル@ブレークポイントから)栄光の「出力」:

 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です。 ミッションが達成されました!

+0

ケースの誰もが、私が代わりに出ての参照を使用することをお勧めしていただけに:その後、私はする必要がありませんので、 *私はそれをTest()に渡す前に*を初期化します。全体のアイデアを完全に破ります。 – AnorZaken

+1

質問は何ですか? – BrokenGlass

+0

TL; DRの途中であなた自身の問題を解決し終わったようです。質問をしたり、回答を投稿したりしても問題ありませんが、質問にではなく回答として回答を投稿してください。これは素晴らしい答えではないにもかかわらず、他の答えを探している場合でも機能します。もしあなたがそれをダンケルするとうまくいけば---うまくいきません:) –

答えて

8
あなたの回避策は不要と思われる

- あなただけの両方の参照と値の型のために働くdefault(T)が必要になります。

public static bool Test<T>(ref A a, out T t) 
{ 
    t = default(T); 
    return true; 
} 
+0

確かに、ハハ、私はそれが実行可能でなければならないことを知っていた...時々あなたは、最も単純なものを忘れる^^ – AnorZaken

関連する問題