C# 反射(Reflection)

文章目录

  • 前言
  • 一、反射的优缺点
    • (一)优点
    • (二)缺点
  • 二、反射的用途
    • (一)查看特性(attribute)信息
    • (二)审查集合中的各种类型并实例化
    • (三)实现延迟绑定的方法和属性
    • (四)在运行时创建新类型并执行任务
  • 三、查看元数据实例解析
    • (一)通过 MemberInfo 查看自定义特性示例
    • (二)通过反射读取类及其成员的自定义特性示例


前言

  反射是 C# 中一种强大且独特的机制,它赋予程序访问、检测以及修改其本身状态或行为的能力。从程序的结构角度来看,程序集是一个包含了多个模块的集合,模块之中又包含了各式各样的类型,而类型进一步包含了诸多成员,如字段、属性、方法等。反射机制则通过提供一系列的对象,对程序集、模块以及类型进行了有效的封装,使得开发者可以在运行时深入探究程序的内部结构并进行相应操作。
在这里插入图片描述

一、反射的优缺点

(一)优点

  • 增强程序灵活性与扩展性: 在实际的软件开发中,需求常常会发生变化。通过反射,程序可以根据不同的运行时条件,动态地去适应这些变化,无需对代码进行大规模的重写。例如在设计一个支持多种数据库连接的应用程序时,利用反射可以根据配置文件中指定的数据库类型,动态加载相应的数据库驱动类并建立连接,轻松应对后续可能新增的数据库类型支持需求,大大提升了程序应对变化的能力。
  • 降低耦合性: 在面向对象编程中,耦合度过高会使得代码的维护和扩展变得困难。反射允许程序创建和控制任何类的对象,而无需提前在代码中硬编码目标类的相关信息。这样一来,不同模块之间的依赖关系可以变得更加松散,比如在一个游戏开发中,不同的游戏角色类可以通过反射机制在需要的时候被动态加载和使用,而不是在各个模块中都显式地引用它们,从而提高了整个系统的自适应能力。

(二)缺点

  • 性能问题: 反射操作本质上是一种解释性的操作,当使用它来访问字段或者调用方法时,其执行速度相较于直接编写代码进行相应操作要慢得多。这是因为反射需要在运行时去解析类型信息、查找成员等额外的开销。正因为如此,反射机制主要适用于那些对灵活性和扩展性要求极高的系统框架开发场景中,像一些普通的、对性能较为敏感的应用程序,一般不建议大量使用反射,以免影响整体的运行效率。
  • 逻辑模糊与维护困难: 程序员在阅读和理解源代码时,往往希望能够清晰地看到程序的逻辑走向。然而,反射机制绕过了常规源代码所体现的直接逻辑,使得代码的理解难度增加。反射代码本身相对来说也比直接编写的对应代码更加复杂,这在后续的维护过程中就容易造成困扰,比如在排查问题或者进行功能扩展时,需要花费更多的时间和精力去理清反射相关代码的逻辑。

二、反射的用途

(一)查看特性(attribute)信息

  在 C# 中,特性是一种可以为代码元素(如类、方法、属性等)添加额外元数据的方式。而反射允许我们在运行时查看这些特性信息,这对于很多基于元数据驱动的开发场景非常关键。例如在进行代码的自动化测试框架设计时,可以通过反射获取方法上标注的测试相关特性(如测试用例的优先级、预期结果等),进而根据这些信息来组织和执行测试用例。

(二)审查集合中的各种类型并实例化

  在处理一些包含多种类型对象的集合或者插件式架构时,反射可以帮助我们审查集合中存在的不同类型,然后根据需要实例化这些类型。比如在一个图形绘制系统中,有多种图形类(圆形、矩形、三角形等)都实现了一个统一的图形接口,通过反射可以扫描指定的程序集,找到所有实现该接口的类型,然后根据用户选择动态实例化相应的图形对象进行绘制。

(三)实现延迟绑定的方法和属性

  延迟绑定意味着在运行时才决定要调用哪个对象的哪个方法或者访问哪个属性。反射提供了这样的能力,使得程序可以根据实时的业务逻辑或者用户输入来动态决定操作的具体目标。例如在一个动态表单生成系统中,根据用户配置的表单字段类型和对应的操作逻辑,通过反射在运行时绑定到相应的数据处理方法和属性,实现灵活的数据交互。

