当前位置: 首页 > news >正文

【一文了解】C#反射

目录

程序集

反射

1.反射的定义与作用

1.1.反射的定义

1.2.反射的作用

2.反射的核心类

3.反射的基本用法

3.1.获取Type对象

3.2.动态创建对象(通过构造函数)

3.3.动态访问字段和属性

3.4.动态调用方法

3.5.加载外部程序集并反射

4.反射的高级特性

4.1.BindingFlags控制成员查找范围

1)BindingFlags的作用

2)BindingFlags最常用的枚举值

3)示例

4.2.泛型类型反射

4.3.特性(Attribute)反射

5.反射的优缺点

5.1.优点

5.2.缺点

6.常见应用场景

7.总结


       本篇文章分享一下C#反射。

程序集

       在学习反射前先了解一下程序集,程序集(Assembly)是.NET中代码的基本部署单元,一个程序集对应一个.dll或.exe文件,包含了类型、资源等元数据

       在Unity中,Assembly-CSharp是存放游戏运行时脚本的程序集,所有在Assets目录下(非Editor文件夹)的C#脚本都会被编译到这个程序集中;Asembly-CSharp-Editor是Unity中存放编辑器扩展脚本的程序集,所有位于Assets目录下任意层级的Editor文件夹内的脚本,都会被编译到这个程序集中。

反射

1.反射的定义与作用

1.1.反射的定义

       在C#中,反射(Reflection)是指程序在运行时动态获取类型信息(如类、方法、属性等)并操作其成员(如调用方法、访问属性)的能力。简单来说,反射允许程序“观察”和“修改”自身的结构,无需在编译时知道具体类型的细节。使用System.Reflection命名空间。

1.2.反射的作用

1)动态获取类型信息:运行时查看类的名称、继承关系、方法、属性、字段等元数据。

2)动态创建对象:无需在代码中显式声明类型,即可创建该类型的实例。

3)动态调用成员:调用类的方法、访问或修改属性/字段,即使编译时不知道这些成员的存在。

2.反射的核心类

类名

作用

常用方法/属性

Type

表示类型的元数据(反射的核心入口)

GetMethod()、GetProperty()、GetField()

Assembly

表示程序集(.dll 或 .exe)

LoadFrom()(加载程序集)、GetTypes()(获取所有类型)

MethodInfo

表示方法的元数据

Invoke()(调用方法)

PropertyInfo

表示属性的元数据

GetValue()、SetValue()(读写属性)

FieldInfo

表示字段的元数据

GetValue()、SetValue()(读写字段)

ConstructorInfo

表示构造函数的元数据

Invoke()(创建对象)

3.反射的基本用法

3.1.获取Type对象

       Type是反射的核心,任何类型(类、结构体、枚举等)都可以通过Type实例获取其元数据。获取Type的3种方式:

1)方式1:通过typeof(类型)获取

2)方式2:通过对象实例的GetType()方法获取

3)方式3:通过Type.GetType(string typeName)获取,动态加载未知类型,其中字符串typeName为“类型全名,程序集名称

●类型全名:包含命名空间,嵌套类型用+连接(如 Namespace.OuterClass+InnerClass)。

●程序集名称:通常是编译后的.dll或.exe文件名(不含扩展名),而非文件路径。

