1. 概述
C#程序可以编译为一个包含元数据、编译代码和资源的程序集。在运行时检查并使用元数据和编译代码的操作称为反射。
反射相关的API均位于System.Reflection命名空间下。我们甚至可以通过System.Reflection.Emit命名空间中的类在运行时动态创建新的元数据和可执行IL(中间语言)指令。
2. 反射和激活类型
2.1. 获取类型
System.Type的实例代表了类型的元数据。可以说,System.Type实例就是打开类型(和定义该类型的程序集)的所有元数据的窗口。System.Type是一个抽象类型。因此,实际上typeof运算符获得的是一个Type的子类。这种CLR使用的子类属于mscorlib的内部类型,称为RuntimeType。
调用任意对象的GetType方法或者使用C#的typeof运算符都可以得到System.Type的实例。
还可以通过类型的名称获得Type对象。如果我们拥有类型所在的Assembly引用,可以调用Assembly.GetType方法。
如果我们并没有Assembly对象,则可以通过对象的程序集限定名称(类型的完整名称加上程序集的完全限定名称)获得对象的实例。同时,该程序集会隐式加载(就像是调用了Assembly.Load(string)方法一样)。
Type具有AsAssemblyQualifiedName属性,其值是类型的FullName,随后跟一个逗号,之后是程序集的完整名称。
2.1.1. 获取数组类型
typeof运算符和GetType同样适用于数组类型。
还可以对元素类型调用MakeArrayType以获得数组类型。
MakeArrayType方法可以接受一个int类型的参数以创建多维矩形数组。
getElementType方法可以返回数组元素的类型。GetArrayRank则可以返回矩形数组的维数。
2.1.2. 获取嵌套类型
要获得嵌套类型,可以在包含类型上调用GetNestedTypes方法。
其中的+会将包含类型与嵌套的命名空间分隔开来。
2.2. 类型名称
类型具有Namespace、Name以及FullName属性。在大多数情况下,FullName是前两者的组合。嵌套类型和封闭的泛型类型例外。
2.2.1. 嵌套类型名称
对于嵌套类型来说,其FullName仅仅是包含的类型名称。其中的+会将包含类型与嵌套的命名空间分隔开来。
2.2.2. 泛型类型名称
泛型类型名称带有'后缀,后续加上类型参数的数目。如果泛型类型是未绑定的类型,则其Name和FullName都将遵循该规则。
如果泛型类型是封闭的,则其FullName(而且仅有FullName)会包含额外的附加信息。其中,每一个类型参数枚举都将使用其程序集限定名称。
2.2.3. 数组和指针类型名称
数组类型的名称和typeof表达式使用的后缀是相同的
2.2.4. ref和out参数的类型名称
ref和out类型的参数带有&后缀
2.3. 基本类型和接口
Type类中具有BaseType属性;
GetInterfaces方法会返回类型实现的接口;
2.4. 实例化类型
从类型创建对象实例的方式有两种:
1)调用静态的Activator.CreateInstance方法
2)调用ConstructorInfo.Invoke方法,并使用Type的GetConstructor方法的返回值作为参数
Activator.CreateInstance接受Type类型的参数,并且还可以接受可选的参数作为传递给构造器的参数
CreateInstance方法可以指定很多选项,例如该类型所在的程序集、目标应用程序域以及是否绑定非公有的构造器。如果运行时无法找到合适的构造器,则会抛出Missing-MethodException。
当所用参数值无法甄别重载构造器时,就需要调用ConstructorInfo的Invoke方法。例如,假设X类拥有两个构造器:一个构造器接受string参数;另一个接受StringBuilder类型的参数。如果向Activator.CreateInstance传递null参数,则调用目标就具有二义性。此时就需要使用ConstructorInfo
2.5 泛型类型
Type既可以表示封闭的泛型类型也可以表示未绑定类型参数的泛型类型。在编译时只能够实例化封闭的泛型类型,而无法实例化未绑定的泛型类型
MakeGenericType方法接受类型参数,即可将未绑定的泛型类型转换为封闭的泛型类型。而GetGenericTypeDefinition方法则实现相反的操作
GetGenericArguments可以返回封闭泛型类型的类型参数;对于未绑定的泛型类型,GetGenericArguments会返回在泛型类型定义中指定为占位符类型的伪类型
3.反射并调用成员
GetMembers方法可以返回类型的成员。如果在调用GetMembers方法时不传递任何参数,则该方法会返回当前类型(及其基类)的所有公有成员。GetMember方法则可以通过名称检索特定的成员。由于成员可能会被重载,因此该方法仍然会返回一个数组。当调用GetMembers方法时,可以传递一个MemberTypes实例来限定返回的成员类型。此外,还可以调用GetMethods、GetFields、GetProperties、GetEvents、GetConstructors以及GetNestedTypes方法获得相应的成员。以上方法均有返回指定的单个成员的版本。
3.1 成员类型
可以根据MemberInfo的MemberType属性将MemberInfo转换为相应的子类型。如果通过GetMethod、GetField、GetProperty、GetEvent、GetConstructor或者GetNestedTypes(及其复数版本)获取成员则无须进行转换。
所有的*Info实例都会在第一次使用时由反射API缓存,和保存对象标识的作用一样,这种缓存有助于改善慢速API的性能。
3.2 泛型类型成员
我们不但可以从未绑定的泛型类型中获得成员元数据,也可以从封闭的泛型类型中获得这些数据:
using System;
using System.Collections.Generic;
using System.Reflection;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
PropertyInfo[] propertyInfos = typeof(IEnumerator<>).GetProperties();
foreach (var property in propertyInfos)
{
Console.WriteLine(property.DeclaringType);
}
PropertyInfo unbound = typeof(IEnumerator<>).GetProperty("Current");
PropertyInfo closed = typeof(IEnumerator<int>).GetProperty("Current");
Console.WriteLine(unbound);
Console.WriteLine(closed);
Console.WriteLine(unbound.PropertyType.IsGenericParameter);
Console.WriteLine(closed.PropertyType.IsGenericParameter);
Console.ReadLine();
}
}
}
从未绑定的或者封闭的泛型类型中得到的MemberInfo是相互独立的,即使对于签名中不含有泛型类型参数的成员也是如此
3.3 动态调用成员
一旦得到了MethodInfo、PropertyInfo或者FieldInfo对象,我们就可以动态对其进行调用或者得到并设置它们的值。这称为“动态绑定”或者“后期绑定”。它会在运行时(而不是在编译时)来决定成员的调用。
static void Main(string[] args)
{
string s = "Hello";
int length = s.Length;
object s1 = "Hello";
PropertyInfo prop = s1.GetType().GetProperty("Length");
int length1 = (int)prop.GetValue(s1, null); // 5
}
3.4 方法的参数
using System;
using System.Reflection;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("方法的参数");
Type type = typeof(string);
Type[] types = { typeof(int), typeof(int) };
//由于Substring有重载方法,因此我们必须将一组参数的类型传递到GetMethod中以获得所需要的方法
//MethodBase(MethodInfo和ConstructorInfo的基类)的GetParameters方法将会返回方法参数的元信息
MethodInfo methodInfo = type.GetMethod("Substring", types);
object[] ars = {2,4};
object rv = methodInfo.Invoke("xsxddshj", ars);
Console.WriteLine(rv);
//Console.ReadKey();
MethodInfo[] methodInfos = type.GetMethods();
foreach (var m in methodInfos)
{
Console.WriteLine(m.Name + " " + m.GetParameters() + " " + m.GetGenericArguments().Length);
}
}
}
}
3.5 使用委托提高性能
动态调用的效率不高,其开销通常为几微秒。如果要在一个循环中重复调用某个方法,则可以为目标动态方法动态实例化一个委托,这样就可以将微秒级的开销降低到纳秒级。
class Program
{
//定义了一个委托
delegate string StringToString(string s,char c);
static void Main(string[] args)
{
MethodInfo trimMethod = typeof(string).GetMethod("Trim", new Type[1] { typeof(char)});
var trim = (StringToString)Delegate.CreateDelegate
(typeof(StringToString), trimMethod);
for (int i = 0; i < 1000000; i++)
{
//执行很快,这是因为动态绑定仅仅执行了一次
Console.WriteLine(trim("test", 't'));
}
}
}
3.6 访问非公有成员
类型上所有检测元数据的方法(例如GetProperty、GetField等)都含有使用BindingFlags枚举的重载方法。
BindingFlags可以按位组合。
using System;
using System.Reflection;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
Type type = typeof(string);
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Static|BindingFlags.Public);
foreach (var m in methodInfos)
{
Console.WriteLine(m.Name + " " + m.GetParameters() + " " + m.GetGenericArguments().Length);
}
}
}
}
3.7 泛型方法
泛型方法无法直接调用,调用泛型方法需要一个额外的步骤,即在MethodInfo对象上调用MakeGeneric-Method方法来指定确切的泛型类型参数。该方法将返回一个新的MethodInfo
using System;
using System.Reflection;
namespace helloworld
{
class Program
{
public static T Test<T>(T x) { return x; }
static void Main(string[] args)
{
MethodInfo methodInfo = typeof(Program).GetMethod("Test");
MethodInfo intTest = methodInfo.MakeGenericMethod(typeof(int));
}
}
}
3.8 调用未知类型的泛型接口成员
当需要调用(直到运行时才能得知的)未知类型参数的泛型接口成员时,使用反射是非常有效的。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace helloworld
{
class Program
{
public static T Test<T>(T x) { return x; }
static void Main(string[] args)
{
Console.WriteLine(ToStringEx(new List<int> { 5, 6, 7 }));
Console.WriteLine(ToStringEx("xyyzzz".GroupBy(c => c)));
}
//public static string ToStringEx<T>(IEnumerable<T> sequence)
//{
// return "";
//}
//这种声明有很多限制,如果sequence中包含需要枚举的嵌套集合该怎么办呢?我们只能重载该方法
//public static string ToStringEx<T>(IEnumerable<IEnumerable<T>> sequence)
//{
// return "";
//}
//那么当sequence含有分组或者嵌套序列的映射时,方法重载的静态解决方案也就变得不太可行了。我们需要一个既可以扩展又能处理任何对象图的方式
//,这段代码是无法编译的:因为未绑定泛型类型参数的类(例如List<>或者IGrouping<>)的成员是无法调用的。本例中的未绑定类型参数的类为List<>。
//我们可以通过使用非泛型的IList接口来解决这个问题
//public static string ToStringEx(object value)
//{
// if (value == null) return "<null>";
// StringBuilder sb = new StringBuilder();
// if (value is List<>) // Error
// sb.Append("List of " + ((List<>)value).Count + " items"); // Error
// if (value is IGrouping <,>) // Error
// sb.Append("Group with key=" + ((IGrouping <,>) value).Key); // Error
// // Enumerate collection elements if this is a collection,
// // recursively calling ToStringEx()
// // ...
// return sb.ToString();
//}
//public static string ToStringEx(object value)
//{
// if (value == null) return "<null>";
// StringBuilder sb = new StringBuilder();
// if (value is IList) // Error
// sb.Append("List of " + ((IList)value).Count + " items"); // Error
// //可惜的是对于IGrouping<,>来说上述方案就无法达成了
// //if (value is IGrouping <,>) // Error
// // sb.Append("Group with key=" + ((IGrouping <,>) value).Key); // Error
// // Enumerate collection elements if this is a collection,
// // recursively calling ToStringEx()
// // ...
// return sb.ToString();
//}
//由于没有访问Key属性的非泛型类型,因此我们只能使用反射方式。
//这个解决方案并非要调用未绑定泛型类型的成员(当然也是不可能的),
//而是在运行时确定参数类型,并调用封闭参数类型的泛型类型成员。
//首先需要确定value是否实现了IGrouping<,>,如果答案是肯定的,
//则需要封闭泛型接口的类型参数。使用LINQ查询是解决这个问题的良好手段。
//此后,我们将获得并调用Key属性
public static string ToStringEx(object value)
{
if (value == null) return "<null>";
if (value.GetType().IsPrimitive) return value.ToString();
StringBuilder sb = new StringBuilder();
if (value is IList)
sb.Append("List of " + ((IList)value).Count + " items: ");
Type closedIGrouping = value.GetType().GetInterfaces()
.Where(t => t.IsGenericType &&
t.GetGenericTypeDefinition() == typeof(IGrouping<,>))
.FirstOrDefault();
if (closedIGrouping != null) // Call the Key property on IGrouping<,>
{
PropertyInfo pi = closedIGrouping.GetProperty("Key");
object key = pi.GetValue(value, null);
sb.Append("Group with key=" + key + ": ");
}
if (value is IEnumerable)
foreach (object element in ((IEnumerable)value))
sb.Append(ToStringEx(element) + " ");
if (sb.Length == 0) sb.Append(value.ToString());
return "\r\n" + sb.ToString();
}
}
}
4. 反射程序集
若需要动态反射程序集,只需调用Assembly对象的GetType或者GetTypes即可
using NPOI.SS.Formula.Functions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//获取包含当前执行的代码的程序集
Type type = Assembly.GetExecutingAssembly().GetType("ConsoleApp1.Program");
Console.WriteLine(type.Name);
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name+" :"+method.IsStatic);
}
}
public static string ToStringEx(object value)
{
if (value == null) return "<null>";
//Type.IsPrimitive获取一个值,通过该值指示 Type 是否为基元类型之一。
if (value.GetType().IsPrimitive) return value.ToString();
StringBuilder sb = new StringBuilder();
if (value is IList)
sb.Append("List of " + ((IList)value).Count + " items: ");
Type closedIGrouping = value.GetType().GetInterfaces()
.Where(t => t.IsGenericType &&
t.GetGenericTypeDefinition() == typeof(IGrouping<,>)).FirstOrDefault();
if(closedIGrouping != null) // Call the Key property on IGrouping<,>
{
PropertyInfo pi = closedIGrouping.GetProperty("Key");
object key = pi.GetValue(value, null);
sb.Append("Group with key=" + key + ": ");
}
if (value is IEnumerable)
foreach (object element in ((IEnumerable)value))
sb.Append(ToStringEx(element) + " ");
if (sb.Length == 0) sb.Append(value.ToString());
return "\r\n" + sb.ToString();
}
}
}
5.使用特性
CLR允许使用特性将额外的元数据追加到类型、成员和程序集上。
5.1 特性基础
名称 | 概述 | 备注 |
位映射特性 | 位映射特性(本书中定义的术语)可以映射到类型元数据的特定位上。大多数的C#修饰符关键字都会编译为位映射特性 | 不可扩展 |
自定义特性 | 自定义特性可以编译为类型的主元数据表中的二进制数据。所有的自定义特性都是由System.Attribute的子类表示的.这些元数据中的二进制块就是该特性类的标识,其中还包含了所有占位和命名的参数的值。 | 可扩展 |
伪自定义特性 | 伪自定义特性与自定义特性之间的差异在于编译器或者CLR内部会进行优化,将伪自定义特性转换为位映射特性。 | 不可扩展 |