什么時候我不應該使用泛型?對泛型我應該使用什么命名規范?我應該在泛型接口上面添加約束嗎?
如何處置(Dispose)泛型接口?可以對一般類型參數進行類型轉換嗎?
對泛型類如何同步多線程訪問?如何序列化泛型類?
什么時候我不應該使用泛型?
????????不使用泛型的主要原因就是跨目標(cross
-
targeting)——如果你要在.NET?
1
.1和.NET?
2
.0下編譯相同的代碼,那么由于只有.NET?
2
.0支持泛型,你就不能夠使用泛型。
對泛型我應該使用什么命名規范?
????????我建議使用一個單獨的大寫字母來表示一般類型參數。如果你對類型參數沒有其他的跟上下文有關的信息(additional?contextual?information),你應該使用字母T:
public
?
class
?MyClass
<
T
>
{
}
??????
?在所有其他場合下,微軟正式的對泛型的的命名規范指導是:
一般類型參數要有描述性的名字,除非一個單獨的字母已經表示得很清楚,再增加描述性的名字也沒有多大用處。
public
?
interface
?ISessionChannel
<
TSession
>
?
{
}
public
?
delegate
?TOutput?Converter
<
TInput,TOutput
>
(TInput?from);
可以考慮在一般類型參數的名字中表示出添加給該一般類型參數的約束。例如,一個被約束到ISession接口的參數可以起名為TSession。
我應該在泛型接口上面添加約束嗎?
????????接口可以為其使用的范型類型添加約束,例如:??
public
?
interface
?ILinkedList
<
T
>
?where?T?:?IComparable
<
T
>
{
}
??但是,你應該小心,在接口層面上定義約束還隱含有另外一層意思。為了強調接口與實現分離的思想,接口不應該包括任何一點實現的細節。雖然有很多方法可以用來實現范型接口,但是使用特定的類型參數畢竟是一種實現的細節。約束通常情況下會更加耦合(couple)接口和特定的實現。
????????更好的方法是,為實現范型接口的類添加約束,保持接口本身沒有約束:
public
?
class
?LinkedList
<
T
>
?:?ILinkedList
<
T
>
?where?T?:?IComparable
<
T
>
{
???
//
Rest?of?the?implementation??
}
如何處置(Dispose)泛型接口?
??????在C#和Visual?Basic中,如果你把一個一般類型參數的對象放在using語句中,編譯器無法知道客戶端(client)指定的實際類型是否支持IDisposable接口。因此編譯器不允許在using語句中使用一般類型參數的實例。
public
?
class
?MyClass
<
T
>
?
{
???
public
?
void
?SomeMethod(T?t)
???
{
??????
using
(t)
//
Does?not?compile?
??????
{
}
???}
}
當然,你可以強制約束類型參數支持IDisposable接口:
public
?
class
?MyClass
<
T
>
?where?T?:?IDisposable?
{
???
public
?
void
?SomeMethod(T?t)
???
{
??????
using
(t)
??????
{
}
???}
}
?????但是你不應該這么做。這樣做的問題在于你不能使用接口作為類型參數了,即使這個接口的基礎類型(underlying?type)支持IDisposable也不行:
public
?
interface
?IMyInterface
{}
public
?
class
?MyOtherClass?:?IMyInterface,IDisposable?
{
}
public
?
class
?MyClass
<
T
>
?where?T?:?IDisposable?
{
???
public
?
void
?SomeMethod(T?t)
???
{
??????
using
(t)
??????
{
}
???}
}
MyOtherClass?myOtherClass?
=
?
new
?MyOtherClass();
MyClass
<
IMyInterface
>
?obj?
=
?
new
?MyClass
<
IMyInterface
>
();
//
Does?not?compile
obj.SomeMethod(myOtherClass);?
??作為替代,我建議你在using語句里對一般類型參數使用C#中的as操作符或者Visual?Basic中的TryCast操作符來允許接口作為一般類型參數使用:
public
?
class
?MyClass
<
T
>
?
{
???
public
?
void
?SomeMethod(T?t)
???
{
??????
using
(t?
as
?IDisposable)
??????
{
}
???}
}
可以對一般類型參數進行類型轉換嗎?
????????對于隱式轉換,編譯器只允許將一般類型參數轉換為object類型,或者其約束里指定的那個類型:
interface
?ISomeInterface
{
}
class
?BaseClass
{
}
class
?MyClass
<
T
>
?where?T?:?BaseClass,ISomeInterface
{
???
void
?SomeMethod(T?t)
???
{
??????ISomeInterface?obj1?
=
?t;
??????BaseClass??????obj2?
=
?t;
??????
object
?????????obj3?
=
?t;
???}
}
這種隱式轉換當然是類型安全的,因為無效的轉換在編譯時就會被發現。
????????對于顯示轉換,編譯器允許將一般類型參數轉換到任何接口,但是不能轉換為類:
interface
?ISomeInterface
{
}
class
?SomeClass
{
}
class
?MyClass
<
T
>
?
{
???
void
?SomeMethod(T?t)
???
{
??????ISomeInterface?obj1?
=
?(ISomeInterface)t;
//
Compiles
??????SomeClass??????obj2?
=
?(SomeClass)t;?????
//
Does?not?compile
???}
}
?但是,你可以通過使用一個臨時的object類型變量來強制將一般類型參數轉到到任何其他類型:
class
?MyOtherClass
{
}
class
?MyClass
<
T
>
?
{
??
???
void
?SomeMethod(T?t)
???
???
{
??????
object
?temp?
=
?t;
??????MyOtherClass?obj?
=
?(MyOtherClass)temp;
???
???}
}
毫無疑問,這樣的顯示轉換是很危險的,因為如果實際使用的替代一般類型參數的類型不是從你要轉換到的類型那里繼承的話,就可能在運行時拋出異常。
????????為了避免這種轉換時有異常的風險,一個更好的辦法是使用is或者as操作符。如果一般類型參數是(
is
)要查詢的類型,
is
?操作符會返回true,而as操作符會在兩個類型兼容的時候執行轉換,否則將返回null。
public
?
class
?MyClass
<
T
>
?
{
???
public
?
void
?SomeMethod(T?t)
???
{
??????
if
(t?
is
?
int
)
??????
{
}
?
??????
if
(t?
is
?LinkedList
<
int
,
string
>
)
??????
{
}
??????
string
?str?
=
?t?
as
?
string
;
??????
if
(str?
!=
?
null
)
??????
{
}
??????LinkedList
<
int
,
string
>
?list?
=
?t?
as
?LinkedList
<
int
,
string
>
;
??????
if
(list?
!=
?
null
)
??????
{
}
???}
}
對泛型類如何同步多線程訪問?
????????通常來說,你不應該在一般類型參數上應用Monitor。這是因為Monitor只能用于引用類型。當你使用范型的時候,編譯器不能預先判斷你將會提供一個引用類型還是值類型的類型參數。在C#中,編譯器會允許你使用lock語句,但是如果你提供了一個值類型作為類型參數,lock語句在運行時將不起作用。在Visual?Basic中,編譯器如果不能確定一般類型參數是一個引用類型,它將不允許在一般類型參數上面使用SyncLock。
????????在C#和Visual?Basic中,唯一你可以安全地將一般類型參數鎖住的時候,是你將一般類型參數限制為引用類型,要么添加約束使其為引用類型,要么從一個基類中繼承:
public
?
class
?MyClass
<
T
>
?where?T?:?
class
{..}
public
?
class
?SomeClass
{
}
public
?
class
?MyClass
<
T
>
?where?T?:?SomeClass
{
}
?????然而,通常對于同步來說,最好避免部分地鎖住單獨的成員變量,因為這會增加死鎖的可能性。
如何序列化泛型類?
????????包括了一般類型參數作為成員的范型類是可以被標記為序列化的:
[Serializable]
public
?
class
?MySerializableClass
<
T
>
{
???T?m_T;
}
??但是,在這種情況下,只有指定的類型參數可以被序列化時,范型類才可以被序列化。看下面的代碼:
public
?
class
?SomeClass
{}
MySerializableClass
<
SomeClass
>
?obj;
??obj不能被序列化,因為類型參數SomeClass不可以被序列化。因此,MySerializableClass
<
T
>
可能可以,也可能不可以被序列化,取決于使用的一般類型參數。這樣可能導致在運行時丟失數據或者系統崩潰,因為客戶應用程序可能不能夠保持對象的狀態。
????????目前,.NET沒有提供將一般類型參數約束為可序列化的機制。解決辦法是在運行時在使用這個類型之前單獨進行檢查,并且在任何損害發生之前馬上中止使用。你可以把這個運行時的驗證放在靜態構造器里面:
[Serializable]
class
?MySerializableClass
<
T
>
{
???T?m_T;
???
static
?MySerializableClass()???
???
{
??????ConstrainType(
typeof
(T));
???}
???
static
?
void
?ConstrainType(Type?type)
???
{
??????
bool
?serializable?
=
?type.IsSerializable;
??????
if
(serializable?
==
?
false
)
??????
{
?????????
string
?message?
=
?
"
The?type?
"
?
+
?type?
+
?
"
?is?not?serializable
"
;
?????????
throw
?
new
?InvalidOperationException(message);
??????}
???}
}
?靜態構造器對每一個應用程序域的每一個類型只執行一次,而且是在類型第一次被請求實例化之前。盡管你有一些通過編程的方式來在運行時進行判斷和執行檢查,但是這種在靜態構造器里面執行約束驗證的技術,對任何無法在編譯時進行檢查的約束都適用。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

