C#基础系列-反射

1、反射的定义

反射(Reflection),是.Net中获取运行时类型信息的方式。程序集中有关程序及其类型的数据被称为元数据(metadata)。程序在运行时,可以查看其它程序集或其本身的元数据。一个运行的程序查看本身的元数据或者其他程序集的元数据的行为叫做反射。

2、反射应用场景

  • 获取有关加载的程序集和其中定义的类型的信息。
  • 在运行时创建、调用和访问类型实例。
  • 访问程序元数据的属性。
  • 检查和实例化程序集中的类型。
  • 在运行时构建新类型。
  • 执行后期绑定,访问在运行时创建的类型的方法。

3、反射的优缺点

优点:

  1. 可以实现动态创建对象和编译,能提高程序的灵活性和扩展性,降低耦合性。

缺点:

  1. 会模糊程序内部逻辑,代码变得更复杂。本来直接new一个对象的问题,反射需要更多的代码才能实现,而且代码不易读,维护成本高。
  2. 性能问题。反射的性能可能也是个老生常谈的问题了,但其实比非反射的性能差异并没有很巨大,特别是使用了缓存之后。

4、反射的使用

本节将详解如何加载程序集和获取其中定义的类型的信息以及如何在运行时创建、调用和访问类型实例。

4.1 使用Assembly加载程序集

4.1.1 Assembly提供三个方法来加载程序集:

    1. Load方法:通过程序集的长名称(包括程序集名,版本信息,语言文化,公钥标记)来加载程序集的,会加载此程序集引用的其他程序集。如没有找到程序集则会抛出异常。一般情况下应优先使用该方法。
    2. LoadFrom方法:从指定的路径来加载程序集,该方法底层还是调用Load方法进行加载,通过Load方法没找到时,不会抛出异常,而是会加载路径指定的程序集。该方式也会加载此程序集引用的其他程序集。
    3. LoadFile方法:从指定的文件来加载程序集。该方法不会调用Load方法,不会加载此程序集引用的其他程序集。

4.1.2 代码示例

         // 长格式名称加载 注意此处不需要后缀
Assembly assembly = Assembly.Load("MyReflection"); // 绝对路径加载
Assembly assembly1 = Assembly.LoadFrom(@"E:\project\Reflection\MyReflection\bin\Debug\MyReflection.dll");
// 如果要加载的程序集跟当前执行程序集处于同一路径,则可省略前面的路径
Assembly assembly2 = Assembly.LoadFrom(@"MyReflection.dll");
// 通过URL地址加载
Assembly assembly3 = Assembly.LoadFrom(@"http://www.test.com/MyReflection.dll"); // 必须传入绝对路径
Assembly assembly4 = Assembly.LoadFile(@"MyReflection.dll");

4.2 获取类型信息并动态创建、调用和访问类型实例

4.2.1 获取类型

    1. Assembly.GetTypes:获取程序集中所有的类型,返回类型数组。
    2. Assembly.GetType:通过类型全名获取程序集中的类,返回指定类的对象,没找到返回null。
    3. typeof():获取指定类的类型。
         // 获取所有类
Type[] types = assembly.GetTypes();
// 通过类名获取类
Type type2 = assembly.GetType("MyReflection.Chinese");
//
Type type3 = typeof(Chinese);

4.2.2 获取父类及接口信息

    1. Type.BaseType:返回父类信息。
    2. Type.GetInterfaces:获取实现或继承的所有接口。
    3. Type.GetInterface:根据名称获取实现或继承的接口,没找到返回null。
         Type type = typeof(Chinese);
// 获取父类
Type baseType = type.BaseType;
// 获取所有接口
Type[] interfaces = type.GetInterfaces();
// 通过接口全名获取接口
Type @interface = type.GetInterface("MyReflection.IInterface");

4.2.3 获取构造函数

    1. Type.GetConstructors:获取类型中所有构造函数。
    2. Type.GetConstructor:通过参数类型获取构造函数。
         Type type = typeof(Chinese);
// 获取所有构造函数
ConstructorInfo[] ctors = type.GetConstructors();
// 通过类型获取对应的构造函数
ConstructorInfo ctor = type.GetConstructor(new Type[] { typeof(string), typeof(int) });

4.2.4 创建类型的实例

    1. Activator.CreateInstance:创建类型的实例。默认调用无参数构造函数创建,可传入参数数组调用有参数构造函数。
         // 调用无参数构造函数
object obj = Activator.CreateInstance(type);
// 调用有参数构造函数 参数顺序、类型应与对应构造函数一致
object obj1 = Activator.CreateInstance(type, new object[] { "张三", });

4.2.5 获取/操作字段

    1. Type.GetFields:获取所有字段。
    2. Type.GetField:通过名称获取字段,未获取到返回null。
    3. FieldInfo.SetValue:给字段设置值。
    4. FieldInfo.GetValue:获取字段值。
         Type type = typeof(Chinese);
