C# 判断类型间能否隐式或强制类型转换,以及开放泛型类型转换

如果要判断某个实例是否与其它类型兼容,C# 已经提供了两个运算符 is 和 as,Type 类也提供了 IsAssignableFrom 方法来对两个类型进行判断。但他们实际上判断的是类型是否在继承层次结构中,而不是类型间是否可以进行转换。例如下面的代码:

1 long a = 0;
2 Console.WriteLine(a is int);
3 Console.WriteLine(typeof(long).IsAssignableFrom(typeof(int)));
4 Console.WriteLine(typeof(int).IsAssignableFrom(typeof(long)));

它们的返回值都是 False,但实际上 int 类型可以隐式转换为 long 类型的,long 类型也可以强制转换为 int 类型。如果希望知道一个类型能否隐式或强制转换为其它类型,这些已有的方法就力不能及了,只能自己来进行判断。

隐式类型转换

首先对隐式类型转换进行判断,隐式类型转换时,需要保证不会报错,数据不会有丢失。我目前总结出下面五点:

  1. 如果要转换到 object 类型,那么总是可以进行隐式类型转换,因为 object 类型是所有类型的基类。
  2. 兼容的类型总是可以进行隐式类型转换,例如子类转换为父类。这个是显然的,而且可以直接通过 Type.IsAssignableFrom 来判断。
  3. .NET 的一些内置类型间总是可以进行隐式类型转换,例如 int 可以隐式转换为 long,这个稍后会总结为一个表格。
  4. 存在隐式类型转换运算符的类型,这个是属于用户自定义的隐式类型转换方法,可以通过 Type.GetMethods 查找名为 op_Implicit 的方法来找到所有的自定义隐式类型转换。
  5. 除了上面的之外,.NET 里还有一个略微特殊的类型:Nullable<T>,T 类型总是可以隐式转换为 Nullable<T>,而且 Nullable<T1> 和 Nullable<T2> 类型之间的转换,需要看 T1 和 T2 能否进行隐式转换。因此这里需要特殊处理下,才能够正确的进行判断。

.Net 内置类型间的隐式转换,我总结成了下面的表格(未包含自身的转换):

要转换到的类型 来源类型
Int16 SByte, Byte
UInt16 Char, Byte
Int32 Char, SByte, Byte, Int16, UInt16
UInt32 Char, Byte, UInt16
Int64 Char, SByte, Byte, Int16, UInt16, Int32, UInt32
UInt64 Char, Byte, UInt16, UInt32
Single Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64
Double Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single
Decimal Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64

对于类型的自定义隐式类型转换,则有些需要特别注意的地方。

自定义隐式类型转换可以有两个方向:转换自和转换到,区别就是参数和返回值不同。例如,下面的方法表示 Test 类型可以隐式转换自 int 类型:

public static implicit operator Test(int t);

而下面的方法则表示 Test 类型可以隐式转换到 int 类型:

public static implicit operator int(Test t);

因此,在判断类型间是否可以隐式类型转换时,需要考虑这两种不同的情况,而且同时还可能存在兼容类型转换或内置类型转换。但是,却不可以进行两次隐式类型转换。

也就是说,假设 Test 类型可以隐式转换为 int 类型或 Test3 类型,Test3 类继承自 Test2 类,Test3 类可以隐式转换为 Test4 类,图 1 的绿色隐式类型转换是合法的,红色是非法的: 

图 1 合法和非法的隐式类型转换

这里的判断算法如图 2 所示,设要判断 fromType 能否隐式转换为 type 类型,取集合 S1 为 type 的隐式转换自方法集合,S2 为 fromType 的隐式转换到方法集合,那么分别判断 S1 中是否存在与 fromType 类型兼容或者可以进行内置类型转换的类型,和 S2 中是否存在与 type 类型兼容或者可以进行内置类型转换的类型。如果找到了这样的类型,就表示可以成功进行隐式类型转换。 

图 2 隐式类型转换判断算法

判断算法的核心代码如下:

