Liao9144 / Blog

我的博客
1 stars 0 forks source link

.NET 高级开发系列之反射(Reflection) #3

Open Liao9144 opened 6 years ago

Liao9144 commented 6 years ago

一、反射概述

1.什么是反射

反射提供描述程序集、模块和类型的对象(Type 类型)。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。如果代码中使用了特性,可以利用反射来访问它们。

2.反射原理

下图是 .net 程序开发到程序运行的整个过程

image

反射(System.Reflection)是.Net框架提供帮助类库,可以读取并使用元数据(Metadata)。 元数据(Metadata)描述了程序集的内容。通过将元数据嵌入每个程序集中,任何程序集都可以实现完全的自描述,从而简化了发布使用较旧技术的组件时所需进行的工作。

二、反射的使用

1.常用 API

  1. 使用 Assembly 定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
  2. 使用 Module 了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
  3. 使用 ConstructorInfo 了解构造函数的名称、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。
  4. 使用 MethodInfo 了解方法的名称、返回类型、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。
  5. 使用 FiedInfo 了解字段的名称、访问修饰符(如 public 或 private)和实现详细信息(如 static)等,并获取或设置字段值。
  6. 使用 EventInfo 了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
  7. 使用 PropertyInfo 了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
  8. 使用 ParameterInfo 了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
  9. 使用 BindingFlags 枚举指定控制绑定以及通过反射执行成员和类型搜索的方式的标记。(一个类中会存在很多私有成员:如私有字段、私有属性、私有方法。一般来说这些私有成员是外部是不能访问的,但是有时候,源代码是别人的,你就不能修改源代码,只提供给你 dll。或者你去维护别人的代码,源代码却有丢失。这样的情况如果你想知道私有成员的值,甚至去想直接调用类里面的私有方法。这时候可以用到反射的 BindingFlags 调用着些私有成员,甚至可以破坏单例。)

2.API 应用

image

项目结构,MyReflection 没有引用 MyReflection.Model。

public class UserInfo<T> where T : struct
{
    private T _id;  // 字段

    [Display(Name = "ID")]  // 特性
    public T Id  // 属性
    {
        get { return _id; }
        set { _id = value; }
    }

    [Display(Name = "姓名")]  // 特性
    public string Name { get; set; }  // 属性

    [Display(Name = "年龄")]
    public int Age { get; set; }  // 属性

    public UserInfo()  // 无参构造函数
    {
        Console.WriteLine("这里是{0}的无参构造函数", this.GetType());
    }

    public UserInfo(T id)  // 带参数构造函数
    {
        _id = id;
        Console.WriteLine("这里是{0}的带参数构造函数,参数id是{1}", this.GetType(), id);
    }

    public void Show1()  // 无参方法
    {
        Console.WriteLine("这里是{0}的无参方法Show1", this.GetType());
    }

    public void Show2(int parameter)  // 有参方法
    {
        Console.WriteLine("这里是{0}的有参方法Show2,参数parameter是{1}", this.GetType(), parameter);
    }

    public void Show3(int parameter)  // 重载方法之一
    {
        Console.WriteLine("这里是{0}的重载方法之一Show3,参数parameter是{1}_{2}", this.GetType(), parameter, parameter.GetType());
    }

    public void Show3(string parameter)  // 重载方法之二
    {
        Console.WriteLine("这里是{0}的重载方法之二Show3,参数parameter是{1}_{2}", this.GetType(), parameter, parameter.GetType());
    }

    private void Show4()  // 私有方法
    {
        Console.WriteLine("这里是{0}的私有方法Show4", this.GetType());
    }

    public static void Show5()  // 静态方法
    {
        Console.WriteLine("这里是{0}的静态方法Show5", typeof(UserInfo<T>));
    }

    public void Show6<P1, P2>(P1 parameter1, P2 parameter2)  // 泛型方法
    {
        Console.WriteLine("这里是{0}的泛型方法Show6,参数parameter是{1}_{2}&{3}_{4}", this.GetType(), parameter1, parameter1.GetType(), parameter2, parameter2.GetType());
    }
}

a. 加载程序集(加载不会错,但是如果没有依赖项,使用的时候会错)

//Assembly assembly = Assembly.Load("MyReflection.Model");  // 从当前目录加载(dll名称无后缀)
Assembly assembly = Assembly.LoadFile(@"E:\MyGitHub\Blog\.NET 高级开发系列\MyReflection\MyReflection.Model\bin\Debug\MyReflection.Model.dll");  // 完整路径加载
//Assembly assembly = Assembly.LoadFrom("MyReflection.Model.dll");  // 从当前目录加载(dll名称带后缀)或者完整路径加

