C#之反射
什么是反射?
程序集包含模块,而模块又包括类型,类型下有成员,反射就是管理程序集,模块,类型的对象,它能够动态的创建类型的实例,设置现有对象的类型或者获取现有对象的类型,能调用类型的方法和访问类型的字段属性。它是在运行时创建和使用类型实例。
引用的:进击的NetER,地址:https://www.bilibili.com/video/BV19J411v7yk?from=search&seid=4031467641635957500
c#编写的代码经过编译器编译,最终生成了dll(程序集,程序集采用的是公共中间语言CIL,或者简称为中间语言IL)或者exe文件,,此时的处理器是不认识这些文件的,为了将CIL代码转换成处理器能够理解的机器码,需要完成一个额外的步骤,通常在执行时进行。虚拟执行系统(VES),偶尔也称之为运行时,它根据需要来编译CIL代码,这个过程称之为即时编译或者JIT编译(just-in-time complelation)。假如代码在“运行时”这样一个“代理”的上下文中执行,就将这些代码称为托管代码,而在“运行时”的控制下的过程称为托管执行。之所以称为托管代码,是因为“运行时”管理着诸如内存分配,安全性和JIT编译等方面,从而控制了程序的主要行为。执行过程中不需要“运行时”的代码称为本机代码或非托管代码。
CLR的核心功能内存管理、程序集加载、安全性、异常处理、线程同步、泛型、尾调用指令和基本的公共语言基础结构 (CLI) 类型系统等。可以将它看成一个平台。
注意:dll或exe中包含了metada元数据和IL语言,元数据: 每个托管代码都包含元数据表,主要有两种表:一种表描述源代码中定义类和成员,另一种描述代码中引用的类型和成员。比如经常使用的泛型:总结来说就是里面记录了类型里面有什么元素。
1.反射的定义
反射是指对程序集中的元数据进行检查的过程。就是读取元数据,可以使用元数据中的元素。Reflection是微软提供的一个帮助类库。
2.获取某个程序集下面所有的类名称
//动态加载dll:这是从exe所在路径查找,不需要加后缀 Assembly assembly = Assembly.Load("Reflection.DB.Mysql"); //这种是全路径:@加在字符串前面,字符串中的 \ 失去转义符的作用,直接写字符串而不需要考虑转义字符,而且表示空格换行都保留着。 Assembly assembly1 = Assembly.LoadFile(@"D:\a_云数\反射1\ConsoleApp1\ConsoleApp1\bin\Debug\netcoreapp2.1\Reflection.DB.Mysql.dll"); //这也是在当前路径查找,这种和第一个相比仅仅是把后缀加上。 Assembly assembly2 = Assembly.LoadFrom("Reflection.DB.Mysql.dll"); //获取 Type类型的当前对象的类型 //获取当前dll中的所有类名称 foreach (Type type in assembly1.GetTypes()) { //把Reflection.DB.Mysql.dll下面所有的类名称全部取出来了。 Console.WriteLine(type.Name); } Console.ReadKey();
下面是这个类库下的所有类名称:
结果:
Class1
MySqlHelper
3.获取具体类中的方法
下面的步骤简单总结为:动态加载程序集中的信息->然后获取要使用的类信息->生成类对象->将对象转化为目的类对象->d调用方法。此步骤中存在步骤4强制转换,前提是知道当前类是什么。
(1)还有就是下面之所以用as强制转换是因为发生异常的时候不会报错,会返回null。
#region 获取其中一个类中的方法并调用 //使用反射之前的操作 // IDBHelper help = new SqlSrverHelper(); // help.Query(); //1.动态加载 Assembly assemblyOne = Assembly.Load("Reflection.DB.SqlServer"); //2.获取Reflection.DB.SqlServer类库下的SqlSrverHelper类信息 Type type1 = assemblyOne.GetType("Reflection.DB.SqlServer.SqlSrverHelper"); //3.创建对象 object objectStart = Activator.CreateInstance(type1); //4.类型转换 IDBHelper helper = objectStart as IDBHelper; //调用方法 helper.Query();
public class SqlSrverHelper:IDBHelper { private static string ConnectionString = ConfigurationManager.ConnectionStrings["connect"].ConnectionString; public SqlSrverHelper() { Console.WriteLine("{0},被构造",this.GetType().Name); } public void Query() { Console.WriteLine("{0},Query", this.GetType().Name); } }
结果:
注意:使用反射的时候还是先进入了无参的构造函数
SqlSrverHelper,被构造
SqlSrverHelper,Query
(2)还有一点就是强制转换那一步也可以直接用dynamic来代替,因为dynamic在编译时候不会做类型检查,是在运行时进行的。如下:
//使用反射之前的操作 // IDBHelper help = new SqlSrverHelper(); // help.Query(); //1.动态加载 Assembly assemblyOne = Assembly.Load("Reflection.DB.SqlServer"); //2.获取Reflection.DB.SqlServer类库下的SqlSrverHelper类信息 Type type1 = assemblyOne.GetType("Reflection.DB.SqlServer.SqlSrverHelper"); //3.创建对象 dynamic helper = Activator.CreateInstance(type1); //4.类型转换 //调用方法 helper.Query(); //下面代码报错,因为SqlSrverHelper类中没有get()方法,但是在编译的时候编译器不会提示错误 //因为dynamic是在运行时才会做检查 helper.get();
看上面的代码觉的很麻烦,还不如直接拿目标类new对象直接调用,所以我们可以封装一个类出来,用的时候直接调用
public class SimpleReflection { public static IDBHelper CreateInstance() { //1.动态加载 Assembly assemblyOne = Assembly.Load("Reflection.DB.SqlServer"); //2.获取Reflection.DB.SqlServer类库下的SqlSrverHelper类信息 Type type1 = assemblyOne.GetType("Reflection.DB.SqlServer.SqlSrverHelper"); //3.创建对象 object objectStart = Activator.CreateInstance(type1); //4.类型转换 return objectStart as IDBHelper; } } public class Program { static void Main(string[] args) { IDBHelper dBHelper = SimpleReflection.CreateInstance(); dBHelper.Query(); } }
但是上面的代码中的dll名称还是写死在代码中的,所以我们可以放在web配置文件中来获取。这样实现了依赖的可配置,断开了对细节的依赖。
比如公司新来一个技术经理,不用sqlserver了,改用mysql,那么我们可以直接修改配置文件中的程序集名称,不用再更改代码。
比如:
public static IDBHelper CreateInstance() { //Reflection.DB.SqlServer.dll 这里之所以加了后缀dll,是因为下面使用LoadFrom方法,此时再使用Load在.netCore中可能会存在问题 //因为此时不在当前类库下引用Reflection.DB.SqlServer.dll程序集了,实现了依赖反转 string dllName = ConfigurationManager.AppSettings["dllName"]; //Reflection.DB.SqlServer.SqlSrverHelper string typeName = ConfigurationManager.AppSettings["typeName"]; //1.动态加载 Assembly assemblyOne = Assembly.LoadFrom(dllName); //2.获取Reflection.DB.SqlServer类库下的SqlSrverHelper类信息 Type type1 = assemblyOne.GetType(typeName); //3.创建对象 object objectStart = Activator.CreateInstance(type1); //4.类型转换 return objectStart as IDBHelper; }
4.
Object.ReferenceEquals(left, right)静态方法:从名称中便可知它用来比较两者是否是相同的引用,我们也永远不应该去重写该方法。它对于值类型对象的比较永远返回false;对于两个null的比较永远返回true。
string peom1 = "Kubla Khan"; string peom2 = "Kubla Khan"; string peom3 = String.Copy(peom2); //ReferenceEquals()判断两个字符串是否指向相同的内存地址 Console.WriteLine("peom1 == peom2:" + (peom1 == peom2));//True Console.WriteLine("peom1 == peom3:" + (peom1 == peom3));//True Console.WriteLine("ReferenceEquals(peom1,peom3):" + ReferenceEquals(peom1, peom3));//False //Equals,先判断两个字符串有相同的内存位置,则两个字符串相等;否则逐字符比较两个字符串,判断是否相等 Console.WriteLine("Equal(peom1,peom3):" + String.Equals(peom1, peom2));//true Console.WriteLine("Equal(peom1,peom3):" + String.Equals(peom1, peom3));//true
结果:
peom1 == peom2:True peom1 == peom3:True ReferenceEquals(peom1,peom3):False Equal(peom1,peom3):True Equal(peom1,peom3):True
反射能够破坏单例模式(private,protected等访问修饰符在反射面前形同虚设) :
/// <summary> /// 单例模式: 能保证在进程中只有一个实例 /// </summary> public sealed class Singleton { private static Singleton _Singleton =null; /// <summary> /// 私有 (1) /// </summary> private Singleton() { Console.WriteLine("被构造"); } /// <summary> /// 静态构造函数 (2) /// </summary> static Singleton() { _Singleton = new Singleton(); }
/// <summary> /// 静态构造函数 (3) /// </summary>
public static Singleton GetInstance() { return _Singleton; } }
下面是不同的调用方法:
Singleton s = Singleton.GetInstance(); Singleton s1 = Singleton.GetInstance(); Singleton s2 = Singleton.GetInstance(); Console.WriteLine(object.ReferenceEquals(s,s2)); //使用反射 Assembly assembly = Assembly.Load("Reflection.DB.SqlServer"); //获取类型 Type type = assembly.GetType("Reflection.DB.SqlServer.Singleton"); //创建对象 object o1 = (Singleton)Activator.CreateInstance(type,true); object o2 = (Singleton)Activator.CreateInstance(type, true); object o3 = (Singleton)Activator.CreateInstance(type, true); Console.WriteLine(object.ReferenceEquals(o1, o3));//判断2者是否是同一个实例,
如上述代码,普通的实例化对象调用 Singleton 类中的GetInstance方法,在Singleton类中的步骤为2>1>3。这是第一次调用的步骤,之后就只会调用第3个步骤,因为_Singleton是一个静态变量,第一次赋值之后就可以直接获取了。
但是使用反射的话,可以直接访问私有的构造函数,直接且只执行(1).因为每次执行了一次构造函数,所以每次都是全新的实例,所以为False。
c#中的对未赋值的称为对象,赋值的之后称之为实例,实例化可以说是new 对象的过程。
结果:
被构造
True
被构造
被构造
被构造
False
下面是使用反射调用类中的不同方法:
比如在MVC中,根据http://localhost:8080/home/index这个路径就能找到home类中的index方法对应的方法(非类型转换),就是通过反射实现的,如下面的调用形式,知道home类,然后调用home类中的index方法(home控制器本身就是一个类)
//使用反射 Assembly assembly = Assembly.Load("Reflection.DB.SqlServer"); //获取类型 Type type = assembly.GetType("Reflection.DB.SqlServer.ReflectionTest"); //创建对象 object oReflectionTest = Activator.CreateInstance(type); //获取特定方法 MethodInfo method = type.GetMethod("Show2");//如果是所有方法的话则是 type.GetMethods() //调用方法: method.Invoke(oReflectionTest, new object[] { 12}); //调用重载方法,所以我们可以设置重载方法参数的类型顺序来接收,注意,前后顺序要一致。 //这是调用重载方法之一的show3方法 MethodInfo mesthodShow3 = type.GetMethod("Show3",new Type[] {typeof(int),typeof(string) }); mesthodShow3.Invoke(oReflectionTest,new object[] {12,"重载方法" }); //调用私有的方法Show4 BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance; MethodInfo methodShow4 = type.GetMethod("Show4", flags); methodShow4.Invoke(oReflectionTest,new object[] { "私有方法"}); //调用静态 MethodInfo mesthodShow5 = type.GetMethod("Show5"); mesthodShow5.Invoke(null,new object[] {"静态方法" });
ReflectionTest类:
/// <summary> /// 反射测试类 /// </summary> public class ReflectionTest { #region Identity /// <summary> /// 无参构造函数 /// </summary> public ReflectionTest() { Console.WriteLine("这是{0}的无参数构造函数",this.GetType()); } /// <summary> /// 这是有参数的构造函数 /// </summary> /// <param name="name"></param> public ReflectionTest(string name) { Console.WriteLine("这是{0}的构造函数", this.GetType()); } public ReflectionTest(int id) { Console.WriteLine("这是{0}的构造函数", this.GetType()); } public ReflectionTest(int id,string name) { Console.WriteLine("这是{0}的构造函数", this.GetType()); } #endregion #region 方法 public void Show1() { Console.WriteLine("这是{0}的Show1", this.GetType()); } public void Show2(int id) { Console.WriteLine("这是{0}的Show2", this.GetType()); } /// <summary> /// 重载方法之一 /// </summary> /// <param name="id"></param> /// <param name="name"></param> public void Show3(int id,string name) { Console.WriteLine("这是{0}的Show3", this.GetType()); } /// <summary> /// 重载方法之二 /// </summary> /// <param name="id"></param> /// <param name="name"></param> public void Show3(string name,int id ) { Console.WriteLine("这是{0}的Show3_2", this.GetType()); } /// <summary> /// 重载方法之三 /// </summary> /// <param name="id"></param> public void Show3( int id) { Console.WriteLine("这是{0}的Show3_3", this.GetType()); } /// <summary> /// 重载方法之四 /// </summary> /// <param name="name"></param> public void Show3(string name) { Console.WriteLine("这是{0}的Show3_4", this.GetType()); } /// <summary> /// 重载方法之五 /// </summary> public void Show3() { Console.WriteLine("这是{0}的Show3_5", this.GetType()); } /// <summary> /// 私有方法 /// </summary> /// <param name="name"></param> private void Show4(string name) { Console.WriteLine("这是{0}的Show4", this.GetType()); } /// <summary> /// 静态方法 /// </summary> /// <param name="name"></param> public static void Show5(string name) { Console.WriteLine("这是{0}的Show5",typeof(ReflectionTest)); } #endregion }
5.泛型与反射
测试的泛型类:
namespace Reflection.DB.SqlServer { public class GenericClass<T, W, X> { public void Show(T t, W w, X x) { Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name+"-值:"+t, w.GetType().Name + "-值:" + w, x.GetType().Name + "-值:" + x); } } /// <summary> /// 普通类中含有泛型方法 /// </summary> public class GenericMethod { public void Show<T, W, X>(T t, W w, X x) { Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name + "-值:" + t, w.GetType().Name + "-值:" + w, x.GetType().Name + "-值:" + x); } } public class GenericDouble<T> { public void Show<W, X>(T t, W w, X x) { Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name + "-值:" + t, w.GetType().Name + "-值:" + w, x.GetType().Name + "-值:" + x); } } }
(1)调用普通类中的泛型
//动态加载 Assembly assembly = Assembly.Load("Reflection.DB.SqlServer"); //获取类型 Type type = assembly.GetType("Reflection.DB.SqlServer.GenericMethod"); //创建对象 object oReflection = Activator.CreateInstance(type); //获取泛型方法 MethodInfo method = type.GetMethod("Show"); //调用泛型的时候,反射同样需要指定确定的类型 MethodInfo show = method.MakeGenericMethod(typeof(int),typeof(string), typeof(DateTime)); //调用方法 show.Invoke(oReflection,new object[] {12,"大地的时间:",DateTime.Now });
结果:
t.type=Int32-值:12,w.type=String-值:大地的时间:,x.type=DateTime-值:2020/5/2 17:34:13
(2)调用泛型类中的泛型方法
#region 泛型类中的泛型发方法与反射 //动态加载 Assembly assembly = Assembly.Load("Reflection.DB.SqlServer"); //获取类型,GenericClass是三个参数泛型类 Type type = assembly.GetType("Reflection.DB.SqlServer.GenericClass`3"); //然后确定这个三个参数的具体类型 Type genericType = type.MakeGenericType(new Type[] {typeof(int),typeof(string),typeof(DateTime)}); //创建对象 object oReflection = Activator.CreateInstance(genericType); //获取方法 MethodInfo method = genericType.GetMethod("Show"); //调用方法 method.Invoke(oReflection,new object[] {2,"测试",DateTime.Now }); #endregion
结果:
t.type=Int32-值:2,w.type=String-值:测试,x.type=DateTime-值:2020/5/3 16:20:53
(3)调用泛型类和泛型方法参数不一致的情况
//动态加载 Assembly assembly = Assembly.Load("Reflection.DB.SqlServer"); //获取类型,GenericClass是三个参数泛型类 Type type = assembly.GetType("Reflection.DB.SqlServer.GenericDouble`1"); //然后确定这个三个参数的具体类型 Type genericType = type.MakeGenericType(new Type[] {typeof(int)}); //创建对象 object oReflection = Activator.CreateInstance(genericType); //获取方法 MethodInfo method = genericType.GetMethod("Show"); MethodInfo showMethod = method.MakeGenericMethod(new Type[] {typeof(string),typeof(DateTime) }); //调用方法 showMethod.Invoke(oReflection,new object[] {2,"测试",DateTime.Now });
结果:
t.type=Int32-值:2,w.type=String-值:测试,x.type=DateTime-值:2020/5/3 16:38:35
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术