先贴一张网上的图:

介绍几个概念:
- ILspy:逆向工程工具:可以把Dll/Exe文件反编译回C#代码;
- IL:是对标于C#代码的代码,不太好阅读
-
metadata:是一个清单数据,只是记录有什么,而不是展示所有的实现;(明细账本)
比如像下面这个熟悉的List类,只是列出了所有的方法供调用,并没有把这些方法的所有实现全都写在里面;
image.png - 反射是System.Reflection命名空间,可以读取metadata,并使用metadata;是微软提供的一个帮助类库;
正题
举例项目的目录结构

- 当我们在写程序的时候,通常创建对象,调用对象方法,会使用如下方式
IDBHelper dBHelper = new SqlServerHelper();
dBHelper.Query();
这种方式,就要在开头有一段这样的引用将涉及到的类的命名空间引用进来
using XXXX.AspNetCore.DB.SqlServer;
- 如果通过反射的方式,要实现想上面那种创建对象,调用方法,怎么做呢?
我们分步骤来:
首先,应该像上面那样引用对应的命名空间
//1.using System.Reflection 有下列几种引入方式,自行选择
Assembly assembly = Assembly.Load("Xxxx.AspNetCore.DB.SqlServer"); //Dll名称,不需要后缀
//Assembly assembly1 = Assembly.LoadFrom(@"这里写的是目录中SqlServerHelper类所在类库编译后的dll文件的全路径"); //全路径 从头写到尾
//Assembly assembly3 = Assembly.LoadFrom(@"Xxxx.AspNetCore.DB.SqlServer.dll"); //dll名称(需要后缀)
//Assembly assembly2 = Assembly.LoadFile(@"同样是全路径"); //从头写到尾 全路径
其次,引入命名空间后,我们就可以获取类型
Type type = assembly.GetType("Xxxx.AspNetCore.DB.SqlServer.SqlServerHelper");
继续,通过类型创建对象
object obj = Activator.CreateInstance(type);
然后,调用方法,下图是SqlServerHelper类存在的方法

有下面几种方法调用的方式
obj.Query(); //行不通,不能Query,因为类型是Object声明的,在C# 语言中;变量的声明是编译时决定;因为是Object,所以不能调用;
dynamic dObject= Activator.CreateInstance(type);
dObject.Get();
dObject.Show();
//动态类型,dynamic 是运行是决定;可以避开编译器的检查(编写代码时不会报错),但是如果通过dynamic声明的对象去动态调用类里不存在的方法,在程序运行时就会抛异常。
上面创建对象和调用方法可以通过类型转换优化一下
IDBHelper dBHelper=(IDBHelper)obj //如果实际类型不一样,会报异常
IDBHelper dBHelper = obj as IDBHelper;
dBHelper.Query();
上面两种创建对象调用方法 分别是普通方式和用反射的方式实现,用反射的方式感觉代码量增加了,怎么解决?没错,封装一下 0.0, 可以封装成下面的样子
public class SimpleFactory
{
public static IDBHelper CreateInstance()
{
string ReflictionConfig = CustomConfigManager.GetConfig("ReflictionConfig");
string tyepName= ReflictionConfig.Split(",")[0];
string dllName = ReflictionConfig.Split(",")[1];
//Assembly assembly = Assembly.Load(dllName); //Dll名称,不需要后缀
Assembly assembly3 = Assembly.LoadFrom(dllName); //dll名称(需要后缀)
Type type = assembly3.GetType(tyepName);
object obj = Activator.CreateInstance(type);
return obj as IDBHelper;
}
}
public static class CustomConfigManager
{
//Core 读取配置文件:appsettings
//1.Microsoft.Extensions.Configuration;
//2.Microsoft.Extensions.Configuration.Json
public static string GetConfig(string key)
{
//默认读取 当前运行目录
var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
IConfigurationRoot configuration = builder.Build();
string configValue = configuration.GetSection(key).Value;
return configValue;
}
}
}
温馨提示:在.net core 中 Assembly.Load 和 Assembly.LoadFrom 建议使用后者,前者使用时如果没有提前做一些处理,会出现找不到文件的异常
封装完以后,我们就可以很方便的调用代码啦,如下:
IDBHelper dBHelper= SimpleFactory.CreateInstance();
dBHelper.Query();
细心的读者会发现,我在封装反射的时候,在调用Assembly.LoadFrom()方法这一块,本来参数是写死的DLL文件,最后改成了通过程序的配置文件的Key 获取到DLL文件这个Value的方式
这是我项目的配置文件
{
"exclude": [
"**/bin",
"**/bower_components",
"**/jspm_packages",
"**/node_modules",
"**/obj",
"**/platforms"
],
"ReflictionConfig": "Xxxxx.AspNetCore.DB.SqlServer,Zhaoxi.AspNetCore.DB.SqlServer"
}
这样做的好处是什么呢?答案可想而知,假设现在公司用的是SqlServer 数据库,但是突然某一天换了个技术经理,要求将公司的SqlServer 数据库改成Mysql 数据库,用传统的不是反射的方法,我们的做法是将 IDBHelper dBHelper = new SqlServerHelper();这段代码改成
IDBHelper dBHelper = new MySqlHelper(); 然后再重新编译运行发布。但是通过上面封装反射,并且通过读取配置文件的形式,并不需要修改代码,需要做的就是在程序外部 Copy Dll文件(将mysql程序集的dll文件拷贝到bin目录下)