b. 获取类型信息(根据类的全名称(命门空间.类名)获取类的信息,泛型根据泛型参数个数(n个)加上 `n)

Type type = assembly.GetType("MyReflection.Model.UserInfo`1");
type = type.MakeGenericType(new Type[] { typeof(int) });  // 注意要重新接收

c. 创建对象

// object instance = Activator.CreateInstance(type);  // 调用无参构造函数
object instance = Activator.CreateInstance(type, new object[] { 1000 });   // 调用有参数构造

d. 读取私有字段

object _id = type.GetField("_id", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(instance);

e. 设置属性值

type.GetProperty("Name").SetValue(instance, "张三");
type.GetProperty("Age").SetValue(instance, 18);

f. 读取属性值

DisplayAttribute idDisplay = (DisplayAttribute)type.GetProperty("Id").GetCustomAttribute(typeof(DisplayAttribute));
DisplayAttribute nameDisplay = (DisplayAttribute)type.GetProperty("Name").GetCustomAttribute(typeof(DisplayAttribute));
DisplayAttribute ageDisplay = (DisplayAttribute)type.GetProperty("Age").GetCustomAttribute(typeof(DisplayAttribute));

g. 读取属性特性 DisplayAttribute idDisplay = (DisplayAttribute)type.GetProperty("Id").GetCustomAttribute(typeof(DisplayAttribute)); DisplayAttribute nameDisplay = (DisplayAttribute)type.GetProperty("Name").GetCustomAttribute(typeof(DisplayAttribute)); DisplayAttribute ageDisplay = (DisplayAttribute)type.GetProperty("Age").GetCustomAttribute(typeof(DisplayAttribute));

h. 调用方法

var show1 = type.GetMethod("Show1");
show1.Invoke(instance, null);  // 没有参数null或者new object[] {  }

var show2 = type.GetMethod("Show2");
show2.Invoke(instance, new object[] { 111 });

var show3_1 = type.GetMethod("Show3", new Type[] { typeof(int) });
show3_1.Invoke(instance, new object[] { 123 });

var show3_2 = type.GetMethod("Show3", new Type[] { typeof(string) });
show3_2.Invoke(instance, new object[] { "abc" });

var show4 = type.GetMethod("Show4", BindingFlags.NonPublic | BindingFlags.Instance);
show4.Invoke(instance, null);

var show5 = type.GetMethod("Show5");
//show5.Invoke(instance, null); 
show5.Invoke(null, null);  // 静态方法不用实例

var show6 = type.GetMethod("Show6");
show6 = show6.MakeGenericMethod(new Type[] { typeof(int), typeof(string) });  // 注意要重新接收
show6.Invoke(instance, new object[] { 123, "abc" });

运行结果

image

三、反射的优缺点

性能问题其实可以说一下,先看下面的性能对比

public static void Show()
{
    long commonTime = 0;
    long reflectionTime = 0;

    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1000000; i++)
        {
            List<int> list = new List<int>();
            list.Clear();
        }
        watch.Stop();
        commonTime = watch.ElapsedMilliseconds;
    }
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1000000; i++)
        {
            Assembly assembly = Assembly.Load("mscorlib");  // 1.动态加载
            Type type = assembly.GetType("System.Collections.Generic.List`1");  // 2.获取类型
            type = type.MakeGenericType(new Type[] { typeof(int) });
            object instance = Activator.CreateInstance(type);  // 3.创建对象
            List<int> list = (List<int>)instance;  // 4.接口强制转换
            list.Clear();  // 5.方法调用
        }
        watch.Stop();
        reflectionTime = watch.ElapsedMilliseconds;
    }

    Console.WriteLine("commonTime={0}ms,reflectionTime={1}ms", commonTime, reflectionTime);
}

运行结果

image

100w 次调用 13ms vs 5242ms,相差 400 多倍,看起来是挺吓人的,但如果是 100 次调用其实反射方式也只是用了0.5242ms,1ms都不到,做一次SQL查询也比这个大得多,绝对值是很小的。其实上述代码可以把1.动态加载&2.获取类型放到for外面,程序集和类型这些都是固定不变的,程序加载一次就够了(空间换时间)。

Stopwatch watch = new Stopwatch();
watch.Start();
Assembly assembly = Assembly.Load("mscorlib");  // 1.动态加载
Type type = assembly.GetType("System.Collections.Generic.List`1");  // 2.获取类型
type = type.MakeGenericType(new Type[] { typeof(int) });
for (int i = 0; i < 10000; i++)
{
    //Assembly assembly = Assembly.Load("mscorlib");  // 1.动态加载
    //Type type = assembly.GetType("System.Collections.Generic.List`1");  // 2.获取类型
    //type = type.MakeGenericType(new Type[] { typeof(int) });
    object instance = Activator.CreateInstance(type);  // 3.创建对象
    List<int> list = (List<int>)instance;  // 4.接口强制转换
    list.Clear();  // 5.方法调用
}
watch.Stop();
reflectionTime = watch.ElapsedMilliseconds;

运行结果

image

看结果现在只是差了4倍多,那绝对值更小了。反射在绝大部分情况不影响你的程序性能,如果你程序追求极致的性能把所有的反射去掉也不会带来多大的性能提升(性能优化是要抓住大头,影响最大的地方去优化),另外在 ORM、MVC、IOC、AutoMapper 都是大量在使用反射技术的。