C#反射的应用案例与讲解

ops/2025/1/11 16:57:27/

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 成员,可能破坏封装性和安全性。

  • 复杂的部署:应用程序依赖于程序集版本时,需要确保正确的程序集被加载。

  • 代码可读性和维护性差:反射的代码通常难以阅读和理解。

反射的性能问题

想必大家经常会听到反射会影响性能,是的没错,它确实是会影响性能,主要原因是它要进行权限判断,拆箱操作,查找方法等一系列操作。但是只要合理的运用也能尽可能的减少对性能的影响,比如 缓存反射信息,减少反射调用次数等。

总结

其实有的时候我也在考虑有没有必要这样刨根问底,换个思考方式就像铁匠去研究锤子的铁是由什么元素组成的一样,其实他就是个打铁的根本没必要去做这些事只要打好自己的铁就可以了。但是古人又说了,不能知其然要知其所以然。奇奇怪怪。


http://www.ppmy.cn/ops/149202.html

相关文章

Vue 开发者的 React 实战指南:状态管理篇

对于 Vue 开发者来说&#xff0c;React 的状态管理可能是最需要转变思维方式的部分之一。本文将从 Vue 开发者熟悉的角度出发&#xff0c;详细介绍 React 的状态管理方案&#xff0c;并通过实战示例帮助你快速掌握。 本地状态管理对比 Vue 的响应式系统 在 Vue 中&#xff0…

如何在 Ubuntu 22.04 上安装 Nagios 服务器教程

简介 在本教程中&#xff0c;我们将解释如何在 Ubuntu 22.04 上安装和配置 Nagios&#xff0c;使用 Apache 作为 Web 服务器&#xff0c;并通过 Let’s Encrypt Certbot 使用 SSL 证书进行保护。 Nagios 是一个强大的监控系统&#xff0c;它可以帮助组织在 IT 基础设施问题影…

拥有23种PDF/图片转换 数据提取 - 免费在线工具

All ComPDFKit Online PDF Tools | ComPDFKit 1. 数据提取 • 提取全部: 从PDF和图片中提取所有文本、表格和图片&#xff0c;并保存为JSON格式。 • 仅提取文本: 仅从PDF和图片中提取所有文本&#xff0c;并保存为TXT和JSON格式。 • 仅提取表格: 仅从PDF和图片中提取表格&am…

el-table拖拽表格

1、拖拽插件安装 npm i -S vuedraggable // vuedraggable依赖Sortable.js&#xff0c;我们可以直接引入Sortable使用Sortable的特性。 // vuedraggable是Sortable的一种加强&#xff0c;实现组件化的思想&#xff0c;可以结合Vue&#xff0c;使用起来更方便。 2、引入拖拽函数…

Java线程安全

1. Java的线程安全 Java线程安全&#xff1a;狭义地认为是多线程之间共享数据的访问。Java语言中各种操作共享的数据有5种类型&#xff1a;不可变、绝对线程安全、相对线程安全、线程兼容、线程独立 ① 不可变 不可变&#xff08;Immutable&#xff09; 的对象一定是线程安全…

基于YOLO11的无人机视角下羊群检测系统

基于YOLO11的无人机视角下羊群检测系统 (价格90) 包含 [sheep] 【羊】 1个类 通过PYQT构建UI界面&#xff0c;包含图片检测&#xff0c;视频检测&#xff0c;摄像头实时检测。 &#xff08;该系统可以根据数据训练出的yolo11的权重文件&#xff0c;运用在其他检测系统上…

rom定制系列------小米max3安卓12 miui14批量线刷 默认开启usb功能选项 插电自启等

小米Max3是小米公司于2018年7月19日发布的机型。此机型后在没有max新型号。采用全金属一体机身设计&#xff0c;配备6.9英寸全面屏.八核处理器骁龙636&#xff0c;后置双摄像头1200万500万像素&#xff0c;前置800万像素.机型代码 &#xff1a;nitrogen.官方最终版为稳定版12.5…

入门网络安全工程师要学习哪些内容【2025年寒假最新学习计划】

&#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 大家都知道网络安全行业很火&#xff0c;这个行业因为国家政策趋势正在大力发展&#xff0c;大有可为!但很多人对网络安全工程师还是不了解&#xff0c;不知道网…