首先我要介绍的技巧是如何提供类型参数之间的转换.我们知道,.NET泛型将每个类型参数理解为一个独立的类型.如果不通过约束指定,编译器不会对类型参数所代表的类型做任何假设.也就是说,如果在某个上下文中有两个不同的类型参数U和V,编译器不会知道运行时他们代表的真实类型能否进行类型转换,因此会拒绝编译如下代码:
Public Function GenericCast( Of U, V )( ByVal obj As U ) As V Return CType( obj, V )End Function
除非我们加以约束U是V的子类.这显然是为了类型安全做出的考虑,因为.NET这种跨程序集的泛型无法在编译时做出足够的检查来确保类型安全.但是限制了这种操作,就妨碍了我们做事,很多代码因此写不出来.于是,我们可以利用.NET所有类型的基类Object来绕过这一限制:
Return DirectCast( DirectCast( obj, Object ), V )
VB的DirectCast运算符在泛型的类型参数上作用与C#的括号运算符相同.也就是说,这段代码用C#写起来是这样:
return ( V )( object )obj;
这样,即使不约束U和V之间的关系,这段代码也能编译了.但是他的功能却不能令我们满意.这样写出的类型转换实际上还是仅当U是V本身或其子类的时候才能转换成功.而其他一切情况都会转换失败.不管U和V的运行时类型之间是否定义有其他类型转换规则.这显然不合我们意愿,我们希望
int与double之间的转换等语言内置的类型转换都能够自动进行,否则就和约束没什么两样了.对VB用户来说有一个极为简单的解决方案——把第二次DirectCast变成CType:
Public Function GenericCast( Of U, V )( ByVal obj As U ) As V Return CType( DirectCast( obj, Object ), V )End Function
现在,GenericCast泛型方法就能执行
int与double等内置规则的转换了.很神奇?因为CType运算符在编译时自动调用了VB运行库的转换函数,该函数在运行期间对泛型类型参数的真实类型做了检查.而且这个过程的性能完全可以接受.
现在VB的用户已经轻松享受这一功能了.但是C#的事情还没完,因为C#没有如此智能的类型转换运算符,因此就需要手工实现VB运行库所代办的那些任务.其实就是利用了一下IConvertible:
static V GenericCast<U, V>( U obj )
{
IConvertible convertibleObj = obj as IConvertible;
if ( convertibleObj != null )
{
Type t = typeof( V );
switch ( Type.GetTypeCode( t ) )
{
case TypeCode.Boolean:
return ( V )( object )convertibleObj.ToBoolean( null );
case TypeCode.Byte:
return ( V )( object )convertibleObj.ToByte( null );
case TypeCode.Char:
return ( V )( object )convertibleObj.ToChar( null );
//.........
default:
//None of them, use the following default way..
break;
}
}
return ( V )( object )obj;
}
不过这个方法看起来不但很丑、很麻烦,功能上还达不到VB版.所以建议您用到这种类型参数之间的转换,就用VB封装这一功能,然后做成dll供C#调用吧.
经过研究发现Convert.ChangeType方法已经封装了IConvertible判断的那些代码,因此C#版可采用这种写法:
static V GenericCast<U, V>( U obj )
{
return ( V )Convert.ChangeType( obj, typeof( V ) );
}
不过它仍然没有VB版的CType功能丰富.此外我还发现了VB运行库的ChangeType还有支持用户自定义类型转换运算符的功能.所以终极版本如下:
Public Function GenericCast( Of U, V )( ByVal obj As U ) As VTry Return CType( DirectCast( obj, Object ), V ) Catch ex As InvalidCastException Return DirectCast(_CompilerServices.Conversions.ChangeType( obj, GetType( V ) ), V ) End TryEnd Function