using System;
using System.Reflection;
using UnityEngine;namespace ReflectionTest
{public class ReflectionTest : MonoBehaviour{public class Person{public string Name { get; set; }public int age;public Person(string name, int age){Name = name;this.age = age;}public void SayHello(){Debug.Log($"Hello, I'm {Name}, {age} years old.");}}private void Start(){//方式1:通过 typeof(类型) 获取Type type1 = typeof(Person);Debug.Log(type1);//方式2:通过对象实例的 GetType() 方法获取Person person = new Person("Alice", 30);Type type2 = person.GetType();Debug.Log(type2);//方式3:通过 Type.GetType(string typeName) 获取,动态加载未知类型,其中字符串 typeName 为(类型全名,程序集名称)Type type3 = Type.GetType("ReflectionTest.ReflectionTest+Person,Assembly-CSharp");Debug.Log(type3);}}
}

       若目标类型与调用代码在同一程序集中,可省略程序集名称(仅用类型全名),此时 Type.GetType 会在当前程序集中查找。

Type type3 = Type.GetType("ReflectionTest.ReflectionTest+Person");

       若类型在外部程序集(未提前加载),需先加载程序集,再获取类型。

//1.加载外部程序集(指定路径)
Assembly assembly = Assembly.LoadFrom(@"C:\Path\MyAssembly.dll");//2.通过类型全名从加载的程序集中获取类型
Type personType = assembly.GetType("ReflectionTest.ReflectionTest+Person");

3.2.动态创建对象(通过构造函数)

       使用ConstructorInfo动态调用构造函数创建实例:

//获取 Person 的构造函数(参数为 string 和 int)
ConstructorInfo ctor = type1.GetConstructor(new Type[] { typeof(string), typeof(int) });//调用构造函数创建实例(参数对应构造函数的参数)
object personInstance = ctor.Invoke(new object[] { "Bob", 25 });//等价于 new Person("Bob", 25)

3.3.动态访问字段和属性

       使用FieldInfo和PropertyInfo读写字段和属性:

//访问公共字段 Age
FieldInfo ageField = type1.GetField("age");
int ageValue = (int)ageField.GetValue(personInstance);//获取值:25
Debug.Log($"Age:{ageValue}");
ageField.SetValue(personInstance, 26);//修改值:26
Debug.Log($"Age:{(int)ageField.GetValue(personInstance)}");//访问公共属性 Name
PropertyInfo nameProp = type1.GetProperty("Name");
string nameValue = (string)nameProp.GetValue(personInstance);//获取值:"Bob"
Debug.Log($"Name:{nameValue}");
nameProp.SetValue(personInstance, "Bobby");//修改值:"Bobby"
Debug.Log($"Name:{(string)nameProp.GetValue(personInstance)}");

3.4.动态调用方法

       使用MethodInfo调用方法(包括公共、私有方法):

//调用公共方法 SayHello()
MethodInfo sayHelloMethod = type1.GetMethod("SayHello");
sayHelloMethod.Invoke(personInstance, null);//输出:Hello, I'm Bobby, 26 years old.//调用私有方法 GetSecret()(需指定 BindingFlags)
MethodInfo getSecretMethod = type1.GetMethod("GetSecret", BindingFlags.Instance | BindingFlags.NonPublic);
string secret = (string)getSecretMethod.Invoke(personInstance, null);//获取私有方法返回值:"This is a secret."
Debug.Log($"GetSecret():{secret}");

       结果:

3.5.加载外部程序集并反射

       反射可加载外部.dll程序集,访问其中的类型(常用于插件系统):

//加载外部程序集(如 MyTest.dll)
Assembly assembly = Assembly.LoadFrom("MyTest.dll");//获取程序集中的所有类型
Type[] types = assembly.GetTypes();//遍历类型并使用
foreach (Type type in types)
{Debug.Log("类型名称:" + type.Name);//动态创建实例、调用方法等...
}

4.反射的高级特性

4.1.BindingFlags控制成员查找范围

1)BindingFlags的作用

       反射时,若不指定BindingFlags,默认只会查找公共实例成员(非静态、非私有)。通过 BindingFlags,可以:

●筛选成员的访问级别(公共、私有、保护、内部);

●筛选成员的作用域(实例成员、静态成员);

●控制查找范围(当前类型、继承链中的基类);

●控制匹配规则(是否忽略大小写等)。

2)BindingFlags最常用的枚举值

       BindingFlags是可组合的(通过位或|操作组合多个枚举值),以下是最常用的枚举值:

枚举值

说明

示例

Public

包含公共成员(默认包含)

查找类的公共方法GetPublicMethod()。

NonPublic

包含非公共成员(私有、保护、内部)

查找类的私有方法GetPrivateMethod()。

Instance

包含实例成员(非静态)

查找类的实例方法(需通过对象实例调用)。

Static

包含静态成员

查找类的静态方法static void StaticMethod()。

DeclaredOnly

仅查找当前类型声明的成员(不包含基类继承的成员)

仅查找DerivedClass自身声明的方法,不包含BaseClass中的方法。

FlattenHierarchy

查找继承链中的公共静态成员(与DeclaredOnly互斥)

查找DerivedClass及所有基类中的公共静态方法。

IgnoreCase

查找时忽略成员名称的大小写

同时匹配MyMethod和mymethod。

3)示例

       如获取私有、静态成员:

//获取私有字段(需指定 Instance + NonPublic)
FieldInfo privateField = type1.GetField("privateFieldName", BindingFlags.Instance | BindingFlags.NonPublic);//获取静态方法(需指定 Static + Public)
MethodInfo staticMethod = type1.GetMethod("StaticMethod", BindingFlags.Static | BindingFlags.Public);

4.2.泛型类型反射

       处理泛型类或方法时,需通过MakeGenericType或MakeGenericMethod实例化:

