.NET学习笔记(二)----无处不在的反射(包含读取json的方法)

脑图:

在这里插入图片描述

前言:

  1. 反射的实现:只需要更换dll与配置文件就可以进行项目的变更无需停止程序的运行或者重新加载
  2. 反射的强大之处:可以做到普通方法做不到的事儿
  3. 除了反射以外,没有其他方法可以对私有化方法进行访问,私有化的就只能从内部访问
  4. 反射动态记载dll,元数据中只要有的,都可以给找出来
  5. 完全不用关注权限问题
  6. 反射的应用场景
    IOC容器----反射+ 配置文件+ 工厂
    MVC(路由器)----类名+方法名就可以调用方法

一、反射/反编译工具/高级语言到计算机语言的历程

**反射:**来自于System.Reflection,是一个帮助类库----编译工具把高级语言编译为dll/exe(注1),反射就是读取dll/exe中的metadata(注2)从而可以使用dll/exe中所包含的东西(代码中含有一切东西:方法、类等),并且可以动态创建dll/exe(Emit)

注释:
1.C#代码编译器编译DLL/EXECLR/JIT机器码(0101010101)
2.metadata:元数据数据清单----记录了dll/exe中包含了哪些东西


二、反射创建对象

项目结构:
在这里插入图片描述

使用的类:

public class AssemblyTest
{
	public class TT
	{
		public int id { get; set; }

		public string Name { get; set; }
	}

	public void Test1()
	{
		Console.WriteLine("This is Test");
	}

	public void Test2()
	{
		Console.WriteLine("This is Test2");
	}

	public interface TestInterface
	{
		void Test2();
	}
}

1.动态读取dll

//参数:dll的名称,需要后缀
Assembly assembly1 = Assembly.LoadFrom("AssemblyTest.dll");
//参数:dll的名称,不需要后缀
Assembly assembly2 = Assembly.Load("AssemblyTest");
//参数:dll的全路径,需要后缀
Assembly assembly3 = Assembly.LoadFile(@"D:\测试\AssemblyTest\bin\Debug\net6.0\AssemblyTest.dll");

2.获取某一个具体的类型

//参数:dll的名字+类名
Type type = assembly1.GetType("AssemblyTest.AssemblyClass");

3.创建对象并调用

//dynamic:可用但不推荐
{
	//dynamic作为类型的时候,调用没有限制
	dynamic dInstance = Activator.CreateInstance(type);
	dInstance.Test1();
	dInstance.GET();
}

//Object
{
	//Object无法使用类中的东西,如果想用需要强转。Activator.CreateInstance()在类工厂中动态创建类的实例
	object? oInstance = Activator.CreateInstance(type);

	//可用但不推荐:但是如果oInstance的真实类型与强转的类型不一致会导致报错,
	AssemblyClass assemblyClass1 = (AssemblyClass)oInstance;
	assemblyClass1.Test1();

	//推荐此方法,如果类型一致就转换,如果不一致就返回null
	AssemblyClass assemblyClass = oInstance as AssemblyClass;
	assemblyClass.Test1();
}

三、断开依赖----从配置文件(Json)中读取

Json展示:

架构:https://json.schemastore.org/appsettings.json

在这里插入图片描述

{
	"ReflictionConfig": "AssemblyTest.AssemblyClass,AssemblyTest.dll"
}

json文件要记得右击设置为始终复制,否则会出现找不到文件的错误

在这里插入图片描述
代码展示:

//传入json中的key
string ReflictionConfig = GetConfig("ReflictionConfig");
//根据逗号分隔字符串并赋值
string typeName = ReflictionConfig.Split(',')[0];
string dllName = ReflictionConfig.Split(',')[1];
//读取dll
Assembly assembly = Assembly.LoadFrom(dllName);
//获取某一个具体的类型
Type type = assembly.GetType(typeName);
object? oInstance = Activator.CreateInstance(type);
AssemblyClass assemblyClass = oInstance as AssemblyClass;
assemblyClass.Test1();

