【一文了解】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文件名(不含扩展名),而非文件路径。

cs 复制代码
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 会在当前程序集中查找。

cs 复制代码
Type type3 = Type.GetType("ReflectionTest.ReflectionTest+Person");

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

cs 复制代码
//1.加载外部程序集(指定路径)
Assembly assembly = Assembly.LoadFrom(@"C:\Path\MyAssembly.dll");

//2.通过类型全名从加载的程序集中获取类型
Type personType = assembly.GetType("ReflectionTest.ReflectionTest+Person");

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

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

cs 复制代码
//获取 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读写字段和属性:

cs 复制代码
//访问公共字段 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调用方法(包括公共、私有方法):

cs 复制代码
//调用公共方法 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程序集,访问其中的类型(常用于插件系统):

cs 复制代码
//加载外部程序集(如 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)示例

如获取私有、静态成员:

cs 复制代码
//获取私有字段(需指定 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实例化:

cs 复制代码
//定义泛型类
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)反射

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

cs 复制代码
//定义自定义特性
[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提升性能

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

相关推荐
小羊失眠啦.2 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
避避风港2 小时前
Java 抽象类
java·开发语言·python
cookies_s_s3 小时前
C++20 协程
linux·开发语言·c++
石油人单挑所有3 小时前
C语言知识体系梳理-第一篇
c语言·开发语言
HahaGiver6663 小时前
Unity Shader Graph 3D 实例 - 基础的模型贴图渲染
3d·unity·游戏程序·贴图·游戏美术
HahaGiver6663 小时前
Unity Shader Graph 3D 实例 - 一个简单的3D打印效果
3d·unity·游戏引擎
把csdn当日记本的菜鸡3 小时前
js查缺补漏
开发语言·javascript·ecmascript
lkbhua莱克瓦243 小时前
Java练习——数组练习
java·开发语言·笔记·github·学习方法
武子康3 小时前
Java-168 Neo4j CQL 实战:WHERE、DELETE/DETACH、SET、排序与分页
java·开发语言·数据库·python·sql·nosql·neo4j