反射类型和泛型类型
从反射的角度来说,泛型类型和普通类型的区别在于,泛型类型与一组类型参数(如果是泛型类型定义)或类型变量(如果是构造的类型)关联。泛型方法与普通方法的区别也在于此。
反射的问题在于提供一种方式来检查类型参数或类型变量的此数组。如果是类型参数,反射还必须提供一种方式来检查约束。本节介绍提供检查泛型类型和方法的能力的 Type 和 MethodInfo 类的方法
概述
要理解反射处理泛型类型和泛型方法的方式,有两点很重要:
- 泛型类型定义和泛型方法定义的类型参数是由 Type 类的实例表示的。
说明:如果 Type 对象表示泛型类型参数,则 Type 的许多属性和方法具有不同的行为。这些不同在属性和方法主题中进行介绍。有关示例,请参见 IsAutoClass 和 DeclaringType。此外,某些成员仅当 Type 对象表示泛型类型参数时才有效。有关示例,请参见 GetGenericTypeDefinition。
- 如果 Type 的实例表示泛型类型,则会包含一组表示类型参数(泛型类型定义)或类型变量(构造类型)的类型。表示泛型方法的 MethodInfo 类的实例也是如此。
反射提供 Type 和 MethodInfo 方法,允许访问类型参数数组,还可以确定 Type 实例表示的是类型参数还是实际类型。
下面的讨论假定已熟悉了泛型术语,如类型参数、类型变量以及开放式或封闭式构造类型之间的差异。有关更多信息,请参见 .NETFramework 中的泛型概述。
是泛型类型还是泛型方法?
使用反射检查 Type 实例表示的未知类型时,请使用 IsGenericType 属性确定未知类型是否为泛型。类似地,检查 MethodInfo 类实例表示的未知方法时,请使用 IsGenericMethod 属性确定方法是否为泛型。
- 是泛型类型定义还是泛型方法定义?
使用 IsGenericTypeDefinition 属性确定 Type 对象是否表示泛型类型定义,使用 IsGenericMethodDefinition 属性确定 MethodInfo 是否表示泛型方法定义。
泛型类型定义和泛型方法定义是一些模板,根据这些模板,可以创建可实例化的类型。.NET 框架类库中的泛型类型(如 Dictionary<TKey, TValue>)是泛型类型定义。
- 类型或方法是开放式的还是封闭式的?
如果可实例化类型的所有类型参数都已替换,包括所有封闭类型的所有类型参数,则泛型类型或泛型方法是封闭式的。如果是封闭式的,则只能创建泛型类型的一个实例。如果类型是开放式的,则 Type.ContainsGenericParameters 属性返回 true。MethodInfo.ContainsGenericParameters 属性执行同样的功能。
生成封闭式泛型类型
只要具有泛型类型定义或泛型方法定义,即可使用 MakeGenericType 方法创建封闭式泛型类型,或使用 MakeGenericMethod 方法为封闭式泛型方法创建 MethodInfo。
如果具有的开放式泛型类型或方法不是泛型类型定义或泛型方法定义,则不能创建它的实例,也不能提供缺少的类型参数。必须具有泛型类型定义或泛型方法定义。使用GetGenericTypeDefinition 方法可获取泛型类型定义,使用 GetGenericMethodDefinition 方法可获取泛型方法定义。
例如,如果有表示 Dictionary<int, string>的 Type 对象,并且希望创建 Dictionary<string,MyClass> 类型,则可以使用GetGenericTypeDefinition 方法获取表示 Dictionary<TKey,TValue> 的 Type,然后使用 MakeGenericType 方法生成表示 Dictionary<int,MyClass> 的 Type。
有关不是泛型类型的开放式泛型类型的示例,请参见本主题后面部分的“是类型参数还是类型变量”。
检查类型变量和类型参数
使用 Type.GetGenericArguments 方法获取一组 Type 对象,这些对象表示泛型类型的类型参数或类型变量,对于泛型方法,使用 MethodInfo.GetGenericArguments 方法执行同样的操作。
只要了解 Type 对象表示类型参数,就可以回答许多其他问题。可以确定类型参数的源、位置及其约束。
- 是类型参数还是类型变量
若要确定数组的特定元素是类型参数还是类型变量,请使用 IsGenericParameter 属性。如果元素是类型参数,则 IsGenericParameter 属性为 true。
泛型类型可以是开放式的非泛型类型定义,在这种情况下,它同时具有类型变量和类型参数。例如,在下面的代码中,派生出类 D 的类型是通过用 D 的第一个类型参数替换 B 的第二个类型参数创建的。
class B<T, U> {}
class D<V, W> : B<int, V> {}
如果获取表示 D<V, W> 的 Type 对象并使用 BaseType 属性获取其基类型,则产生的 typeB<int, V> 是开放式的非泛型类型定义。
- 泛型参数的源
泛型类型参数可能来自要检查的类型、封闭式类型或泛型方法。可以确定泛型类型参数的源,方法如下:
首先,使用 DeclaringMethod 属性确定类型参数是否来自泛型方法。如果该属性值不是空引用(在 Visual Basic 中为 Nothing),则源是泛型方法。
如果源不是泛型方法,则使用 DeclaringType 属性确定泛型类型参数所属的泛型类型。
如果类型参数属于泛型方法,则 DeclaringType 属性返回声明该泛型方法的类型,这已没有意义。
- 泛型参数的位置
在极少情况下,需要确定类型参数在其声明类的类型参数列表中的位置。例如,假定具有一个 Type 对象,该对象表示上一示例中的 B<int, V> 类型。GetGenericArguments 方法提供类型变量列表,在检查 V 时,可以使用 DeclaringMethod 和 DeclaringType 属性获取其来源。然后,可以使用 GenericParameterPosition 属性确定其在定义时在类型参数列表中的位置。在本示例中,V 在进行定义的类型参数列表的位置 0(零)处。
- 基类型约束和接口约束
使用 GetGenericParameterConstraints 方法可获取类型参数的基类型约束和接口约束。数组元素的顺序不重要。如果是接口类型,则元素表示接口约束。
- 特殊约束
GenericParameterAttributes 属性获取一个 GenericParameterAttributes 值,该值指示特殊约束。类型参数可以约束为引用类型、不可为空值类型和具有默认构造函数。
固定条件
有关泛型类型反射中常用术语的固定条件表,请参见 Type.IsGenericType。有关泛型方法的其他相关术语,请参见 MethodInfo.IsGenericMethod。
如何:使用反射检查和实例化泛型类型
与其他类型的信息一样,泛型类型信息的获取方式为:检查表示泛型类型的 Type 对象。主要差异在于,泛型类型具有一组表示其泛型类型参数的 Type 对象。本部分的第一个步骤是检查泛型类型。第二个步骤演示,通过将类型变量绑定到泛型类型定义的类型参数,可以创建表示构造类型的 Type 对象。
检查泛型类型及其类型参数
1: using System;
2: using System.Collections.Generic;
3: using System.Reflection;
4: namespace CheckGenericType
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: Type t = typeof(Dictionary<,>);
11: Console.WriteLine(" Is this a generic type? {0}", t.IsGenericType);
12: Console.WriteLine(" Is this a generic type definition? {0}", t.IsGenericTypeDefinition);
13: Type[] typeParameters = t.GetGenericArguments();
14: Console.WriteLine(" List {0} type arguments:", typeParameters.Length);
15: foreach (Type tParam in typeParameters)
16: {
17: if (tParam.IsGenericParameter)
18: { DisplayGenericParameter(tParam); }
19: else
20: {
21: Console.WriteLine(" Type argument: {0}", tParam);
22: }
23: }
24: Console.ReadKey();
25: }
26: private static void DisplayGenericParameter(Type tp)
27: {
28: Console.WriteLine(" Type parameter: {0} position {1}", tp.Name, tp.GenericParameterPosition);
29: Type classConstraint = null; foreach (Type iConstraint in tp.GetGenericParameterConstraints())
30: {
31: if (iConstraint.IsInterface)
32: {
33: Console.WriteLine(" Interface constraint: {0}", iConstraint);
34: }
35: }
36: if (classConstraint != null)
37: {
38: Console.WriteLine(" Base type constraint: {0}", tp.BaseType);
39: }
40: else
41: {
42: Console.WriteLine(" Base type constraint: None");
43: }
44: GenericParameterAttributes sConstraints = tp.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
45: if (sConstraints == GenericParameterAttributes.None)
46: {
47: Console.WriteLine(" No special constraints.");
48: }
49: else
50: {
51: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.DefaultConstructorConstraint))
52: {
53: Console.WriteLine(" Must have a parameterless constructor.");
54: }
55: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.ReferenceTypeConstraint))
56: {
57: Console.WriteLine(" Must be a reference type.");
58: } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint))
59: {
60: Console.WriteLine(" Must be a non-nullable value type.");
61: }
62: }
63: }
64: }
65: }
说明
- 第11行。获取表示泛型类型的 Type 实例。下面代码,使用 C# 的 typeof 运算符获取类型。注意,余下的步骤中,类型包含在名为 t 的方法参数中。
- 第13、14行。使用 IsGenericType 属性确定类型是否为泛型,然后使用 IsGenericTypeDefinition 属性确定类型是否为泛型类型定义。
- 第16行。使用 GetGenericArguments 方法获取包含泛型类型参数的数组。
- 第17-28行。对每个类型变量,使用 IsGenericParameter 属性确定其是不是类型参数(例如,在泛型类型定义中),是不是已为类型参数指定的类型(例如,在构造类型中)。
- 第33行。在类型系统中,和普通类型一样,泛型类型参数是由 Type 的实例表示的。下面的代码演示表示泛型类型参数的 Type 对象的名称和参数位置。此处,参数位置无足轻重;它在检查类型参数(用作其他泛型类型的类型变量)时更有价值。
- 第35-50行。通过使用 GetGenericParameterConstraints 方法获取单个数组中的所有约束,确定泛型类型参数的基类型约束和接口约束。不保证约束处于任何特定顺序。
- 第52行。使用 GenericParameterAttributes 属性获取类型参数的特殊约束,如要求其为引用类型。该属性还包含表示方差的值,该值可屏蔽,如下面的代码所示。
- 第54-72行。特殊约束的属性为标志,表示没有任何特殊约束的标志(GenericParameterAttributes.None) 还表示没有协变或逆变。因此,若要测试其中一个条件,必须使用适当的屏蔽。在此情况下,请使用 GenericParameterAttributes.SpecialConstraintMask 隔离特殊约束标志。
构造泛型类型的实例
泛型类型和模板类似。除非指定其泛型类型参数的实际类型,否则不能创建泛型类型的实例。若要在运行时使用反射创建实例,需要使用 MakeGenericType 方法。
1: using System;
2: using System.Collections.Generic;
3: using System.Reflection;
4: namespace CheckGenericType
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: Type t = typeof(Dictionary<,>);
11: Console.WriteLine(" Is this a generic type? {0}", t.IsGenericType);
12: Console.WriteLine(" Is this a generic type definition? {0}", t.IsGenericTypeDefinition);
13: Type[] typeParameters = t.GetGenericArguments();
14: Console.WriteLine(" List {0} type arguments:", typeParameters.Length);
15: foreach (Type tParam in typeParameters)
16: {
17: if (tParam.IsGenericParameter)
18: { DisplayGenericParameter(tParam); }
19: else
20: {
21: Console.WriteLine(" Type argument: {0}", tParam);
22: }
23: }
24: Console.ReadKey();
25: }
26: private static void DisplayGenericParameter(Type tp)
27: {
28: Console.WriteLine(" Type parameter: {0} position {1}", tp.Name, tp.GenericParameterPosition);
29: Type classConstraint = null; foreach (Type iConstraint in tp.GetGenericParameterConstraints())
30: {
31: if (iConstraint.IsInterface)
32: {
33: Console.WriteLine(" Interface constraint: {0}", iConstraint);
34: }
35: }
36: if (classConstraint != null)
37: {
38: Console.WriteLine(" Base type constraint: {0}", tp.BaseType);
39: }
40: else
41: {
42: Console.WriteLine(" Base type constraint: None");
43: }
44: GenericParameterAttributes sConstraints = tp.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
45: if (sConstraints == GenericParameterAttributes.None)
46: {
47: Console.WriteLine(" No special constraints.");
48: }
49: else
50: {
51: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.DefaultConstructorConstraint))
52: {
53: Console.WriteLine(" Must have a parameterless constructor.");
54: }
55: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.ReferenceTypeConstraint))
56: {
57: Console.WriteLine(" Must be a reference type.");
58: } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint))
59: {
60: Console.WriteLine(" Must be a non-nullable value type.");
61: }
62: }using System;
63: using System.Collections.Generic;
64: using System.Text;
65: namespace ConstructGenericType
66: {
67: class Program
68: {
69: static void Main(string[] args)
70: {
71: Type d1 = typeof(Dictionary<,>);
72: Console.WriteLine(d1.FullName);
73: Console.WriteLine();
74: Dictionary<string, int> d2 = new Dictionary<string, int>();
75: Type d3 = d2.GetType(); Type d4 = d3.GetGenericTypeDefinition();
76: Console.WriteLine(d3.FullName);
77: Console.WriteLine();
78: Console.WriteLine(d4.FullName);
79: Type[] typeArgs = { typeof(string), typeof(int) };
80: Type constructed = d1.MakeGenericType(typeArgs);
81: object o = Activator.CreateInstance(constructed);
82: Console.ReadKey();
83: }
84: }
85: }
86: }
87: }
说明:
- 第11-21行。获取表示泛型类型的 Type 对象。以两种方式获取泛型类型 Dictionary<TKey, TValue>:一种是使用 Type.GetType(String) 方法重载和描述类型的字符串,另一种方法调用构造类型 Dictionary<String, Example>的 GetGenericTypeDefinition 方法。MakeGenericType 方法需要泛型类型定义。
- 注意:“d3.FullName”的输出;d1和d4的FullName是相同的。
- 第23行。构造一组用于替换类型参数的类型变量。数组必须包含正确数目的 Type 对象,且顺序和对象在类型参数列表中的顺序相同。本例,“键”的类型为 String,“值”为 int 。
- 第24行。调用 MakeGenericType 方法将类型变量绑定到类型参数,然后构造类型。
- 第25行。使用 CreateInstance(Type) 方法重载来创建构造类型的对象。生成的 Dictionary<String,int> 对象中存储 int的两个实例。
示例
下面示例定义 DisplayGenericType 方法来检查泛型类型定义和代码中使用的构造类型,并显示它们的信息。DisplayGenericType 方法演示如何使用 IsGenericType、IsGenericParameter和 GenericParameterPosition 属性以及 GetGenericArguments 方法。该示例还定义 DisplayGenericParameter 方法来检查泛型类型参数并显示其约束。
代码示例定义一组测试类型,包括说明类型参数约束的泛型类型,并演示如何显示这些类型的信息。
示例通过创建一组类型参数并调用 MakeGenericType 方法,从 Dictionary<TKey,TValue> 类构造类型。程序对使用 MakeGenericType 构造的 Type 对象和使用 typeof(Visual Basic中为GetType)获取的 Type 对象进行比较,演示这两个对象是相同的。类似地,程序使用 GetGenericTypeDefinition 方法获取构造类型的泛型类型定义,并将其与表示 Dictionary<TKey, TValue> 类的 Type 对象进行比较。
1: using System;
2: using System.Reflection;
3: using System.Collections.Generic;
4: using System.Security.Permissions;
5:
6: /// <summary>
7: /// 定义接口
8: /// </summary>
9: public interface ITestArgument { }
10: /// <summary>
11: /// 定义基类
12: /// </summary>
13: public class TestBase { }
14: /// <summary>
15: /// 定义带一个参数的范型类(generic class)
16: /// </summary>
17: /// <remarks>
18: /// 三个限制:
19: /// 1, 它必须继承 TestBase
20: /// 2, 它必须实现 ITestArgument
21: /// 3, 它必须有一个无参的构造函数
22: /// </remarks>
23: /// <typeparam name="T"></typeparam>
24: public class Test<T> where T : TestBase, ITestArgument, new() { }
25: /// <summary>
26: /// Define a class that meets the constraints on the type parameter of class Test.
27: /// </summary>
28: public class TestArgument : TestBase, ITestArgument
29: {
30: public TestArgument() { }
31: }
32: public class Example
33: {
34: // The following method displays information about a generic type.
35: private static void DisplayGenericType(Type t)
36: {
37: Console.WriteLine("/r/n {0}", t);
38: Console.WriteLine(" Is this a generic type? {0}", t.IsGenericType);
39: Console.WriteLine(" Is this a generic type definition? {0}", t.IsGenericTypeDefinition);
40: // Get the generic type parameters or type arguments.
41: Type[] typeParameters = t.GetGenericArguments();
42: Console.WriteLine(" List {0} type arguments:",
43: typeParameters.Length);
44: foreach (Type tParam in typeParameters)
45: {
46: if (tParam.IsGenericParameter)
47: {
48: DisplayGenericParameter(tParam);
49: }
50: else
51: {
52: Console.WriteLine(" Type argument: {0}", tParam);
53: }
54: }
55: }
56: // The following method displays information about a generic
57: // type parameter. Generic type parameters are represented by
58: // instances of System.Type, just like ordinary types.
59: private static void DisplayGenericParameter(Type tp)
60: {
61: Console.WriteLine(" Type parameter: {0} position {1}", tp.Name, tp.GenericParameterPosition);
62: Type classConstraint = null;
63: foreach (Type iConstraint in tp.GetGenericParameterConstraints())
64: {
65: if (iConstraint.IsInterface)
66: {
67: Console.WriteLine(" Interface constraint: {0}", iConstraint);
68: }
69: }
70: if (classConstraint != null)
71: {
72: Console.WriteLine(" Base type constraint: {0}", tp.BaseType);
73: }
74: else
75: Console.WriteLine(" Base type constraint: None");
76: GenericParameterAttributes sConstraints = tp.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
77: if (sConstraints == GenericParameterAttributes.None)
78: {
79: Console.WriteLine(" No special constraints.");
80: }
81: else
82: {
83: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.DefaultConstructorConstraint))
84: {
85: Console.WriteLine(" Must have a parameterless constructor.");
86: }
87: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.ReferenceTypeConstraint))
88: {
89: Console.WriteLine(" Must be a reference type.");
90: }
91: if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint))
92: {
93: Console.WriteLine(" Must be a non-nullable value type.");
94: }
95: }
96: }
97: [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
98: public static void Main()
99: {
100: // Two ways to get a Type object that represents the generic
101: // type definition of the Dictionary class.
102: //
103: // Use the typeof operator to create the generic type
104: // definition directly. To specify the generic type definition,
105: // omit the type arguments but retain the comma that separates
106: // them.
107: Type d1 = typeof(Dictionary<,>);
108: // You can also obtain the generic type definition from a
109: // constructed class. In this case, the constructed class
110: // is a dictionary of Example objects, with String keys.
111: Dictionary<string, Example> d2 = new Dictionary<string, Example>();
112: // Get a Type object that represents the constructed type,
113: // and from that get the generic type definition. The
114: // variables d1 and d4 contain the same type.
115: Type d3 = d2.GetType();
116: Type d4 = d3.GetGenericTypeDefinition();
117: // Display information for the generic type definition, and
118: // for the constructed type Dictionary<String, Example>.
119: DisplayGenericType(d1);
120: DisplayGenericType(d2.GetType());
121: // Construct an array of type arguments to substitute for
122: // the type parameters of the generic Dictionary class.
123: // The array must contain the correct number of types, in
124: // the same order that they appear in the type parameter
125: // list of Dictionary. The key (first type parameter)
126: // is of type string, and the type to be contained in the
127: // dictionary is Example.
128: Type[] typeArgs = { typeof(string), typeof(Example) };
129: // Construct the type Dictionary<String, Example>.
130: Type constructed = d1.MakeGenericType(typeArgs);
131: DisplayGenericType(constructed);
132: object o = Activator.CreateInstance(constructed);
133: Console.WriteLine("/r/nCompare types obtained by different methods:");
134: Console.WriteLine(" Are the constructed types equal? {0}", (d2.GetType() == constructed));
135: Console.WriteLine(" Are the generic definitions equal? {0}", (d1 == constructed.GetGenericTypeDefinition()));
136: // Demonstrate the DisplayGenericType and
137: // DisplayGenericParameter methods with the Test class
138: // defined above. This shows base, interface, and special
139: // constraints.
140: DisplayGenericType(typeof(Test<>));
141: Console.ReadKey();
142: }
143: }