• c# 反射专题—————— 介绍一下是什么是反射[ 一]


    前言

    为什么有反射这个系列,这个系列后,asp net 将会进入深入篇,如果没有这个反射系列,那么asp net的源码,看了可能会觉得头晕,里面的依赖注入包括框架源码是大量的反射。

    正文

    下面是官方文档的介绍:

    https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/reflection

    说的比较绕,反射就是用来动态创建对象的。

    那么什么是动态创建对象? 动态创建对象就是运行时创建对象。

    那么为什么需要动态创建对象呢?

    可以思考一下,我们写代码的时候为什么需要动态创建?

    这里我举一个例子。

    比如说,eventbus,通过不同的字符串反射成不同的事件。

    可能有人没怎么接触这个eventbus,再举个例子。

    有一个api,用户可以传入动物的名字和该动物的一些属性,那么当我们拿到这些字符串的时候,我们在内部根据动物的名字和属性创建响应的对象。

    反射动态创建对象:

    static void Main(string[] args)
    {
    	Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集
    	dynamic obj = assembly.CreateInstance("ConsoleApp7.Cat");
    	Console.WriteLine(obj);
    }
    

    可以看到,我们通过反射创建一个对象。

    那么为什么反射能创建一个对象呢? 它的原理是什么呢?

    在https://docs.microsoft.com/zh-cn/dotnet/framework/reflection-and-codedom/viewing-type-information 文中写道:

    当反射提出请求时,公共语言运行时为已加载的类型创建 Type 。 可使用 Type 对象的方法、字段、属性和嵌套类来查找该类型的任何信息。
    

    也就是说在运行的时候,会为加载的类型,创建Type,那么如何获取Type 呢?

     Type catType = Type.GetType("ConsoleApp7.Cat");
    

    那么这里原理也清晰了,原来在.net 运行的时候会为加载的类型创建Type,通过Type 就能创建实例。

    那么Type 里面有什么呢? 首先肯定有构造函数吧,不然怎么创建实例呢? 那么还有什么呢?

    上文也提及到了里面有方法、字段、属性、嵌套类等用来描述这个类型的信息。

    举个例子:比如说我这个Cat 类吧, 在加载的时候会创建Cat的Type,那么这个Type 里面就存有我这个Cat的方法、字段、属性等,这些可以统称为元数据。

    元数据也就是metadata:

    可以看下下面这篇介绍:

    https://baike.baidu.com/item/元数据/1946090?fr=aladdin

    元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。
    

    这就是这个Type 就是对加载类型的描述了,有了它那么可以做事情。

    例如获取属性,获取方法等。

    举个例子:

    Type catType = Type.GetType("ConsoleApp7.Cat");
    var  cat = Activator.CreateInstance(catType);
    

    这个时候有人就问了,为什么c# 不设计成: catType.CreateInstance();

    这样获取不是更加简单吗? 这就是c# 的优雅的地方。 Type 是仅仅对类型的描述,这样 Type 就不会随着扩展变得臃肿。不谈这个领域驱动篇马上开始了,这里面会介绍。

    那么这里有一个问题了,其实我们反射创建对象也就是为了调用里面方法,那么这个怎么做呢?

    比如说调用里面的Cat的Hi 方法?

    public class Cat
    {
    	public void Hi()
    	{
    		Console.WriteLine("hi master!");
    	}
    }
    

    然后这样写:

    static void Main(string[] args)
    {
    	Type catType = Type.GetType("ConsoleApp7.Cat");
    	var  cat = (Cat)Activator.CreateInstance(catType);
    	cat.Hi();
    	Console.ReadKey();
    }
    

    这样写会运行正常吗? 那肯定能运行正常。

    那么我们为什么一般不这样写?这里就有一个问题,我们反射的目的是什么?

    是为了动态加载,动态加载是为了动态创建对象? 这显然不是,是为了动态能够执行某段代码,也就是动态运行。

    既然我们在运行的时候才知道类型,那么我们是如何能保证知道我们要调用的方法呢?

    所以一般这样写:

    static void Main(string[] args)
    {
    	Type catType = Type.GetType("ConsoleApp7.Cat");
    	var  cat = (Cat)Activator.CreateInstance(catType);
    	var  hiMethod = catType.GetMethod("Hi");
    	hiMethod.Invoke(cat, null);
    	Console.ReadKey();
    }
    

    这里不要看我写的是固定ConsoleApp7.Cat 和 Hi,到了真正写代码都是传入参数进去的。

    一样的能够运行。

    从上面可以看出,其实在反射的眼里,方法也就是对象,通过把Hi 这个方法当成了himethod 这个对象。

    可能在c# 中表现的不是很明显,在js 语言中表现的非常明显,方法就是一个对象。

    看起来反射挺好用的啊,那么现在反射的优点就是动态加载,那么反射的缺点有吗?

    反射的缺点也很明显,那就是执行更慢,为什么会执行更慢呢? 那是因为反射是几乎是边解释边运行的。

    为什么反射会边解释边运行呢? 因为他做不到先编译再运行,微软在新的c#版本中做了优化,如何能避免反射,这个后续说明。

    下面测试一下性能:

    cat 中加入方法:

    static void Main(string[] args)
    {
    	Type catType = Type.GetType("ConsoleApp7.Cat");
    	var  cat = Activator.CreateInstance(catType);
    	var  hiMethod = catType.GetMethod("Hi");
    	Console.WriteLine("反射的运行时间");
    	Stopwatch stopwatch = Stopwatch.StartNew();
    	for (var i =0; i<9999999; i++)
    	{
    		hiMethod.Invoke(cat, null);
    	}
    	stopwatch.Stop();
    	var elapsed = stopwatch.Elapsed;
    	Console.WriteLine(elapsed.Milliseconds);
    	
    	Console.WriteLine("正常创建对象的时间");
    	Cat cat2 = new Cat();
    	Stopwatch stopwatch2 = Stopwatch.StartNew();
    	for (var i = 0; i < 9999999; i++)
    	{
    		cat2.Hi();
    	}
    	stopwatch2.Stop();
    	var elapsed2 = stopwatch2.Elapsed;
    	Console.WriteLine(elapsed2.Milliseconds);
    	Console.ReadKey();
    }
    

    结果:

    可以看到相差非常远。

    那么既然性能相差这么远,为什么我们还要用反射呢?

    这个问题很简单,很多人喜欢拿性能说事,简单的说就是我们做工程的,考虑的是工程角度,性能是是否能满足需求的指标,反射大多数场景还是满足需求的。

    那么反射是否可以优化?

    可以。 我没有优化过,网上有很多文章,没有遇到过这种场景。

    这一节只是介绍一下什么是反射,和反射的优缺点,这是一个系列,所以后面大多数是介绍一些反射的api调用 和 原理。以上为个人整理,如有错误望请指点。

    很大一部分参考于官方文档: https://docs.microsoft.com/zh-cn/dotnet/framework/reflection-and-codedom/reflection, 只是个人用通俗的语言整理一下。

  • 相关阅读:
    计算广告(1):计算广告基础
    shell定时任务
    shell脚本运行报错:-bash: xxx: /bin/sh^M: bad interpreter: No such file or directory
    spark文件读取与保存(scala实现)
    spark中的共享变量(广播变量和累加器)
    spark基本概念与运行架构
    用二维数组输出螺旋方阵(JAVA实现)
    InputStream、InputStreamReader和BufferedReader
    Arrays.sort()自定义排序的实现
    BufferedReader和Scanner
  • 原文地址:https://www.cnblogs.com/aoximin/p/16440966.html
Copyright © 2020-2023  润新知