//定义泛型类
public class MyGeneric<T> 
{ public T Value { get; set; } 
}//获取泛型类型并实例化(如 MyGeneric<int>)
Type genericType = typeof(MyGeneric<>);
Type intGenericType = genericType.MakeGenericType(typeof(int));//实例化为 MyGeneric<int>
object genericInstance = Activator.CreateInstance(intGenericType);//创建实例

4.3.特性(Attribute)反射

       反射可读取类型或成员上的自定义特性:

//定义自定义特性
[AttributeUsage(AttributeTargets.Class)]
public class MyAttribute : Attribute 
{ public string Description { get; } public MyAttribute(string desc) { Description = desc; } 
}
//应用特性
[MyAttribute("这是一个测试类")]
public class TestClass { }//反射读取特性
Type testType = typeof(TestClass);
MyAttribute attr = (MyAttribute)Attribute.GetCustomAttribute(testType, typeof(MyAttribute));
Debug.Log(attr.Description);//输出:这是一个测试类

5.反射的优缺点

5.1.优点

1)灵活性高:可动态操作未知类型,适合插件系统、序列化(如JSON序列化)、依赖注入等场景。

2)元数据访问:能获取类型的详细信息(如注释、特性),用于文档生成、代码分析工具。

5.2.缺点

1)性能开销:反射操作比直接调用慢(约慢10-100倍),频繁调用可能影响性能。

2)安全性:反射可访问私有成员,破坏封装性;在部分受限环境(如沙箱)中可能被禁用。

3)代码可读性差:动态调用逻辑不如直接调用直观,调试难度较高。

6.常见应用场景

1)序列化/反序列化:如Newtonsoft.Json或System.Text.Json,通过反射遍历对象成员并转换为JSON。

2)依赖注入(DI)容器:如Autofac、Microsoft.Extensions.DependencyInjection,通过反射创建服务实例并注入依赖。

3)ORM 框架:如Entity Framework,通过反射将数据库表字段映射到类的属性。

4)插件系统:动态加载外部插件(.dll),反射调用插件中的方法。

5)单元测试框架:如xUnit、NUnit,通过反射发现并执行标记了Fact或Test的测试方法。

7.总结

       反射是C#中极强大的特性,允许程序在运行时“自省”和动态操作类型。尽管存在性能和封装性的权衡,但在灵活性优先的场景(如框架开发、动态功能)中不可或缺。实际使用时,应尽量减少频繁反射调用,必要时可通过缓存Type或MethodInfo提升性能

       好了,本次的分享到这里就结束啦,希望对你有所帮助~

http://www.dtcms.com/a/605336.html

相关文章:

  • 网站建设seo推广外贸网站建设海外推广
  • 网站ip域名查询安徽省住房城乡建设厅网站电工
  • 202511-Selenium技术深度解析:Web自动化测试的王者之路
  • Android 打开 在线 pdf 文件
  • Python 教程:如何快速在 PDF 中添加水印(文字、图片)
  • 普中51单片机学习笔记-矩阵按键
  • 视觉语言模型新突破!开源项目解读
  • 深圳南山区住房和建设局网站官网天天向上做图网站
  • 微算法科技(NASDAQ MLGO)通过容量证明(PoC)构建全球存储资源池,为Web3应用提供低成本、抗审查的数据存储服务
  • 08-微服务原理篇(Canal-Redis)
  • 填写网站备案信息深圳建设材料价格网站
  • 【Spring Boot 报错已解决】Spring Boot开发避坑指南:Hibernate实体类主键配置详解与异常修复
  • 【CSS】cursor: auto, default, none 有什么区别?
  • 网站备案负责人三网合一营销型全网站
  • 7.2 Dify核心功能与技术架构:前后端分离、API接口、数据存储
  • 观察Springboot AI-Function Tools 执行过程
  • 信贷风控建设的多维意义解析
  • 如何在产品已上线后发现需求遗漏进行补救
  • 重卡充电桩平台支持针对不同车队单独配置计费规则
  • 美丽寮步网站建设高性能广州公关公司有哪些
  • Linux告别搜索卡顿:解决“Argument list too long”与实现文件内容秒搜
  • .NET驾驭Excel之力:工作簿与工作表操作基础
  • 基于 C++ OpenCV 生成小视频
  • 个人网站审批网站防止采集
  • 5.6 Multiple region interfaces
  • 聊聊缓存测试用例设计方案
  • IU5516T低功耗,1M@2.0A降压稳压器
  • Arbess从初级到进阶(3) - 使用Arbess+GitLab+SonarQube搭建Java项目自动化部署
  • 外贸的网站有哪些网站开发心得体会
  • Spring Boot参数校验全流程解析