【一文了解】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提升性能

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值