1 /// <summary>
2 /// 确定当前的 <see cref=”System.Type”/> 的实例是否可以从指定 <see cref=”System.Type”/>
3 /// 的实例进行隐式类型转换。
4 /// </summary>
5 /// <param name=”type”>要判断的实例。</param>
6 /// <param name=”fromType”>要与当前类型进行比较的类型。</param>
7 /// <returns>如果当前 <see cref=”System.Type”/> 可以从 <paramref name=”fromType”/>
8 /// 的实例分配或进行隐式类型转换,则为 <c>true</c>;否则为 <c>false</c></returns>
9 public static bool IsImplicitFrom(this Type type, Type fromType)
10 {
11 if (type == null || fromType == null)
12 {
13 return false;
14 }
15 // 总是可以隐式类型转换为 Object。
16 if (type == typeof(object))
17 {
18 return true;
19 }
20 // 对 Nullable<T> 的支持。
21 Type[] genericArguments;
22 if (InInheritanceChain(typeof(Nullable<>), type, out genericArguments))
23 {
24 type = genericArguments[0];
25 if (InInheritanceChain(typeof(Nullable<>), fromType, out genericArguments))
26 {
27 fromType = genericArguments[0];
28 }
29 }
30 // 判断是否可以从实例分配。
31 if (IsAssignableFromEx(type, fromType))
32 {
33 return true;
34 }
35 // 对隐式类型转换运算符进行判断。
36 if (GetTypeOperators(type).Any(pair => pair.Value.HasFlag(OperatorType.ImplicitFrom) &&
37 IsAssignableFromEx(pair.Key, fromType)))
38 {
39 return true;
40 }
41 if (GetTypeOperators(fromType).Any(pair => pair.Value.HasFlag(OperatorType.ImplicitTo) &&
42 IsAssignableFromEx(type, pair.Key)))
43 {
44 return true;
45 }
46 return false;
47 }

下面附上一些隐式类型转换判断方法(IsImplicitFrom)和 Type.IsAssignableFrom 方法的对比:

1 Console.WriteLine(typeof(object).IsAssignableFrom(typeof(uint))); // True
2 Console.WriteLine(typeof(object).IsImplicitFrom(typeof(uint))); // True
3 Console.WriteLine(typeof(int).IsAssignableFrom(typeof(short))); // False
4 Console.WriteLine(typeof(int).IsImplicitFrom(typeof(short))); // True
5 Console.WriteLine(typeof(long?).IsAssignableFrom(typeof(int?))); // False
6 Console.WriteLine(typeof(long?).IsImplicitFrom(typeof(int?))); // True
7 Console.WriteLine(typeof(long).IsAssignableFrom(typeof(TestClass))); // False
8 Console.WriteLine(typeof(long).IsImplicitFrom(typeof(TestClass))); // True
9 class TestClass
10 {
11 public static implicit operator int(TestClass t) { return 1; }
12 }

强制类型转换

在进行强制类型转换时,无需保证数据不丢失,也无需保证类型一定能够兼容。判断的过程也类似于隐式类型转换,同样总结为五点:

  1. 总是可以与 object 类型进行相互转换。
  2. 兼容的类型不但可以从子类转换为父类,也可以从父类转换为子类。
  3. .NET 的一些内置类型间总是可以进行强制类型转换,这是另外一个表格。
  4. 存在隐式或显式类型转换运算符的类型,可以通过 Type.GetMethods 查找名为 op_Explicit 的方法来找到所有的自定义显式类型转换。
  5. 仍然要考虑 Nullable<T> 类型,不过在强制类型转换时,T 类型总是可以与 Nullable<T> 相互转换。

.Net 内置的强制类型转换比较简单,就是 Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double 和 Decimal 之间的相互转换。

强制类型转换的判断算法也是与隐式类型转换类似的,只不过是要考虑一下自定义的显式类型转换方法。

1 /// <summary>
2 /// 确定当前的 <see cref=”System.Type”/> 的实例是否可以从指定 <see cref=”System.Type”/>
3 /// 的实例进行强制类型转换。
4 /// </summary>
5 /// <param name=”type”>要判断的实例。</param>
6 /// <param name=”fromType”>要与当前类型进行比较的类型。</param>
7 /// <returns>如果当前 <see cref=”System.Type”/> 可以从 <paramref name=”fromType”/>
8 /// 的实例分配或进行强制类型转换,则为 <c>true</c>;否则为 <c>false</c></returns>
9 public static bool IsCastableFrom(this Type type, Type fromType)
10 {
11 if (type == null || fromType == null)
12 {
13 return false;
14 }
15 // 总是可以与 Object 进行强制类型转换。
16 if (type == typeof(object) || fromType == typeof(object))
17 {
18 return true;
19 }
20 // 对 Nullable<T> 的支持。
21 Type[] genericArguments;
22 if (InInheritanceChain(typeof(Nullable<>), type, out genericArguments))
23 {
24 type = genericArguments[0];
25 }
26 if (InInheritanceChain(typeof(Nullable<>), fromType, out genericArguments))
27 {
28 fromType = genericArguments[0];
29 }
30 // 判断是否可以从实例分配,强制类型转换允许沿着继承链反向转换。
31 if (IsAssignableFromCastEx(type, fromType) || IsAssignableFromCastEx(fromType, type))
32 {
33 return true;
34 }
35 // 对强制类型转换运算符进行判断。
36 if (GetTypeOperators(type).Any(pair => pair.Value.AnyFlag(OperatorType.From) &&
37 IsAssignableFromCastEx(pair.Key, fromType)))
38 {
39 return true;
40 }
41 if (GetTypeOperators(fromType).Any(pair => pair.Value.AnyFlag(OperatorType.To) &&
42 IsAssignableFromCastEx(type, pair.Key)))
43 {
44 return true;
45 }
46 return false;
47 }