//读取json文件的方法
//Core 读取配置文件:assemblysettings
string GetConfig(string key)
{
    //需要安装Microsoft.Extensions.Configuration.Json包
    var builder = new ConfigurationBuilder().AddJsonFile("assemblysettings.json");  //默认读取  当前运行目录
    IConfigurationRoot configuration = builder.Build();
    string configValue = configuration.GetSection(key).Value;
    return configValue;
}

四、反射黑科技----反射破坏单例

创建一个Singleton(单例)类,单例类中存在私有的Singleton()方法

using System;

namespace AssemblyTest
{
	/// <summary>
	/// 单例模式:类,能保证再整个进程中只有一个实例
	/// </summary>
	public sealed class Singleton
	{
		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 singleton1 = Singleton.GetInstance();
Singleton singleton2 = Singleton.GetInstance();
Singleton singleton3 = Singleton.GetInstance();
Singleton singleton4 = Singleton.GetInstance();

调试后发现,虽然声明了四次但是静态构造函数实际上只执行了一次

执行结果
在这里插入图片描述
使用反射声明

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.Singleton");
Singleton singleton1 = (Singleton)Activator.CreateInstance(type, true);
Singleton singleton2 = (Singleton)Activator.CreateInstance(type, true);
Singleton singleton3 = (Singleton)Activator.CreateInstance(type, true);
Singleton singleton4 = (Singleton)Activator.CreateInstance(type, true);

调试后发现,每一次声明都可以突破访问修饰符的权限访问静态构造函数

执行结果:
在这里插入图片描述


五、反射调用

1、利用反射访问构造函数

创建ReflectionTest类,里面有无参与有参构造函数:
在这里插入图片描述
测试用的代码:

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.ReflectionTest");
//调用无参数构造函数的
object noParaObject = Activator.CreateInstance(type);
//调用有参数的---需要传递一个object类型的数组作为参数,参数按照从左往右严格匹配
//按照参数的类型去执行对应的和参数类型顺序匹配的构造函数
//如果没有匹配的---报异常
object paraObject = Activator.CreateInstance(type, new object[] { 123 });
object paraObject1 = Activator.CreateInstance(type, new object[] { "字符串" });
object paraObject2 = Activator.CreateInstance(type, new object[] { 234, "字符串" });
object paraObject3 = Activator.CreateInstance(type, new object[] { "字符串", 456 });

执行结果:
在这里插入图片描述

2、反射调用方法

测试需要使用的方法们
在这里插入图片描述
测试用的代码:

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.ReflectionTest");
object oMethod = Activator.CreateInstance(type);

执行MethodInfo 的Invoke方法,传递方法所在的类的实例对象+参数

①.调用无参方法
//获取方法
MethodInfo method1 = type.GetMethod("Methods1");
//调用无参方法的三种的方式
method1.Invoke(oMethod, new object[] { });
method1.Invoke(oMethod, new object[0]);
method1.Invoke(oMethod, null);

执行结果:
在这里插入图片描述

②.调用有参方法
//获取方法
MethodInfo method2 = type.GetMethod("Methods2");
method2.Invoke(oMethod, new object[] { 123 });

执行结果:
在这里插入图片描述
跟调用构造函数一样,如果传入的参数类型没有与现有方法中的参数类型相符的就会报错

③.调用重载方法

需要通过方法参数类型类区别方法
传递参数–严格匹配参数类型

MethodInfo methods3_1 = type.GetMethod("Methods3", new Type[] { typeof(string), typeof(int) });
Methods3_1.Invoke(oMethod, new object[] { "字符串", 234 });

MethodInfo methods3_2 = type.GetMethod("Methods3", new Type[] { typeof(int), typeof(string) });
Methods3_2.Invoke(oMethod, new object[] { 234, "字符串" });


MethodInfo methods3_3 = type.GetMethod("Methods3", new Type[] { typeof(string) });
Methods3_3.Invoke(oMethod, new object[] { "字符串" });

MethodInfo methods3_4 = type.GetMethod("Methods3", new Type[] { typeof(int) });
Methods3_4.Invoke(oMethod, new object[] { 234 });


MethodInfo methods3_5 = type.GetMethod("Methods3", new Type[] { });
Methods3_5.Invoke(oMethod, null);

执行结果:
在这里插入图片描述

④.调用私有方法

在获取私有方法的时候,加上参数BindingFlags.NonPublic || BindingFlags.Instance

MethodInfo methods4 = type.GetMethod("Methods4", BindingFlags.NonPublic | BindingFlags.Instance);
method4.Invoke(oMethod, new object[] { "String" });

执行结果:
在这里插入图片描述

⑤.调用静态方法
MethodInfo methods5 = type.GetMethod("Methods5");
//调用静态方法的两种方式
methods5.Invoke(null, new object[] { "String" });
methods5.Invoke(oMethod, new object[] { "String" });

执行结果:
在这里插入图片描述

⑥.调用泛型方法

获取到泛型方法后,先确定类型,严格按照参数类型传递参数就可以正常调用

1.GenericMethod的Show方法调用

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.GenericMethod");
object oMethod = Activator.CreateInstance(type);

//获取方法
MethodInfo show = type.GetMethod("Show");
//在执行之前选哟确定是什么类型; 
MethodInfo genericshow = show.MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
genericshow.Invoke(oMethod, new object[] { 123, "字符串", DateTime.Now });

执行结果:
在这里插入图片描述2.GenericClass的Show方法调用

获取具体类型时,需要指定几个类型,` 后面的数字就写几

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.GenericClass`3");
Type generType = type.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
object oInstance = Activator.CreateInstance(generType);
MethodInfo show = generType.GetMethod("Show");
show.Invoke(oInstance, new object[] { 123, "字符串", DateTime.Now });

Type type2 = assembly.GetType("AssemblyTest.GenericClass`2");
Type generType2 = type2.MakeGenericType(new Type[] { typeof(int), typeof(DateTime) });
object oInstance2 = Activator.CreateInstance(generType2);
MethodInfo show2 = generType2.GetMethod("Show");
show2.Invoke(oInstance2, new object[] { 123, DateTime.Now });

执行结果:
在这里插入图片描述
3.GenericDouble的Show方法调用

Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");
Type type = assembly.GetType("AssemblyTest.GenericDouble`1");
//在这里的时候已经给T指定了类型,所以在下面的代码中只需要提供两个类型就可以了
Type generType = type.MakeGenericType(new Type[] { typeof(int) });
object oInstance = Activator.CreateInstance(generType);
MethodInfo show = generType.GetMethod("Show");
//这里只需要给W,X指定类型就可以了
MethodInfo genericMethod = show.MakeGenericMethod(new Type[] { typeof(string), typeof(DateTime) });
genericMethod.Invoke(oInstance, new object[] { 123, "字符串", DateTime.Now });

执行结果:
在这里插入图片描述


六、反射调用属性、字段

相比较于普通方式对于属性、字段的操作,反射操作略显繁琐。但是因为反射的特性,使用反射操作可以让我们的程序更加稳定。当我们对一个类进行字段变更的时候,普通方式需要重新编译、发布,而反射在赋值阶段不能说是没啥优势,只能说是确实麻烦,但是在取值阶段,不需要修改代码直接就可以使用。