(四)在运行时创建新类型并执行任务

  有时候,我们可能需要在程序运行过程中动态地创建一些新的类型,反射可以帮助我们达成这一目标。例如在代码生成工具或者动态代理类的创建场景中,利用反射能够根据特定的规则和模板在运行时生成新的类型,并且使用这些新创建的类型去完成诸如数据封装、接口代理等各种任务。

三、查看元数据实例解析

(一)通过 MemberInfo 查看自定义特性示例

  在 C# 中,要查看与类相关的特性信息,首先需要初始化 System.Reflection 类的 MemberInfo 对象。以下是一个简单的示例代码来说明这一过程:

using System;[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{public readonly string Url;public string Topic  // Topic 是一个命名(named)参数{get{return topic;}set{topic = value;}}public HelpAttribute(string url)  // url 是一个定位(positional)参数{this.Url = url;}private string topic;
}
[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}namespace AttributeAppl
{class Program{static void Main(string[] args){System.Reflection.MemberInfo info = typeof(MyClass);object[] attributes = info.GetCustomAttributes(true);for (int i = 0; i < attributes.Length; i++){System.Console.WriteLine(attributes[i]);}Console.ReadKey();}}
}

在上述代码中:
  首先定义了一个自定义特性 HelpAttribute,它有相应的属性(如 Url、Topic)以及构造函数。然后将这个特性应用到 MyClass 类上。在 Main 方法中,通过 typeof(MyClass) 获取 MyClass 对应的 MemberInfo 对象,接着调用 GetCustomAttributes(true) 方法获取该类上的所有自定义特性,并通过循环将它们打印出来。当代码被编译和执行时,就会显示附加到 MyClass 类上的自定义特性,这里会输出 HelpAttribute。

(二)通过反射读取类及其成员的自定义特性示例

  下面再来看一个更复杂一些的例子,展示如何使用反射来读取类及其成员中的元数据(以 Rectangle 类和 DeBugInfo 特性为例):

using System;
using System.Reflection;namespace BugFixApplication
{// 一个自定义特性 BugFix 被赋给类及其成员[AttributeUsage(AttributeTargets.Class |AttributeTargets.Constructor |AttributeTargets.Field |AttributeTargets.Method |AttributeTargets.Property,AllowMultiple = true)]public class DeBugInfo : System.Attribute{private int bugNo;private string developer;private string lastReview;public string message;public DeBugInfo(int bg, string dev, string d){this.bugNo = bg;this.developer = dev;this.lastReview = d;}public int BugNo{get{return bugNo;}}public string Developer{get{return developer;}}public string LastReview{get{return lastReview;}}public string Message{get{return message;}set{message = value;}}}[DeBugInfo(45, "Zara Ali", "12/8/2012",Message = "Return type mismatch")][DeBugInfo(49, "Nuha Ali", "10/10/2012",Message = "Unused variable")]class Rectangle{// 成员变量protected double length;protected double width;public Rectangle(double l, double w){length = l;width = w;}[DeBugInfo(55, "Zara Ali", "19/10/2012",Message = "Return type mismatch")]public double GetArea(){return length * width;}[DeBugInfo(56, "Zara Ali", "19/10/2012")]public void Display(){Console.WriteLine("Length: {0}", length);Console.WriteLine("Width: {0}", width);Console.WriteLine("Area: {0}", GetArea());}}//end class Rectangle  class ExecuteRectangle{static void Main(string[] args){Rectangle r = new Rectangle(4.5, 7.5);r.Display();Type type = typeof(Rectangle);// 遍历 Rectangle 类的特性foreach (Object attributes in type.GetCustomAttributes(false)){DeBugInfo dbi = (DeBugInfo)attributes;if (null!= dbi){Console.WriteLine("Bug no: {0}", dbi.BugNo);Console.WriteLine("Developer: {0}", dbi.Developer);Console.WriteLine("Last Reviewed: {0}",dbi.LastReview);Console.WriteLine("Remarks: {0}", dbi.Message);}}// 遍历方法特性foreach (MethodInfo m in type.GetMethods()){foreach (Attribute a in m.GetCustomAttributes(true)){DeBugInfo dbi = (DeBugInfo)a;if (null!= dbi){Console.WriteLine("Bug no: {0}, for Method: {1}",dbi.BugNo, m.Name);Console.WriteLine("Developer: {0}", dbi.Developer);Console.WriteLine("Last Reviewed: {0}",dbi.LastReview);Console.WriteLine("Remarks: {0}", dbi.Message);}}}Console.ReadLine();}}
}

在这个示例中:
  首先定义了 DeBugInfo 特性,它用于记录调试相关的信息,如错误编号、开发者、最后审查时间以及备注信息等,并且通过 AttributeUsage 特性指定了它可以应用的目标元素范围以及允许重复应用。
  接着在 Rectangle 类及其部分方法上应用了 DeBugInfo 特性,用于标记不同位置的调试相关元数据。
在 Main 方法中:
  先创建了 Rectangle 类的实例并调用 Display 方法展示基本的矩形信息。
  然后通过 typeof(Rectangle) 获取 Rectangle 类对应的 Type 对象,利用这个对象先遍历类级别的自定义特性,将每个 DeBugInfo 特性中的相关信息打印出来。
  之后再通过 GetMethods 方法获取 Rectangle 类的所有方法,进一步遍历每个方法上的自定义特性,同样将其中的关键调试信息打印输出。
  当代码被编译和执行后,就会按照顺序输出矩形的基本信息以及类和方法上标注的 DeBugInfo 特性所包含的详细调试信息,清晰地展示了通过反射获取类及其成员元数据的完整过程。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35033.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Jdk配置、maven配置、gradle配置、Java开发常用的依赖库

一、配置JDK环境变量&#xff1a; 1. 安装 JDK 首先确保已经安装了 JDK。如果还没有安装&#xff0c;可以从 Oracle 官网 或者 AdoptOpenJDK 下载并安装合适的 JDK 版本。 我建议下载这个&#xff1a;https://adoptium.net/zh-CN/temurin/releases/?oswindows&archx64&…

【AI系统】FBNet 系列

FBNet 系列 本文主要介绍 FBNet 系列&#xff0c;在这一章会给大家带来三种版本的 FBNet 网络&#xff0c;从基本 NAS 搜索方法开始&#xff0c;到 v3 版本的独特方法。在本节中读者会了解到如何用 NAS 搜索出最好的网络和训练参数。 FBNet V1 模型 FBNetV1:完全基于 NAS 搜…

二十三、Linux可视管理之webmin和bt运维工具

1、webmin 基本介绍 Webmin 是功能强大的基于 Web 的 Unix/linux 系统管理工具。管理员通过浏览器访问 Webmin 的各种管理功能并完成相应的管理操作。除了各版本的 linux 以外还可用于&#xff1a;AIX、HPUX、Solaris、Unixware、Irix 和 FreeBSD 等系统安装webmin&配置 …

聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程

聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程 系统采用FastAdmin框架独立全新开发&#xff0c;安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一些JD&#xff0c;TB等业务定制&#xff0c;子账号业务逻辑API 非常详细&#xff0c;方便内置…

Transformer真的是机器人技术的基础吗?

生成式预训练Transformer&#xff08;GPT&#xff09;被吹捧为将彻底改变机器人技术。但实际应用中&#xff0c;GPT需要庞大且昂贵的计算资源、冗长的训练时间以及&#xff08;通常&#xff09;非机载无线控制&#xff0c;诸多限制之下&#xff0c;GPT技术真的实用吗&#xff1…

使用脚本语言实现Lumerical官方案例——闪耀光栅(Blazed grating)(纯代码)(2)

接《使用脚本语言实现Lumerical官方案例——闪耀光栅(Blazed grating)(纯代码)(1)》 一、添加分析组 1.1 代码实现 #添加分析组 addanalysisgroup(); set("name", "grating_R"); set("x", 0); set("y", 2.5*um); addanalysisgrou…

【Java】异常处理见解,了解,进阶到熟练掌握

各位看官早安午安晚安呀 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 大家好我们今天来学习Java面向对象的的抽象类和接口&#xff0c;我们大家庭已经来啦~ 目录 1.(throws和throw&#xff09;我们不管这个异常&…

Oracle数据库 用户管理模式下的冷备份与热备份

1. 用户管理模式下的冷备份 1.1. 通过数据库相关视图查询 查实例 select instance_name,version,status,archiver,database_status from v$instance; 查数据库 select dbid,name,log_mode from v$database; 查数据文件状态 select file_name,tablespace_name,status,o…

Solon 3.0.4 发布(Spring 的替代方案,备胎计划)

Solon 框架&#xff01; 面向全场景的 Java 应用开发框架。从零开始构建&#xff08;非 java-ee 架构&#xff09;&#xff0c;有灵活的接口规范与开放生态。新一代 Java 应用软件的生态基座。开放原子开源基金会&#xff0c;孵化项目。 追求&#xff1a; 更快、更小、更简单…

严格单元测试造就安全软件

在信息技术迅速发展的今天&#xff0c;软件在各个行业中扮演着至关重要的角色&#xff0c;尤其是在汽车行业&#xff0c;其中软件的可靠性和安全性直接影响到人们的生命安全。软件缺陷所带来的潜在风险不容小觑&#xff0c;尤其在涉及到自动驾驶和车辆控制等关键系统时&#xf…

基于CALMET诊断模型的高时空分辨率精细化风场模拟技术应用

在研究流场时&#xff0c;常用观测、模型风洞测试和数值模拟方法进行研究。但时常遇到研究区气象站点分布稀疏&#xff0c;不能代表周边复杂地形的风场。风洞模拟需要对地形进行实景的微缩&#xff0c;但实际过程中可能更关心近地表边界层的风场。风洞模拟一方面费用较高&#…

手写签名OCR识别-提升文档处理效率

随着数字化办公和智能化管理的普及&#xff0c;企业在日常业务中处理的各类单据和文件数量与日俱增。这些文件不仅包括了繁琐的文字信息&#xff0c;还涉及到重要的签名和印章等手写元素。尤其是在合同、协议、发票、审批单等文档中&#xff0c;手写签名不仅是身份确认的重要标…

Flutter:webview打开网页

官方文档地址 下方代码来自官方文档&#xff0c;先简单记录下如何通过webview打开某个http地址 添加权限&#xff1a;android/app/src/main/AndroidManifest.xml <uses-permission android:name"android.permission.INTERNET"/>pubspec.yaml webview_flutter: …

WPF+LibVLC开发播放器-音量控制和倍速控制

界面 界面上增加音量的控件和倍速控制控件 音量控制 主要也是一个Slider进度条控件来实现音量调节 我们这里设置默认的最大值为100&#xff0c;默认Value值也为100&#xff0c;默认声音开到最大 这里目前完全由前端控制音量调节&#xff0c;可以直接使用ValueChanged事件实…

重生之我在异世界学编程之C语言:初识一维和二维数组篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文数组的基本类型&#xff1a;一维…

Ubuntu 新加硬盤分區

1. 查看系統是否識別新的硬盤 lsblk 假設硬盤的名字為&#xff1a;sudo fdisk /dev/sda 注意硬盤類型為gpt&#xff0c;大小沒有限制 2. 硬盤分區 sudo fdisk /dev/sda 根據提示&#xff1a; n 新加分區&#xff0c;設置分區大小。 p 查看分區情況以及大小 d: 刪除也…

openGauss开源数据库实战十六

文章目录 任务十六 openGauss逻辑结构:触发器管理任务目标实施步骤一、测试openGauss的触发器1.创建测试表2.创建触发器对应的函数3.创建触发器4.测试触发器 二、触发器的类型1.行级触发器2.语句级触发器3.AFTER触发器和 BEFORE触发器 任务十六 openGauss逻辑结构:触发器管理 …

键值对形式读取值

效果 学习啦&#xff01; 异步请求数据方法&#xff1a; ①async/await <script setup> import { nextTick, onMounted } from vuelet Info {}const loadData async () > {try {const response await fetch(/json/info.json)const data await response.json()data…

Java面向对像编程OOP

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 OOP总结&#xff1a; 类和对象的说明Java中有无static修饰的方法的使用区别&#xff1a;✅️有stat…

【MARL】MAT论文阅读笔记

文章目录 前言一、如何产生这个想法(TRPO -> ) PPO -> MAPPO -> HAPPO -> MAT 二、多智能体优势值分解定理三、transformer 在MAT的应用四、伪代码简述五、实验效果 前言 正好有节课让我们调研最新的自己的方向的新论文&#xff0c;找到一篇自己觉得比较可行&…