是否可以分配到开放泛型类型

开放泛型类型指的就是类似于 List<> 这样的没有提供类型参数的泛型类型,有时也是需要判断

typeof(ICollection<>).IsAssignableFrom(typeof(List<int>))

的,不过很可惜,Type.IsAssignableFrom 方法同样不适用,这里同样需要自己来处理。
这里需要考虑的情况简单些,假设 type 是要测试的开放泛型类型,fromType 是源类型,则有下面几种情况:

  fromType 是接口 fromType 是类型
type 是接口 判断 fromType 及其实现的接口是否匹配 type 判断 fromType 实现的接口是否匹配 type
type 是类 不可能匹配 判断 fromType 的所有基类是否匹配 type

而泛型类型间是否匹配的判断,只要从 fromType 从将类型参数提取出来,再通过 type.MakeGenericType 生成相应的类型,就可以用 IsAssignableFrom 判断类型是否匹配了,这样做还可以一并获取匹配的类型参数。

主要代码为:

1 /// <summary>
2 /// 确定当前的开放泛型类型的实例是否可以从指定 <see cref=”System.Type”/> 的实例分配,并返回泛型的参数。
3 /// </summary>
4 /// <param name=”type”>要判断的开放泛型类型。</param>
5 /// <param name=”fromType”>要与当前类型进行比较的类型。</param>
6 /// <param name=”genericArguments”>如果可以分配到开放泛型类型,则返回泛型类型参数;否则返回 <c>null</c></param>
7 /// <returns>如果当前的开放泛型类型可以从 <paramref name=”fromType”/> 的实例分配,
8 /// 则为 <c>true</c>;否则为 <c>false</c></returns>
9 public static bool OpenGenericIsAssignableFrom(this Type type, Type fromType, out Type[] genericArguments)
10 {
11 if (type != null && fromType != null && type.IsGenericType)
12 {
13 if (type.IsInterface == fromType.IsInterface)
14 {
15 if (InInheritanceChain(type, fromType, out genericArguments))
16 {
17 return true;
18 }
19 }
20 if (type.IsInterface)
21 {
22 // 查找实现的接口。
23 Type[] interfaces = fromType.GetInterfaces();
24 for (int i = 0; i < interfaces.Length; i++)
25 {
26 if (InInheritanceChain(type, interfaces[i], out genericArguments))
27 {
28 return true;
29 }
30 }
31 }
32 }
33 genericArguments = null;
34 return false;
35 }
36 /// <summary>
37 /// 确定当前的开放泛型类型是否在指定 <see cref=”System.Type”/> 类型的继承链中,并返回泛型的参数。
38 /// </summary>
39 /// <param name=”type”>要判断的开放泛型类型。</param>
40 /// <param name=”fromType”>要与当前类型进行比较的类型。</param>
41 /// <param name=”genericArguments”>如果在继承链中,则返回泛型类型参数;否则返回 <c>null</c></param>
42 /// <returns>如果当前的开放泛型类型在 <paramref name=”fromType”/> 的继承链中,
43 /// 则为 <c>true</c>;否则为 <c>false</c></returns>
44 private static bool InInheritanceChain(Type type, Type fromType, out Type[] genericArguments)
45 {
46 // 沿着 fromType 的继承链向上查找。
47 while (fromType != null)
48 {
49 if (fromType.IsGenericType)
50 {
51 genericArguments = fromType.GetGenericArguments();
52 if (genericArguments.Length == type.GetGenericArguments().Length)
53 {
54 try
55 {
56 Type closedType = type.MakeGenericType(genericArguments);
57 if (closedType.IsAssignableFrom(fromType))
58 {
59 return true;
60 }
61 }
62 catch (ArgumentException)
63 {
64 // 不满足参数的约束。
65 }
66 }
67 }
68 fromType = fromType.BaseType;
69 }
70 genericArguments = null;
71 return false;
72 }

下面是一个例子:

typeof(IEnumerable<>).OpenGenericIsAssignableFrom(typeof(Dictionary<string, int>), out genericArguments);

方法可以正确的判断出 Dictionary<string,int> 类型是可以分配到 IEnumerable<> 类型的,而且类型参数为 KeyValuePair<string,int>。

完整的代码源文件可见 https://github.com/CYJB/Cyjb/blob/master/Cyjb/TypeExt.cs

本文链接



You must enable javascript to see captcha here!

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress

无觅相关文章插件,快速提升流量