  1. 直接只用typeof()获取到类的type
  2. 将type传给Activator.CreateInstance方法
  3. 循环CreateInstance方法取到的Object值进行属性操作
  4. type.GetProperties();----操作属性,type.GetFields()----操作字段
  5. SetValue给属性赋值,GetValue取属性的值,Name为属性的名

使用到的类:
在这里插入图片描述
共用代码:

Type type = typeof(User);
object uObject = Activator.CreateInstance(type);

给属性赋值与获取值:

foreach (var prop in type.GetProperties())
{
	//输出属性的名
	Console.WriteLine(prop.Name);
	//逐个属性赋值
	if (prop.Name.Equals("Id"))
	{
		prop.SetValue(uObject, 134);
	}
	else if (prop.Name.Equals("Name"))
	{
		prop.SetValue(uObject, "张三");
	}
	else if (prop.Name.Equals("Age"))
	{
		prop.SetValue(uObject, 25);
	}
	//获取值
	Console.WriteLine(prop.Name + "=" + prop.GetValue(uObject));
}

给字段赋值与取值:

foreach (var prop in type.GetFields())
{
	//输出属性的名
	Console.WriteLine(prop.Name);
	//逐个属性赋值
	if (prop.Name.Equals("Description"))
	{
		prop.SetValue(uObject, "字符串");
	}
	else if (prop.Name.Equals("Description2"))
	{
		prop.SetValue(uObject, "字符串2");
	}
	
	//获取值
	Console.WriteLine(prop.Name + "=" + prop.GetValue(uObject));
}

七、反射的局限----性能问题

同样执行一百万次,普通方式要比反射方式性能快上不少,主要问题在加载dll的时候会损耗性能,所以在实际使用的时候将尽量加载dll放在循环外面。经过优化后反射的性能提升甚至跟普通方式差不多,再加上反射的能量真的很大,所以其实这点局限可以忽略不计。下面有我测试性能使用的测试方法以及测试结果,供大家参考
测试用的代码:

Console.WriteLine("*******************Monitor*******************");
long commonTime = 0;
long reflectionTime = 0;
{
	Stopwatch watch = new Stopwatch();
	watch.Start();
	for (int i = 0; i < 1000000; i++)
	{
		AssemblyClass assemblyClass = new AssemblyClass();
		assemblyClass.Test1();
	}
	watch.Stop();
	commonTime = watch.ElapsedMilliseconds;
}
{
	Stopwatch watch = new Stopwatch();
	watch.Start();
	Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");//1 动态加载
	Type aType = assembly.GetType("AssemblyTest.AssemblyClass");//2 获取类型  
	for (int i = 0; i < 1000000; i++)
	{
		//Assembly assembly = Assembly.LoadFrom("AssemblyTest.dll");//1 动态加载
		//Type aType = assembly.GetType("AssemblyTest.AssemblyClass");//2 获取类型  
		object oDBHelper = Activator.CreateInstance(aType);//3 创建对象
		AssemblyClass assemblyClass = (AssemblyClass)oDBHelper;//4 接口强制转换
		assemblyClass.Test1();//5 方法调用
	}
	watch.Stop();
	reflectionTime = watch.ElapsedMilliseconds;
}

Console.WriteLine($"commonTime={commonTime} reflectionTime={reflectionTime}");

普通方式的第一次测试结果:
在这里插入图片描述

commonTime=84434 reflectionTime=0

普通方式的第二次测试结果:
在这里插入图片描述

commonTime=85208 reflectionTime=0


反射方式的第一次测试结果(加载dll放在循环里面):
在这里插入图片描述

commonTime=0 reflectionTime=127708

反射方式的第二次测试结果(加载dll放在循环里面):
在这里插入图片描述

commonTime=0 reflectionTime=131105


反射方式的第一次测试结果(加载dll放在循环外面):
在这里插入图片描述

commonTime=0 reflectionTime=83324

反射方式的第二次测试结果(加载dll放在循环外面):
在这里插入图片描述

commonTime=0 reflectionTime=81914


八、Emit技术(运行时动态的生成dll)

程序集内部结构:
程序集的内部结构
在这里插入图片描述

等我玩的更明白了再继续更新第八点=。=


实战:

泛型和反射的实战: 泛型与反射的实际使用练习(包含一个泛型缓存)----手写ORM框架

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焦糖丨玛奇朵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值