文章目录
- 前言
- 一、前置知识
- 1、编译器
- 2、程序集(`Assembly`)
- 3、元数据(`Metadata`)
- 二、反射
- 1、反射的概念
- 2、反射的作用
- 3、反射的核心`Type` 类
- 3.1 `Type` 类介绍
- 3.2 不同方法获取 `Type`
- 3.3 获取type类型所在的程序集的相关信息
- 4、反射的常见用法
- 4.1 获取 `Type`类型信息
- 4.2 获取类的公共成员
- 4.3 获取类的构造函数
- 4.4 获取类的字段
- 4.5 获取类的成员方法
- 4.6 其他反射操作
- 5、反射访问并操作私有(private)成员(包括字段、属性、方法等)
- 6、总结
- 三、关键类Assembly和Activator
- 1、**反射中的关键类**
- 1.1 `Type` 类(前面已经介绍过了)
- 1.2 `Assembly` 类
- 1.3 `Activator` 类
- 2、**使用 `Activator` 类实例化对象**
- 示例 1:无参构造函数
- 示例 2:带参数的构造函数
- 3、**使用 `Assembly` 类加载程序集**
- 3.1 加载程序集:
- (1)获取当前执行的程序集:
- (2)从指定路径加载程序集:
- 3.2 获取指定名称的类型
- 3.3 获取所有类型:
- 3.4 获取并调用类型中的方法:
- 4、综合示例
- 四、**总结**
- 专栏推荐
- 完结
前言
C# 反射 (Reflection) 是一种强大的机制,它允许程序在运行时动态地查看和操作程序的元数据。通过反射,我们可以在不事先知道类型信息的情况下,获取类型信息并操作类型中的成员(如类、方法、属性、字段等)。
一、前置知识
1、编译器
编译器是将源语言程序(如 C#、Java、C
等)翻译为目标语言程序(如机器代码或伪机器代码)的工具。源代码经过编译器的处理后,可以生成可执行文件
(.exe
) 或库文件
(.dll
) ,供程序运行时使用。
2、程序集(Assembly
)
程序集是经过编译器编译得到的中间产物,可以是一个库文件 (.dll
) 或可执行文件 (.exe
)。它是 .NET 程序中的代码集合,包含了类型、方法、属性等元数据,是程序执行的基础。
程序集就是我们写的一个代码集合,我们现在写的所有代码,最终都会被编译器翻译为一个程序集供别人使用。比如一个代码库文件(dll)或者一个可执行文件(exe)
我们新建一个C#项目,运行一次后,系统就会自动在bin目录里生成对应的程序集文件
3、元数据(Metadata
)
在C#中,元数据(Metadata)指的是描述程序及其组成部分(如类、方法、属性、字段等)的信息。它是关于代码的“数据”,但并不是实际执行的代码。可以将元数据视为程序代码的“数据字典”,它提供了有关类型、成员和引用等的信息,使得程序在运行时可以进行动态操作。
元数据是随代码一起编译成程序集(Assembly)的一部分,并且可以在运行时通过反射机制进行访问。
元数据是指有关程序结构和组成部分的描述信息,它不仅有助于程序的运行时类型检查,还为反射、动态加载等功能提供了支持。在C#中,元数据通常存储在程序集
文件中,并可以通过反射机制访问和操作。
二、反射
1、反射的概念
反射允许程序在运行时查看其他程序集
或自身的元数据
,动态获取类型信息并对其进行操作。例如,通过反射可以查看类的构造函数、方法、字段、属性等,甚至可以在运行时动态创建对象并调用方法。
2、反射的作用
- 程序运行时获取类型信息:反射可以帮助我们动态获取类、方法、字段等的元数据信息。
- 动态操作对象:反射使得我们可以动态实例化对象,调用方法,修改字段值等。
- 增强灵活性:反射使得代码更加灵活,尤其在插件机制、依赖注入等场景下尤为重要。
3、反射的核心Type
类
3.1 Type
类介绍
Type
类是反射的基础,它提供了关于类的所有信息,例如类的名称、构造函数、方法、字段、属性等。它是访问元数据的主要方式。
3.2 不同方法获取 Type
-
通过对象的
GetType()
方法获取:int a = 42; Type type = a.GetType(); Console.WriteLine(type); // 输出: System.Int32
-
通过
typeof
关键字获取:Type type = typeof(int); Console.WriteLine(type); // 输出: System.Int32
-
通过类的全名获取:
Type type = Type.GetType("System.Int32"); Console.WriteLine(type); // 输出: System.Int32
其实就是
命名空间.类名
3.3 获取type类型所在的程序集的相关信息
Console.WriteLine(type.Assembly);
打印
System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
- System.Private.CoreLib 是程序集的名称。(通常是 .exe 或 .dll 文件的名称)
- Version=9.0.0.0 是程序集的版本。
- Culture=neutral 表示它是通用区域设置的。(如果是通用语言程序集,则为 neutral)。
- PublicKeyToken=7cec85d7bea7798e 是公共密钥标识符。(用于区分同名的不同程序集版本)。
4、反射的常见用法
为了方便测试反射的常见用法,这里我先新增一个简单的类,里面包含基本的构造函数、方法、字段、属性。
ps
:这里只是测试探究,实际开发肯定不需要通过反射来操作自己的类。
class Test
{
private int i = 1;
public int j = 2;
public int k { get; set; }
public string str = "向宇的客栈";
public Test() { }
public Test(int i)
{
this.i = i;
}
public Test(int i, string str) : this(i)
{
this.str = str;
}
public void Log()
{
Console.WriteLine("学习C#");
}
}
4.1 获取 Type
类型信息
Type t = typeof(Test);// 获取类型信息
4.2 获取类的公共成员
使用 GetMembers()
方法获取类中的所有公共成员:
MemberInfo[] infos = t.GetMembers();
foreach (var info in infos)
{
Console.WriteLine(info);
}
打印结果
可以看到,除了万物之父(object
)中的方法,打印出来的都是我们前面Test类中定义的公共成员信息(构造函数、方法、字段、属性)。
4.3 获取类的构造函数
- 获取所有构造函数:
ConstructorInfo[] ctors = t.GetConstructors();
foreach (var ctor in ctors)
{
Console.WriteLine(ctor);
}
结果
- 获取无参构造函数并调用:
ConstructorInfo ctor = t.GetConstructor(Type.EmptyTypes);
Test obj = (Test)ctor.Invoke(null);
Console.WriteLine(obj);
- 获取有参构造函数并调用:
ConstructorInfo ctor = t.GetConstructor(new Type[] { typeof(int), typeof(string) });
Test obj = (Test)ctor.Invoke(new object[] { 10, "Hello" });
Console.WriteLine(obj);
4.4 获取类的字段
- 获取所有公共字段:
FieldInfo[] fieldInfos = t.GetFields();
foreach (var field in fieldInfos)
{
Console.WriteLine(field);
}
结果
- 获取指定字段:
FieldInfo fieldInfo = t.GetField("j");
Console.WriteLine(fieldInfo);
结果
- 使用反射获取和设置字段的值:
FieldInfo fieldInfo = t.GetField("j");
Test test = new Test();
test.j = 99;
fieldInfo.SetValue(test, 100);
Console.WriteLine(fieldInfo.GetValue(test)); // 输出: 100
4.5 获取类的成员方法
- 获取所有公共方法(记得引入
using System.Reflection;
反射命名空间):
using System.Reflection;
MethodInfo[] methods = t.GetMethods();
foreach (var method in methods)
{
Console.WriteLine(method);
}
- 获取指定方法并调用:
MethodInfo method = t.GetMethod("ToString");
object result = method.Invoke(test, null);
Console.WriteLine(result);
- 对于方法重载,需要提供方法的参数类型:
MethodInfo method = t.GetMethod("Substring", new Type[] { typeof(int), typeof(int) });
object result = method.Invoke("Hello, World!", new object[] { 7, 5 });
Console.WriteLine(result); // 输出: World
4.6 其他反射操作
-
枚举:
获取枚举类型的名称:Enum.GetEnumName(typeof(DayOfWeek), 1); // 输出: Monday
-
事件:
获取类的事件:EventInfo eventInfo = t.GetEvent("MyEvent"); Console.WriteLine(eventInfo);
-
接口:
获取类实现的接口:Type[] interfaces = t.GetInterfaces(); foreach (var iface in interfaces) { Console.WriteLine(iface); }
-
属性:
获取类的属性:PropertyInfo[] properties = t.GetProperties(); foreach (var property in properties) { Console.WriteLine(property); }
5、反射访问并操作私有(private)成员(包括字段、属性、方法等)
在默认情况下,反射是可以访问私有成员的,但你必须明确告诉 .NET 你要访问它们。具体来说,你需要使用 BindingFlags 来指定访问私有成员。
例如,默认情况下,反射会忽略私有字段和方法,但你可以通过显式地指定 BindingFlags.NonPublic
和 BindingFlags.Instance
来让反射能够访问这些私有成员。
示例
// 创建 MyClass 的实例
MyClass myObject = new MyClass();
// 获取 MyClass 类型的信息
Type type = typeof(MyClass);
// 使用反射访问私有字段 secretValue
FieldInfo fieldInfo = type.GetField("secretValue", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
// 获取字段的值
var value = fieldInfo.GetValue(myObject);
Console.WriteLine("Private Field 'secretValue' Value: " + value);
}
// 使用反射访问私有方法 PrintSecret
MethodInfo methodInfo = type.GetMethod("PrintSecret", BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo != null)
{
// 调用私有方法
methodInfo.Invoke(myObject, null);
}
这种机制虽然强大,但应谨慎使用,避免破坏封装性,尤其是在生产环境中。这种做法通常用于调试、测试、动态代理等特殊场景。
6、总结
C# 中的反射为程序提供了强大的动态能力,通过 Type
类及其提供的方法,我们可以在运行时获取类型的元数据,动态创建对象,调用方法,甚至修改字段值。
虽然反射提供了灵活性,但是反射的性能较差
,因为它是动态查找和操作的。如果频繁使用反射,可能会影响程序性能,尤其是在处理大量数据时。因此应该在合适的场合使用反射,并考虑到性能和可维护性。
三、关键类Assembly和Activator
反射(Reflection)是 C# 中一个非常强大的机制,它允许在运行时访问和操作程序中的类型信息。通过反射,程序可以在运行时动态地获取类、方法、属性等信息,并实例化对象、调用方法、访问字段等。下面是对 Assembly
和 Activator
这两个关键类的详细介绍和相关用法。
1、反射中的关键类
1.1 Type
类(前面已经介绍过了)
Type
类提供了对类型信息的访问,它是所有类型的元数据的基础。通过 Type
对象,你可以获取类的构造函数、属性、字段、方法等信息。
1.2 Assembly
类
Assembly
类用于加载和操作程序集。程序集包含了类、接口、枚举等信息,反射可以用来加载程序集,并且在程序运行时查找类型和操作它们。
1.3 Activator
类
Activator 类位于 System 命名空间下,提供了一组静态方法来创建类型的实例。它常用于在运行时通过反射动态创建对象,特别是在我们事先不知道具体类型的情况下。例如,依赖注入、插件加载、对象池等场景中,可以使用 Activator 来动态实例化对象。
2、使用 Activator
类实例化对象
Activator
提供了几种创建对象的方法,最常用的是 CreateInstance
,它可以用来根据 Type
对象动态创建实例。
示例 1:无参构造函数
假设有一个类 Test
,它有一个无参构造函数。你可以使用 Activator.CreateInstance
来创建该类的实例。
using System;
public class Test
{
public string str = "Hello, World!";
}
class Program
{
static void Main()
{
// 获取 Test 类的 Type 对象
Type testType = typeof(Test);
// 使用 Activator 创建对象实例
Test testobj = Activator.CreateInstance(testType) as Test;
// 输出结果
Console.WriteLine(testobj.str); // Output: Hello, World!
}
}
示例 2:带参数的构造函数
如果类有带参数的构造函数,可以使用 Activator.CreateInstance
方法传递参数来创建实例。
using System;
public class Test
{
public int j;
public string str;
// 构造函数
public Test(int j, string str)
{
this.j = j;
this.str = str;
}
}
class Program
{
static void Main()
{
Type testType = typeof(Test);
// 使用带参数的构造函数
Test testobj1 = Activator.CreateInstance(testType, 99, "Hello") as Test;
Console.WriteLine(testobj1.j); // Output: 99
Console.WriteLine(testobj1.str); // Output: Hello
Test testobj2 = Activator.CreateInstance(testType, 55, "World") as Test;
Console.WriteLine(testobj2.j); // Output: 55
Console.WriteLine(testobj2.str); // Output: World
}
}
3、使用 Assembly
类加载程序集
前面我们一直都是对当前程序的操作,实际开发肯定是操作外部程序集。Assembly
类使得我们可以在运行时加载、查询、获取外部程序集的类型信息等。
常用方法:
GetExecutingAssembly()
:获取当前执行的程序集。LoadFrom(string path)
:从指定路径加载程序集。GetTypes()
:获取程序集中的所有类型。GetType(string typeName)
:获取指定名称的类型。GetManifestResourceNames()
:获取程序集中的所有嵌入式资源名称。
3.1 加载程序集:
(1)获取当前执行的程序集:
Assembly assembly = Assembly.GetExecutingAssembly();
Console.WriteLine("当前程序集: " + assembly.FullName);
(2)从指定路径加载程序集:
Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Console.WriteLine("加载程序集: " + assembly.FullName);
3.2 获取指定名称的类型
Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type type = assembly.GetType("Namespace.ClassName"); // 获取程序集中的某个类型
Console.WriteLine("类型名称: " + type.FullName);
3.3 获取所有类型:
Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
Console.WriteLine(t.FullName); // 输出程序集中的所有类型
}
3.4 获取并调用类型中的方法:
其实就是结合前面Type知识配合使用
Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type type = assembly.GetType("Namespace.ClassName");
MethodInfo methodInfo = type.GetMethod("MethodName");
object result = methodInfo.Invoke(null, null); // 调用静态方法
Console.WriteLine(result);
4、综合示例
我这里项目的程序集文件路径为:C:\Users\zw\Desktop\C#\bin\Debug\net9.0\C#.dll
程序集文件里主要的代码是,我自定义了一个Test类
代码
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 加载程序集(因为是反斜杠,所以加个@不希望它转义)
Assembly assembly = Assembly.LoadFrom(@"C:\Users\zw\Desktop\C#\bin\Debug\net9.0\C#.dll");
// 获取程序集中的所有类型
Type[] types = assembly.GetTypes();
foreach (var type in types)
{
Console.WriteLine(type.Name);
}
// 获取程序集中的一个具体类型
Type testType = assembly.GetType("Test");
// 使用反射实例化对象
object myClassObj = Activator.CreateInstance(testType);
// 获取并调用方法
MethodInfo method = testType.GetMethod("Log");
method.Invoke(myClassObj, null);
// 获取指定字段并打印值
FieldInfo field1 = testType.GetField("str");
Console.WriteLine(field1.GetValue(myClassObj));
// 获取指定字段,先修改值,在打印值
FieldInfo field2 = testType.GetField("j");
field2.SetValue(myClassObj, 100);
Console.WriteLine(field2.GetValue(myClassObj));
}
}
结果,程序集里的方法被执行,字段也被打印了(Program
类和E_Days
枚举是定义的其他两个测试数据,不用管即可)
四、总结
Type
:提供关于类、接口、枚举等类型的信息。Assembly
:用于加载程序集,可以获取程序集中的所有类型和类型成员信息。Activator
:用于在运行时动态创建对象实例,支持无参数和带参数构造函数。- 反射虽然非常强大,但会影响性能,因此在需要时使用,避免过度依赖。
专栏推荐
地址 |
---|
【从零开始入门unity游戏开发之——C#篇】 |
【从零开始入门unity游戏开发之——unity篇】 |
【制作100个Unity游戏】 |
【推荐100个unity插件】 |
【实现100个unity特效】 |
【unity框架开发】 |
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~