什么时候我不应该使用泛型?对泛型我应该使用什么命名规范?我应该在泛型接口上面添加约束吗?
如何处置(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);
}
}
}
静态构造器对每一个应用程序域的每一个类型只执行一次,而且是在类型第一次被请求实例化之前。尽管你有一些通过编程的方式来在运行时进行判断和执行检查,但是这种在静态构造器里面执行约束验证的技术,对任何无法在编译时进行检查的约束都适用。
如何处置(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);
}
}
}
静态构造器对每一个应用程序域的每一个类型只执行一次,而且是在类型第一次被请求实例化之前。尽管你有一些通过编程的方式来在运行时进行判断和执行检查,但是这种在静态构造器里面执行约束验证的技术,对任何无法在编译时进行检查的约束都适用。