C# 反射
文章目录
- C# 反射
- 前言
- 案例展示
- 将对象转为字典
- 测试用例
- 执行效果
- 代码讲解
- HasValue扩展
- 测试用例
- 执行效果
- 代码讲解
- 反射的底层逻辑
- 反射的原理
- 反射的基本概念
- 反射常用的API和方法
- GetType类
- Activator类
- PropertyInfo类
- EventInfo 类
- MemberInfo类
- MethodInfo类
- 反射的优缺点
- 优点
- 缺点
- 反射的性能问题
- 总结
前言
反射在我们实际开发中其实比较常见的,比如我们接口对接时将对象序列化为json字符串和将对方传过来的json字符串序列化为我们的DTO对象,还有进行依赖注入的时候其实都用到的反射。下面我就来用实际开发案例的方法来给大家讲解一些C# 反射的用法和原理。
案例展示
将对象转为字典
实现代码:
Dictionary<string, object> dictionary = new Dictionary<string, object>();
if (obj != null)
{foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)){if (property.CanRead){Type type = property.PropertyType;if (type.IsValueType){dictionary[property.Name] = "";}else{if (type == typeof(string)){dictionary[property.Name] = property.GetValue(obj, null)??"";}else if (typeof(IEnumerable).IsAssignableFrom(type)){dictionary[property.Name] =JsonConvert.SerializeObject(property.GetValue(obj, null));}else if(type.IsClass){dictionary[property.Name] = JsonConvert.SerializeObject(property.GetValue(obj, null));}else{dictionary[property.Name] = property.GetValue(obj, null) ?? 0;}}}}
}
else
{throw new ArgumentNullException(nameof(obj));
}return dictionary;
测试用例
public class HouseData{public float Size { get; set; }public string msg { get; set; }public Test test { get; set; }public List<Test> TestList { get; set; }}public class Test{public string Test1 { get; set; }public string Test2 { get; set; }public int Number { get; set; }}static void Main(string[] args){Test test1 = new Test();test1.Test1 = "12321";test1.Test2 = "55555";test1.Number =132465;HouseData houseData = new HouseData();houseData.test = test1;houseData.TestList = new List<Test>();houseData.TestList.Add(test1);houseData.Size = 123;houseData.msg = "sssss";Dictionary<string, object> keyValues = ToDictionary(houseData);Console.WriteLine(JsonConvert.SerializeObject(keyValues));Console.Read();}
执行效果
代码讲解
- 这里是循环对象中的属性,获取用public修饰的属性,这样私有变量就不会被进行序列化。
- 这里是反射对象的数据类型;下面的isValueType是判断该属性是否为值类型。
- 下面的
Ienumerable
是判断对象是否为可遍历类型,IsAssignableFrom
是判断我们对象中属性类型是否继承于Ienumerable
像List和Arry,这些数据类型都继承于Ienumerable。这里如果是继承自Ienumerable接口咱们就直接把它序列化为json字符串就可以了。 - IsClass是判断该属性是否是一个类。如果是一个类咱们就可以直接把它序列化为json字符串就可以了。
HasValue扩展
我们在代码编写过程中经常会遇到判断属性是否有值的情况,比如
- String类型
if(string.IsNullOrWhiteSpace(strVal))
-List类型
List<string> strings=new List<string>();if (strings != null && strings.Count > 0)
在编写代码的过程中我发现.netCore中的HasValue方法用起来还挺方便的
就像这样
但是只有int类型有这个属性其他类型就没有了,于是突发奇想要不自己写一个也方便使用。
以下代码是在.net 6框架中对值校验方法的补充,判断传入对象是否有值。
public static class ValueVerifyExtend{public static bool HasValue<T>(this T? obj){if (obj == null)return false;Type type = obj.GetType();//判断值类型是否有值if (type.IsValueType){//上面已经判断过是否为null,如果不为null那么值类型就是一定有值的return true;}else{//判断引用类型是否有值if (obj is { }){if (type == typeof(string)){return !string.IsNullOrWhiteSpace(obj.ToString());}else if (typeof(IEnumerable).IsAssignableFrom(type)){IEnumerable? Enumerable = obj as IEnumerable;if (Enumerable != null){IEnumerator? enumerator = Enumerable.GetEnumerator();if (enumerator != null){return enumerator.MoveNext();}else{return false;}}else{return false;}}else if (typeof(IListSource).IsAssignableFrom(type) && !type.IsInterface){IListSource? listSource = obj as IListSource;if (listSource != null){IList list = listSource.GetList();return list.Count > 0;}else{return false;}}else if (typeof(DataRow).IsAssignableFrom(type)){DataRow? row = obj as DataRow;if (row != null){return row.ItemArray.Count() > 0;}else{return false;}}else{return false;}}else{return false;}}}}
测试用例
int a = 0;Console.WriteLine("Int类型是否有值:" + a.HasValue());int? a1 = null;Console.WriteLine("Int类型是否有值:" + a1.HasValue());string str = "";Console.WriteLine("String类型是否有值:" + str.HasValue());string str1 = "123";Console.WriteLine("String类型是否有值:" + str1.HasValue());long? b = null;Console.WriteLine("Long类型是否有值:" + b.HasValue());long? b1 = 123465;Console.WriteLine("Long类型是否有值:" + b1.HasValue());List<string> strings=new List<string>();Console.WriteLine("List类型是否有值:" + strings.HasValue());strings.Add("123");Console.WriteLine("List类型是否有值:" + strings.HasValue());Dictionary<string,string> valuePairs=new Dictionary<string, string>();Console.WriteLine("Dictionary类型是否有值:" + valuePairs.HasValue());valuePairs.Add("rte", "resfas");Console.WriteLine("Dictionary类型是否有值:" + valuePairs.HasValue());Console.ReadLine();
执行效果
代码讲解
- 方法中首先进行的是是否为NULL的判断,都为NULL就没必要再走后续代码去耗费性能了
- 后面就判断传入的类型是值类型还是引用类型。值类型和引用类型的判断方式是不一样的。
(注意:在引用类型中string需要单独进行判断,它是一个特殊的引用类型。虽然它有值类型的特性,但在内存管理方面它表现得更像是引用类型
)
反射的底层逻辑
经过上述的两个案例想必大家对反射的应用也有初步的了解了,接下来我们来看看反射的内部是怎么实现的。
首先咱们来看看C# 文件的编译过程
这里给大家补充一些小知识
-
IL/MSIL (Microsoft Intermediate Language) 微软中间语言 (IL是MSIL的缩写,译为中间语言)
C#源代码通过LC转换为IL代码,IL主要包含一些元数据和中间语言指令JIT编译器把IL代码转为机器识别的机器代码,其实就相当于汇编语言
-
JIT (Just in time)即时编译器
代码转换为本地机器代码,从而提高程序的执行效率和性能
-
CTS (Common Type System)通用类型系统
一种规范,用于定义、使用和管理类型的系统。CTS确保所有编程语言在.NET框架中共享相同的数据类型,从而促进不同语言编写的应用程序和库之间的无缝通信 例如:C#中的int和VB.NET中的Integer在编译后都会统一为Int32
-
CLS (Common Language Specification)公共语言规范
CLS是一套规则,描述了支持.NET的编译器必须支持的最小的和完全的特征集,以生成可由CLR承载的代码
-
CLR (Common Language Runtime)公共语言运行时(也有的叫公共语言运行库)
主要作用是定位、加载和管理.NET类型、内存管理、安全检查、线程管理等。.NET运行库提供了一个定义明确的运行库层,可以被支持.NET的所有语言和平台共享
小知识参考自:
https://www.cnblogs.com/djh5520/p/14286801.html
https://blog.csdn.net/u010918911/article/details/130961425
反射的原理
反射是通过 System.Reflection 命名空间提供的类和接口实现的,它可以使我们在运行时获取程序集、模块、类型(类、接口、枚举等)的元数据,并能创建对象、调用方法、访问字段和属性等。 基于.NET的元数据和公共语言运行库(CLR)。当.NET程序编译时,所有的类型信息(包括类的定义、成员、继承信息等)都会被存储在可执行文件(如DLL或EXE)中的元数据部分。反射API能够读取这些元数据,因此可以动态地获取和使用类型信息。
反射的基本概念
反射主要包括以下几个核心概念:
- 类型(Type): 类型是反射的基础,代表了在程序集中定义的类、接口、结构体等。类型提供了关于其成员(字段、方法、属性等)的详细信息。
- 成员(Member): 成员是类型的组成部分,包括字段、属性、方法、事件等。
- 属性(Property): 属性是类的成员,具有名称和值,通常用于封装字段。
- 方法(Method): 方法是类的成员,定义了类的操作行为,包括函数和过程。
- 参数(Parameter): 方法中的参数是传递给方法的值,用于指定方法如何执行操作。
- Assembly(程序集): 程序集是编译后的代码库,它包含了类型和其他可重用类型定义。每个程序集都有一个唯一的标识符(Assembly Name)。
参考自:
https://blog.csdn.net/qq_35320456/article/details/135980899
https://www.cnblogs.com/wugh8726254/p/17434403.html
反射常用的API和方法
GetType类
- IsClass:类型判断是否为对象
- Type.GetType(string fullName): 获取指定完全限定名的类型。
- Type.GetType(string fullName, bool throwOnError): 获取指定完全限定名的类型,如果类型不存在,则根据布尔值决定是否抛出异常。
- Type.GetTypeFromProgID(string progID): 从ProgID获取类型。
- Type.GetTypeFromCLSID(Guid clsid): 从CLSID获取类型。
IsClass示例:
Type type = obj.GetType();
if(type.IsCalss)
{
....
}
Type.GetType(string fullName)使用示例:
在上方案例中我们通过此方法获取对象中public修饰的属性。
obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
Activator类
- GetTypes():获取程序集中的所有类型
- Assembly.LoadFile(string fileName): 从指定的文件加载程序集
- CreateInstance()创建由指定泛型类型参数设计的类型的实例
- Assembly.GetExecutingAssembly(): 获取当前执行的程序集。
CreateInstance()示例:
通过此方法创建由指定泛型类型参数设计的类型的实例,通常在我们编写SDK的时候用到,需要注意的是该方法时通过调用无参构造是现实的创建对象实例。
private T DoSend<T>(IBaseReq<T> request, Dictionary<string, string> Headers = null,string mediaType= "application/json") where T : BaseRes{var rspModel = Activator.CreateInstance<T>();
}
PropertyInfo类
- PropertyInfo.GetValue():获取属性的值。
- PropertyInfo.SetValue():设置属性的值。
- PropertyInfo.CanRead:检查属性是否可读。
- PropertyInfo.CanWrite:检查属性是否可写。
PropertyInfo.GetValue()示例:
在我们的案例中其实我们也用到了该类中的方法,就在这里
EventInfo 类
- EventInfo.AddEventHandler(object obj, Delegate handler): 订阅事件。
- EventInfo.RemoveEventHandler(object obj, Delegate handler): 取消订阅事件。
EventInfo.AddEventHandler(object obj, Delegate handler) 使用案例
using System;
using System.Reflection;public class MyClass
{public event EventHandler MyEvent;public void TriggerEvent(){MyEvent(this, EventArgs.Empty);}
}public class Program
{public static void Main(){MyClass myClassInstance = new MyClass();EventInfo eventInfo = typeof(MyClass).GetEvent("MyEvent");// 创建事件处理程序EventHandler eventHandler = (sender, e) => Console.WriteLine("Event triggered.");// 附加事件处理程序eventInfo.AddEventHandler(myClassInstance, eventHandler);// 触发事件myClassInstance.TriggerEvent();}
}
在这个例子中,我们定义了一个名为 MyClass 的类,它有一个名为 MyEvent 的事件。然后,在 Program 类的 Main 方法中,我们使用反射获取 MyEvent 的 EventInfo 对象,并使用 AddEventHandler 方法将事件处理程序附加到该事件上。当我们调用 myClassInstance.TriggerEvent() 方法触发事件时,附加的事件处理程序会被执行,在控制台上打印出 “Event triggered.”。
MemberInfo类
- MemberInfo.GetHashCode():获取成员的哈希码
- MemberInfo.Equals():比较两个成员是否相等
- MemberInfo.Name: 获取成员的名称
- MemberInfo.GetCustomAttributes(Type attributeType, bool inherit) / MemberInfo.GetCustomAttributes(): 获取成员的自定义属性。
MemberInfo.GetCustomAttributes使用案例
using System;
using System.Reflection;public class MyAttribute : Attribute
{public string Name { get; private set; }public MyAttribute(string name){this.Name = name;}
}public class MyClass
{[MyAttribute("Attribute1")]public void MyMethod() { }
}class Program
{static void Main(){MethodInfo methodInfo = typeof(MyClass).GetMethod("MyMethod");MyAttribute[] attributes = (MyAttribute[])methodInfo.GetCustomAttributes(typeof(MyAttribute), false);foreach (MyAttribute attribute in attributes){Console.WriteLine(attribute.Name);}}
}
在这个例子中,我们定义了一个名为 MyAttribute 的自定义属性,并将其应用于 MyClass 类的 MyMethod 方法上。然后,我们使用 GetMethod 获取 MethodInfo 对象,并使用 GetCustomAttributes 获取应用在该方法上的所有 MyAttribute 实例
MethodInfo类
- MethodInfo.MethodInfo 类用于操作方法。
- MethodInfo.Invoke():调用方法。
- MethodInfo.GetParameters():获取方法的参数。
- MethodInfo.IsStatic:检查方法是否为静态。
MethodInfo.Invoke() 使用案例:
using System;
using System.Reflection;public class Calculator
{public int Add(int a, int b){return a + b;}public int Subtract(int a, int b){return a - b;}
}class Program
{static void Main(string[] args){Calculator calculator = new Calculator();Type calculatorType = calculator.GetType();MethodInfo addMethod = calculatorType.GetMethod("Add");MethodInfo subtractMethod = calculatorType.GetMethod("Subtract");int resultAdd = (int)addMethod.Invoke(calculator, new object[] { 1, 2 });int resultSubtract = (int)subtractMethod.Invoke(calculator, new object[] { 3, 1 });Console.WriteLine($"Add result: {resultAdd}");Console.WriteLine($"Subtract result: {resultSubtract}");}
}
假设我们有一个名为Calculator的类,它有两个方法Add和Subtract,我们将使用反射来调用这些方法
反射的优缺点
优点
-
动态加载类型和程序集。
-
运行时检查对象的类型。
-
动态创建对象,调用方法,以及访问字段和属性。
-
动态构建 late-binding 方法调用。
缺点
-
性能问题:反射相当于多次间接访问,通常比直接调用慢。
-
安全问题:使用反射时可以执行任何 private 或 internal 成员,可能破坏封装性和安全性。
-
复杂的部署:应用程序依赖于程序集版本时,需要确保正确的程序集被加载。
-
代码可读性和维护性差:反射的代码通常难以阅读和理解。
反射的性能问题
想必大家经常会听到反射会影响性能,是的没错,它确实是会影响性能,主要原因是它要进行权限判断,拆箱操作,查找方法等一系列操作。但是只要合理的运用也能尽可能的减少对性能的影响,比如 缓存反射信息,减少反射调用次数等。
总结
其实有的时候我也在考虑有没有必要这样刨根问底,换个思考方式就像铁匠去研究锤子的铁是由什么元素组成的一样,其实他就是个打铁的根本没必要去做这些事只要打好自己的铁就可以了。但是古人又说了,不能知其然要知其所以然。奇奇怪怪。