2013-02-15 60 views
14

我在尋找樣品使用這樣的事情:約束一般是空類型

Foo<string> stringFoo = new Foo<string>("The answer is"); 
Foo<int> intFoo = new Foo<int>(42); 
// The Value of intFoo & stringFoo are strongly typed 
stringFoo.Nullify(); 
intFoo.Nullify(); 
if (stringFoo == null && intFoo == null) 
    MessageBox.Show("Both are null); 

鑑於這種類Foo,我可以自動換行牛逼到一個可空:

public class Foo1<T> 
    where T : struct 
{ 
    private T? _value; 

    public Foo(T? initValue) 
    { 
     _value = initValue; 
    } 

    public T? Value { get { return _value; } } 

    public void Nullify { _value = null; } 

} 

這適用於基元,但不適用於String或其他類。

下一頁味適用於字符串,但不是基本類型:

public class Foo2<T> 
{ 
    private T _value; 

    public Foo(T initValue) 
    { 
     _value = initValue; 
    } 

    public T Value { get { return _value; } } 

    public void Nullify { _value = default(T); } 

} 

我可以用Nullable<int>爲foo2的,代碼將工作是這樣的:

Foo2<int?> intFoo = new Foo<int?>(42); 

但是,這是很容易出錯,因爲它失敗爲Foo2。如果我可以將T約束爲支持可空性的類型,那就沒問題了。

所以,所有這一切,有什麼辦法來約束T爲可空類型?

一些附加說明:.NET 4.0,VS2010。我在這裏找到了一個類似的問題,但沒有成功的答案。

回答

9

你也許能夠使Foo<T>內部的構造,並要求新的情況下,只能通過工廠類創建:

public class Foo<T> 
{ 
    private T value; 
    internal Foo(T value) 
    { 
     this.value = value; 
    } 

    public void Nullify() 
    { 
     this.value = default(T); 
    } 

    public T Value { get { return this.value; } } 
} 

public class Foo 
{ 
    public static Foo<T> Create<T>(T value) where T : class 
    { 
     return new Foo<T>(value); 
    } 

    public static Foo<T?> Create<T>(T? value) where T : struct 
    { 
     return new Foo<T?>(value); 
    } 
} 
+0

有趣。在現實生活中,Foo更復雜,並且由框架自動創建。我需要看看是否有方法鉤住/定製創建對象的方式,以使這種方法可行。 – tcarvin 2013-02-15 14:15:59

+0

接受,因爲它對庫的內部客戶(設計/編譯時與運行時間)給予最少的驚喜。但@JonSkeet使用的測試也很棒! – tcarvin 2013-02-20 13:30:04

17

有沒有限制,你可以申請,但你可以測試它在執行時:

if (default(T) != null) 
{ 
    throw new SomeAppropriateException(typeof(T) + " is not a nullable type"); 
} 

你甚至可以將它放入一個靜態構造函數,這將確保它只能執行一旦每構造類型 - 任何地方試圖使用Foo<int>將忽略TypeInitializerException很難。這對於public API來說並不是非常友好,但我認爲它對於內部API來說是合理的。

編輯:有是使其更難創建Foo<int>實例之一可怕方式...你可以使用ghastly code in this blog post(使用重載決策規則與缺省參數和一些約束泛型類型一起)和標記過載,其目標是不可空值類型,因爲過時而帶有錯誤。這樣,Foo<int>仍然是一個有效的類型,但你很難創建它的一個實例。我不會建議你這樣做雖然...

+0

@NikolayKhil:你在這裏回覆的評論已經消失了,但是這個答案中的代碼*將會爲'int?'工作,因爲無效性檢查對於可空類型參數「做正確的事情」。 – 2013-02-15 13:34:14

+0

我是錯的還是可以爲空的類型都是結構體?我的意思是「可空類型」沒有限制,但是struct有一個約束。但是我做了我自己的測試,如果你給一些泛型參數賦予T:struct約束,那麼以後你不能使用可爲空的類型作爲泛型參數。例如,X 將不適用於此約束。 – 2013-02-15 13:49:03

+0

顯然這是因爲int?是可空的和Nullable是一個引用類型,但也許.NET devteam可以做出某種語法糖來允許這種用例 – 2013-02-15 13:50:15

0

我不喜歡它,就像Foo1的語法,但這裏是Foo3:

public class Foo3<T> 
    where T : struct 
{ 

    private T _value; 
    private T _nullValue; 

    public Foo3(T initValue) 
     : this(initValue, default(T)) 
    { 
    } 

    public Foo3(T initValue, T nullValue) 
    { 
     _value = initValue; 
     _nullValue = nullValue; 
    } 

    public T Value { get { return _value; } } 

    public bool IsNull 
    { 
     get 
     { 
      return object.Equals(_value, _nullValue); 
     } 
    } 

    public void Nullify() { _value = _nullValue; } 

} 

然後我的使用就變成了:

Foo3<string> stringFoo = new Foo<string>("The answer is"); 
Foo3<int> intFoo = new Foo<int>(42, int.MinValue); 
stringFoo.Nullify(); 
intFoo.Nullify(); 
if (stringFoo.IsNull && intFoo.IsNull) 
    MessageBox.Show("Both are null); 

這仍然是錯誤的,因爲獲取Foo3(和Foo2)的Value屬性不是直截了當的。 Foo1是最好的,因爲自動包裝的值將無效支持。

我可能只需要ValueTypeFoo和ObjectFoo並處理兩個版本。