修改配置文件,把数据库的版本给更换了
将 "ReflictionConfig": "Xxxxx.AspNetCore.DB.SqlServer,Zhaoxi.AspNetCore.DB.SqlServer" 改成
"ReflictionConfig": "Xxxxx.AspNetCore.DB.MySql.MySqlHelper,Zhaoxi.AspNetCore.DB.MySql.dll"
这样做实现了程序的可配置;程序的可扩展; 使程序更好的解耦:去掉对细节的依赖。
反射能不能做到一些普通方式做不到的事儿呢? 可以
传统的单例模式
{
private static Singleton _Singleton = null;
/// <summary>
/// 创建对象的时候执行
/// </summary>
private Singleton()
{
Console.WriteLine("Singleton被构造");
}
/// <summary>
/// 被CLR 调用 整个进程中 执行且只执行一次
/// </summary>
static Singleton()
{
_Singleton = new Singleton();
}
public static Singleton GetInstance()
{
return _Singleton;
}
}
测试单例模式
Singleton singleton = Singleton.GetInstance();
Singleton singleton1 = Singleton.GetInstance();
Singleton singleton2 = Singleton.GetInstance();
Console.WriteLine(object.ReferenceEquals(singleton, singleton1));
Console.WriteLine(object.ReferenceEquals(singleton, singleton2));
Console.WriteLine(object.ReferenceEquals(singleton1, singleton2));
//ReferenceEquals,如果是同一个引用,true 否则 false
运行的结果是输出三个 true,因为静态方法在程序运行的时候只能在最开始的时候执行一次,同时这个单例模式中无参构造器用private来修饰,意味着在外部无法通过new的方法来创建Singleton对象,所以即使调用了三次GetInstance(),得出来的三个对象其实地址是一样的(同一个对象);然后利用反射的方式,反射可以突破方法的权限限制,如下:
Assembly assembly = Assembly.LoadFrom("Xxxxx.AspNetCore.DB.SqlServer.dll"); //dll名称(需要后缀)
Type type = assembly.GetType("Xxxxx.AspNetCore.DB.SqlServer.Singleton");
Singleton singleton = (Singleton)Activator.CreateInstance(type, true);
Singleton singleton1 = (Singleton)Activator.CreateInstance(type, true);
Singleton singleton2 = (Singleton)Activator.CreateInstance(type, true);
Console.WriteLine(object.ReferenceEquals(singleton, singleton1));
Console.WriteLine(object.ReferenceEquals(singleton, singleton2));
Console.WriteLine(object.ReferenceEquals(singleton1, singleton2));
通过在Singleton的无参构造打断点的方式来调试,发现当用反射的方法来创建类的对象时,即使无参构造器是用private修饰的,仍然可以每次都进入无参构造,结果就是输出三个false,三个对象地址都不一样。
反射几乎可以实现所以想要的功能,但是反射难道就没有局限吗?
虽然反射很强大,但是同时也存在着一些性能上的问题;下面举一个测试用例
public class Monitor
{
public static void Show()
{
Console.WriteLine("*******************Monitor*******************");
long commonTime = 0;
long reflectionTime = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000_000; i++) //1000000000
{
IDBHelper iDBHelper = new SqlServerHelper();
iDBHelper.Query();
}
watch.Stop();
commonTime = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
//Assembly assembly = Assembly.Load("Xxxx.AspNetCore.DB.SqlServer");//1 动态加载
//Type dbHelperType = assembly.GetType("Xxxx.AspNetCore.DB.SqlServer.SqlServerHelper");//2 获取类型 消耗性能
//缓存的使用;
for (int i = 0; i < 1000_000; i++)
{
Assembly assembly = Assembly.Load("Xxxx.AspNetCore.DB.SqlServer");//1 动态加载
Type dbHelperType = assembly.GetType("Xxxx.AspNetCore.DB.SqlServer.SqlServerHelper");//2 获取类型
object oDBHelper = Activator.CreateInstance(dbHelperType);//3 创建对象
IDBHelper dbHelper = (IDBHelper)oDBHelper;//4 接口强制转换
dbHelper.Query();//5 方法调用
}
watch.Stop();
reflectionTime = watch.ElapsedMilliseconds;
}
Console.WriteLine($"commonTime={commonTime} reflectionTime={reflectionTime}");
}
}
上面代码实现的功能简单来说就是分别测试用普通的new 来创建对象和用反射来创建对象在分别创建100万次对象并调用方法的情况下所花费的时间。

很明显的差距。。。足以证明反射的性能相对来说是比较低的。
所以,在使用反射的时候,要有一种扬长避短的原则,尽量发挥反射最大的用处,至于性能上的不足,我们可以在编写代码的时候,尽量的去优化代码,"正确"的使用反射,比如上面利用反射创建100万次对象的代码,我们其实可以把1.动态加载和2.创建类型放到for循环外面,只需要进行一次操作就行,这样改进后结果如下:

性能得到了很大的优化,我又分别让两种方式都执行了一亿次。。。。。如下,

稍微总结一下,经过优化以后,其实反射性能并没有想象的那么差;所以需要理性选择;
我觉得反射没有不该用的地方,应该正确使用;