object obj = Activator.CreateInstance(type); // 获取所有的字段
FieldInfo[] fields = type.GetFields();
// 通过名称获取字段
FieldInfo field = type.GetField("Name"); // 设置值
field.SetValue(obj, "张三");
// 获取值
object fieldVal = field.GetValue(obj);

4.2.6 获取/操作属性

    1. Type.GetProperties:获取所有属性。
    2. Type.GetPropertie:通过名称获取属性,未获取到返回null。
    3. PropertyInfo.SetValue:设置值。
    4. PropertyInfo.GetValue:获取属性值。
         Type type = typeof(Chinese);
object obj = Activator.CreateInstance(type); // 获取所有的属性
PropertyInfo[] props = type.GetProperties();
// 通过名称获取属性
PropertyInfo prop = type.GetProperty("Name"); // 设置值
prop.SetValue(obj, "张三");
// 获取值
object propVal = prop.GetValue(obj);

4.2.7 获取/调用方法

    1. Type.GetMethods:获取所有方法。
    2. Type.GetMethod:通过名称获取方法,可传入参数类型来区分重载方法。
    3. MethodInfo.IsGenericMethod:是否是泛型方法。
    4. MethodInfo.ContainsGenericParameters:是否包含未赋值的泛型类型参数。
        Type type = typeof(Chinese);
object obj = Activator.CreateInstance(type); // 获取所有方法
MethodInfo[] methods = type.GetMethods(); // 获取并调用无参数方法
MethodInfo method = type.GetMethod("Eat");
method.Invoke(obj, null); // 获取并调用有参数方法
MethodInfo method1 = type.GetMethod("SayChinese");
method1.Invoke(obj, new object[] { "我是中国人" }); // 获取并调用无参数重载方法
MethodInfo method2 = type.GetMethod("SayHello", new Type[] { });
method2.Invoke(obj, null); // 获取并调用无参数重载方法
MethodInfo method3 = type.GetMethod("SayHello", new Type[] { typeof(string) });
method3.Invoke(obj, new object[] { "李四" }); // 获取并调用泛型方法
MethodInfo method4 = type.GetMethod("ShowObject");
// 判断方法是否是泛型方法
bool isGenericMethod = method4.IsGenericMethod;
// 判断泛型方法是否包含未赋值的泛型类型参数
bool containsGenericParameters = method4.ContainsGenericParameters;
// 替换泛型方法定义的类型参数
MethodInfo genericMethod = method4.MakeGenericMethod(new Type[] { typeof(int) });
// 调用
genericMethod.Invoke(obj, new object[] { });

4.2.8 获取事件及相关操作

    1. Type.GetEvents:获取声明或继承的所有事件。
    2. Type.GetEvent:通过事件名称获取事件。
    3. EventInfo.EventHandlerType:获取与此事件关联的基础事件处理程序委托的 Type 对象。
    4. Delegate.CreateDelegate:创建指定类型的委托。
    5. EventInfo.AddEventHandler:添加事件。
    6. EventInfo.RemoveEventHandler:移除事件。
         Type type = typeof(Chinese);
object obj = Activator.CreateInstance(type); // 获取声明或继承的所有事件
EventInfo[] events = type.GetEvents();
// 获取事件
EventInfo eventInfo = type.GetEvent("StudentEvent");
// 获取处理事件的委托类型
Type delegateType = eventInfo.EventHandlerType; // 查看委托签名
//Console.WriteLine(delegateType.GetMethod("Invoke").ToString()); // 获取要添加的方法
MethodInfo methodInfo = type.GetMethod("DelegateMethod", BindingFlags.Public | BindingFlags.Instance); // 创建委托
Delegate d = Delegate.CreateDelegate(delegateType, obj, methodInfo);
// 将委托实例添加到事件
eventInfo.AddEventHandler(obj, d); // 获取调用事件方法并调用
MethodInfo invokeMethod = type.GetMethod("InvokeEvent");
invokeMethod.Invoke(obj, null);

4.2.9 BindingFlags的使用

    1. BindingFlags:指定控制绑定和由反射执行的成员和类型搜索方法的标志,允许按位组合成员值。获取信息时,可传入该枚举进行过滤。

注:使用时必须包含BindingFlags.Instance和BindingFlags.Static中的一个,否则会找不到方法。

         // 获取所有的非公开的属性
PropertyInfo[] props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
// 获取所有公开的静态属性
PropertyInfo[] props1 = type.GetProperties(BindingFlags.Public | BindingFlags.Static);
// 在公开的实例方法中搜索
MethodInfo methodInfo = type.GetMethod("DelegateMethod", BindingFlags.Public | BindingFlags.Instance);

5、总结

本文列出了反射的一些基本用法,使用反射可以在运行时创建、调用和访问类型实例。

本节涉及源码可至gitHub进行下载。

上一篇:ThinkPHP集成万象优图


下一篇:nanosleep() -- 更精确的延迟 -----一个使用用例