深入理解泛型(一)

2021-08-04 00:14:45 字數 4404 閱讀 8965

一、型別推斷

在我們寫泛型**的時候經常有大量的""符號,這樣有時候**一多,也難免會讓開發者在閱讀**過程中會覺得有點暈的,此時我們覺得暈的時候肯定就會這樣想:是不是能夠省掉一些""符號的呢?你有這種需求了, 當然微軟這位好人肯定也會幫你解決問題的,這樣就有了我們這部分的內容——型別推斷

,意味著編譯器會在呼叫乙個泛型方法時自動判斷要使用的型別,(這裡要注意的是:型別推斷只使用於泛型方法,不適用於泛型型別),下面是演示**:

using system;

namespace 型別推斷例子

// 型別推斷的demo

private static void genericmethodtest(ref t t1,ref t t2)}}

**中都有詳細的注釋,這裡就不解釋了。

二、型別約束

如果大家看了我的上乙個專題的話,就應該會注意到我在實現泛型類的時候用到了where

t : icomparable,在上乙個專題並沒有和大家介紹這個是泛型的什麼用法,這個用法就是這個部分要講的型別約束,其實where

t : icomparable這句**也很好理解的,猜猜也明白的(如果是我不知道的話,應該是猜型別引數t要滿足icomparable這個介面條件,因為where就代表符合什麼條件的意思,然而真真意思也確實如此的)下面就讓我們具體看看泛型中的型別引數有哪幾種約束的。   首先,編譯泛型**時,c#編譯器肯定會對**進行分析,如果我們像下面定義乙個泛型型別方法時,編譯器就會報錯:

// 比較兩個數的大小,返回大的那個

private static t max(t obj1, t obj2)

return obj2;

}

如果像上面一樣定義泛型方法時,c#編譯器會提示錯誤資訊:「t」不包含「compareto」的定義,並且找不到可接受型別為「t」的第乙個引數的擴充套件方法「compareto」。 這是因為此時型別引數t可以為任意型別,然而許多態別都沒有提供compareto方法,所以c#編譯器不能編譯上面的**,這時候我們(編譯器也是這麼想的

)。所以上面的**可以指定乙個型別約束,讓c#編譯器知道這個型別引數一定會有compareto方法的,這樣編譯器就不會報錯了,我們可以將上面**改為(**中t:icomparable為型別引數t指定的型別實參都必須實現泛型icomparable介面):

// 比較兩個數的大小,返回大的那個

private static t max(t obj1, t obj2) where t:icomparable

return obj2;

}

型別約束就是用where 關鍵字來限制能指定型別實參的型別數量,如上面的where t:icomparable語句。c# 中有4種約束可以使用,然而這4種約束的語法都差不多。(約束要放在泛型方法或泛型型別宣告的末尾,並且要使用where關鍵字)

(1) 引用型別約束

表示形式為 t:class, 確保傳遞的型別實參必須是引用型別(注意約束的型別引數和型別本身沒有關係,意思就是說定義乙個泛型結構體時,泛型型別一樣可以約束為引用型別,此時結構體型別本身是值型別,而型別引數約束為引用型別),可以為任何的類、介面、委託或陣列等;但是注意不能指定下面特殊的引用型別:system.object,system.array,system.delegate,system.multicastdelegate,system.valuetype,system.enum和system.void.

如下面定義的泛型類:

using system.io;  

public class samplereferencewhere t : stream

}

上面**中型別引數t設定了引用型別約束,where t:stream的意思就是告訴編譯器,傳入的型別實參必須是system.io.stream或者從stream中派生的乙個型別,如果乙個型別引數沒有指定約束,則預設t為system.object型別(相當於乙個預設約束一樣,就想每個類如果沒有指定建構函式就會有預設的無引數建構函式,如果指定了帶引數的建構函式,編譯器就不會生成乙個預設的建構函式)。然而,如果我們在**中顯示指定system.object約束時,此時會編譯器會報錯:約束不能是特殊類「object」(這裡大家可以自己試試看的)

(2)值型別約束

表示形式為t:struct,確保傳遞的型別實參時值型別,其中包括列舉,但是可空型別排除,(可空型別將會在後面專題有所介紹),如下面的示例:

// 值型別約束

public class samplevaluetypewhere t : struct

}

在上面**中,new t()是可以通過編譯的,因為t 是乙個值型別,而所有值型別都有乙個公共的無參建構函式,然而,如果t不約束,或約束為引用型別時,此時上面的**就會報錯,因為有的引用型別沒有公共的無參建構函式的。

(3)建構函式型別約束

表示形式為t:new(),如果型別引數有多個約束時,此約束必須為最後指定。確保指定的型別實參有乙個公共無參建構函式的非抽象型別,這適用於:所有值型別;所有非靜態、非抽象、沒有顯示宣告的建構函式的類(前面括號中已經說了,如果顯示宣告帶引數的建構函式,則編譯器就不會為類生成乙個預設的無參建構函式,大家可以通過il反匯程式設計序檢視下的,這裡就不貼圖了);顯示宣告了乙個公共無參建構函式的所有非抽象類。(注意: 如果同時指定構造器約束和struct約束,c#編譯器會認為這是乙個錯誤,因為這樣的指定是多餘的,所有值型別都隱式提供乙個無參公共建構函式,就如定義介面指定訪問型別為public一樣,編譯器也會報錯,因為介面一定是public的,這樣的做只多餘的,所以會報錯。)

(4)轉換型別約束

表示形式為 t:基類名 (確保指定的型別實參必須是基類或派生自基類的子類)或t:介面名(確保指定的型別實參必須是介面或實現了該介面的類)或t:u(為 t 提供的型別引數必須是為 u 提供的引數或派生自為 u 提供的引數)。轉換約束的例子如下: 宣告

已構造型別的例子

class samplewhere t: stream

sample有效的

sample無效的

class samplewhere t:  idisposable

sample有效的

sample無效的

class samplewhere t: u

sample有效的

sample無效的

(5)組合約束(第五種約束就是前面的4種約束的組合)

將多個不同種類的約束合併在一起的情況就是組合約束了。(注意,沒有任何型別即時引用型別又是值型別的,所以引用約束和值約束不能同時使用)如果存在多個轉換型別約束時,如果其中乙個是類,則類必須放在介面的前面。不同的型別引數可以有不同的約束,但是他們分別要由乙個單獨的where關鍵字。下面看一些有效和無效的例子來讓大家加深印象:

有效:class samplewhere t:class, idisposable, new();

class samplewhere t:class where u: struct

無效的:

class samplewhere t: class, struct (沒有任何型別即時引用型別又是值型別的,所以為無效的)

class samplewhere t: stream, class (引用型別約束應該為第乙個約束,放在最前面,所以為無效的)

class samplewhere t: new(), stream (建構函式約束必須放在最後面,所以為無效)

class samplewhere t: idisposable, stream(類必須放在介面前面,所以為無效的)

class samplewhere t: struct where u:class, t (型別形參「t」具有「struct」約束,因此「t」不能用作「u」的約束,所以為無效的)

class samplewhere t:stream, u:idisposable(不同的型別引數可以有不同的約束,但是他們分別要由乙個單獨的where關鍵字,所以為無效的)

三、利用反射呼叫泛型方法

下面就直接通過乙個例子來演示如何利用反射來動態呼叫泛型方法的(關於反射的內容可以我部落格中的這篇文章:

),演示**如下:

using system;

using system.reflection;

namespace reflectiongenericmethod

}public class test}}

上面**在呼叫泛型方法時傳入的兩個實參都是null,傳入第乙個為null是因為呼叫的是乙個靜態方法, 第二null是因為呼叫的方法是個無參的方法。 執行結果截圖(結果是輸出出 型別實參的型別,結果和我們預期的一樣):

四、小結

泛型專題中用到的所有demo的源**:

C 泛型深入理解介紹

引言 在上乙個專題中介紹了c 2.0 中引入泛型的原因以及有了泛型後所帶來的好處,然而上一專題相當於是介紹了泛型的一些基本知識的,對於泛型的效能為什麼會比非泛型的效能高卻沒有給出理由,所以在這個專題就中將會介紹原因和一些關於泛型的其他知識。一 泛型型別和型別引數 泛型型別和其他int,string一...

Oracle Number型的深入理解

number資料型別 number precision,scale a precision表示數字中的有效位 如果沒有指定precision的話,oracle將使用38作為精度。b 如果scale大於零,表示數字精確到小數點右邊的位數 scale預設設定為0 如果scale小於零,oracle將把該...

深入理解泛型(二) 協變性和逆變性

引言 在c 2.0中泛型並不支援可變性的 可變性指的就是協變性和逆變性 我們知道在物件導向的繼承中就具有可變性,當方法宣告返回型別為stream,我們可以在實現中返回乙個filestream的型別,此時就存在乙個隱式的轉化 從filestream型別 子類引用 stream型別 父類引用 並且引用型...