C# 进阶 -反射(Reflection)
C# 反射概述
反射(Reflection) 是 .NET 框架提供的一个强大功能,允许程序在运行时检查和操作类型的信息。通过反射,可以动态地创建对象、调用方法、访问属性和字段等。
反射提供了封装程序集、模块和类型的对象(Type类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了特性,可以利用反射对它们进行访问。
exe和dll的区别
在 Windows 操作系统中,EXE 和 DLL 是两种常见的可执行文件格式,它们各自有不同的用途和特点。
1. 定义
- EXE (Executable): 可执行文件,用于启动独立的应用程序。当用户双击一个 EXE 文件时,操作系统会加载并运行该应用程序。
- DLL (Dynamic Link Library): 动态链接库,包含可以被多个程序共享的代码和数据。DLL 文件不能直接运行,而是被其他程序在运行时加载和使用。
2. 用途
- EXE:
- 用于创建独立的应用程序。
- 包含应用程序的入口点(即
Main方法)。 - 通常包含用户界面和业务逻辑。
- DLL:
- 用于封装可重用的代码和资源。
- 可以被多个应用程序共享,减少内存占用和磁盘空间。
- 常用于实现模块化设计,便于维护和更新。
3. 结构
- EXE:
- 包含一个或多个入口点(如
Main方法)。 - 可以包含资源文件(如图标、位图、字符串表等)。
- DLL:
- 不包含入口点。
- 可以包含类、方法、属性、字段等。
- 可以导出函数和类,供其他程序调用。
4. 加载方式
- EXE:
- 由操作系统直接加载并运行。
- 通常在进程的地址空间中独立运行。
- DLL:
- 由其他程序在运行时通过
LoadLibrary或DllImport等机制加载。 - 可以在多个进程中共享同一个 DLL 实例。
- 性能和资源管理
- EXE:
- 每个 EXE 文件都是一个独立的进程,占用独立的内存空间。
- 启动时间可能较长,因为需要加载整个应用程序。
- DLL:
- 可以在多个进程中共享,节省内存。
- 加载速度快,因为只需要加载一次即可被多个程序使用。
C# metadata元数据
在 C# 和 .NET 框架中,元数据是指存储在程序集(如 EXE 或 DLL 文件)中的附加信息。这些信息描述了程序集中的类型、成员、属性、方法等。元数据对于反射、序列化、属性(Attributes)等功能至关重要。
1. 元数据的内容
元数据通常包括以下内容:
- 类型信息:类、结构、枚举、接口等。
- 成员信息:字段、属性、方法、事件等。
- 参数信息:方法和属性的参数。
- 自定义属性:用户定义的属性,用于提供额外的元数据信息。
- 版本信息:程序集的版本号。
- 依赖信息:程序集依赖的其他程序集。
2. 元数据的作用
- 反射:通过反射可以动态地获取类型和成员的信息,并在运行时进行操作。
- 序列化:元数据帮助序列化器了解如何将对象转换为字节流。
- 调试:调试器使用元数据来显示变量和类型的详细信息。
- 编译器:编译器使用元数据来验证类型和成员的正确性。
- 工具:各种开发工具(如 Visual Studio)使用元数据来提供智能感知、代码生成等功能。
如何生成dll
解决方案

选择类库

然后在这里面写一堆类

右键类库,点生成或重新生成

文件资源管理器打开

找到bin文件夹,往下找,这个就是生成的dll

C# 反射用途
主要用途:
1. 动态加载程序集:可以通过 Assembly类来动态加载程序集,然后从中获取类型信息。
2. 创建和操作对象:使用 Activator.CreateInstance方法可以根据类型信息在运行时创建对象实例。
3. 访问私有成员:虽然通常不推荐这样做,但反射允许访问类中的私有字段和方法。
4. 获取类型信息:可以获取类的公共属性、字段、方法等的所有信息。
5. 调用方法:即使在编译时不知道方法的名字,也可以通过反射在运行时调用它们。
6. 设置和获取属性值:可以动态地读取或设置对象的属性值。
基本示例
反射加载程序集
使用 Assembly.Load 方法
用途:从全局程序集缓存(GAC)或应用程序的基础目录及其子目录中加载程序集。
优点:简单易用,适用于已知路径的程序集。
缺点:不能加载不在基础目录或GAC中的程序集。
Assembly ToolAssembly = Assembly.Load( "ClassLibrary" );

使用 Assembly.LoadFrom 方法
用途:从指定路径加载程序集,路径可以是绝对路径或相对路径。
优点:可以加载不在基础目录或GAC中的程序集。
缺点:可能会导致程序集加载冲突,因为多个程序集可能具有相同的名字但不同的路径。
Assembly ToolAssembly = Assembly.LoadFrom( "../../../dll/ClassLibrary.dll" ); Assembly ToolAssembly = Assembly.LoadFrom( @"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll" );
这个相对路径是以生成的exe所在目录来相对的



使用 Assembly.LoadFile 方法
用途:从指定文件路径加载程序集,不考虑程序集的依赖关系。
优点:可以加载任何文件路径的程序集。
缺点:不会解析程序集的依赖关系,可能导致运行时错误。
Assembly ToolAssembly = Assembly.LoadFile(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");
使用 AppDomain.CurrentDomain.Load 方法
用途:在指定的应用域中加载程序集。
优点:可以在不同的应用域中加载相同的程序集,适用于需要隔离的场景。
缺点:需要显式管理应用域的生命周期。
byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll"); Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes);
反射读取程序集内类型名称
//加载程序集
byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");
Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes);
//输出类型名称
foreach (var type in ToolAssembly.GetTypes())
{
Console.WriteLine(type.Name);
}
//Tool
//Student
//MyGenericClass`1
反射创建对象
无参数的构造函数
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student()
{
Name = "";
Age = 0;
}
public void ShowInfo()
{
Console.WriteLine($"My Name is {Name},I'm {Age} years old. ");
}
}
我们定义了一个Student类,这个类是有一个无参数构造函数的。
//获取Student类的Type对象 Type StudentType = typeof(Student); //创建实例 object StudentInstance = Activator.CreateInstance(StudentType);
有参数的构造函数
在 C# 中,如果你没有显式地定义任何构造函数,编译器会自动为你添加一个无参数的构造函数。但是一旦你定义了有参数的构造函数,编译器就不会再为你添加无参数的构造函数。可以看到下面这个Student类是没有构造函数的。
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowInfo()
{
Console.WriteLine($"My Name is {Name},I'm {Age} years old. ");
}
}
执行创建对象是不会报错的。但是看下面这个定义了有参数构造函数的Student类
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
public void ShowInfo()
{
Console.WriteLine($"My Name is {Name},I'm {Age} years old. ");
}
}
这个再使用上面的创建对象方式就会报错。会提示没有找到无参数的构造函数。

这种就需要使用Activator.CreateInstance的重载,将构造函数的参数也传入
//获取Student类的Type对象 Type StudentType = typeof(Student); //创建实例 object StudentInstance = Activator.CreateInstance(StudentType,"张三",15);
使用程序集内的类创建对象,只要正确加载DLL就行
//加载程序集 byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll"); Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes); //获取Student类的Type对象 Type StudentType = typeof(Student); //创建实例 object StudentInstance = Activator.CreateInstance(StudentType,"张三",15);
反射读取与赋值对象属性
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student()
{
Name = "";
Age = 0;
}
public Student(string name, int age)
{
Name = name;
Age = age;
}
public void ShowInfo()
{
Console.WriteLine($"My Name is {Name},I'm {Age} years old. ");
}
}
赋值Name属性
//获取Student类的Type对象
Type StudentType = typeof(Student);
//创建实例
object StudentInstance = Activator.CreateInstance(StudentType, "张三", 15);
//获取Name属性对象
PropertyInfo NamePropertyInfo = StudentType.GetProperty("Name");
//设置Name属性的值
NamePropertyInfo.SetValue(StudentInstance, "李四", null);
读取Name属性
2种方式
//打印属性 Console.WriteLine(NamePropertyInfo.GetValue(StudentInstance, null));
//转成dynamic,读取属性 Console.WriteLine(((dynamic)StudentInstance).Name);
反射读取程序集内类的方法名称和参数类型
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student()
{
Name = "";
Age = 0;
}
public Student(string name, int age)
{
Name = name;
Age = age;
}
public void ShowInfo()
{
Console.WriteLine($"My Name is {Name},I'm {Age} years old. ");
}
}
//加载程序集
Assembly ToolAssembly = Assembly.Load("ClassLibrary");
//获取Student类的Type对象
Type StudentType = typeof(Student);
foreach (var method in StudentType.GetMethods())
{
//输出方法名
Console.WriteLine($"参数方法名:{method.Name}");
//输出参数名称和类型
foreach (var param in method.GetParameters())
{
Console.WriteLine($"参数名称:{param.Name},参数类型:{param.ParameterType}");
}
Console.WriteLine("");
}
输出
参数方法名:get_Name 参数方法名:set_Name 参数名称:value,参数类型:System.String 参数方法名:get_Age 参数方法名:set_Age 参数名称:value,参数类型:System.Int32 参数方法名:ShowInfo 参数方法名:GetType 参数方法名:ToString 参数方法名:Equals 参数名称:obj,参数类型:System.Object 参数方法名:GetHashCode
反射调用方法
对象的方法
//加载程序集
Assembly ToolAssembly = Assembly.Load("ClassLibrary");
//获取Student类的Type对象
Type StudentType = typeof(Student);
//获取Student类中的ShowInfo()方法对象
MethodInfo methodInfo = StudentType.GetMethod("ShowInfo");
//调用,这是个动态类,需要指定对象
methodInfo.Invoke(StudentInstance,null);
静态类的方法
public static class Tool
{
/// <summary>
/// 求和a+b
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int AddNumber(int a, int b)
{
return a + b;
}
}
Type ToolType = typeof(Tool);
MethodInfo ToolMethodInfo = ToolType.GetMethod("AddNumber");
// 创建参数数组
object[] parameters = new object[] { 3, 4 };
Console.WriteLine($"3+4={ToolMethodInfo.Invoke(null, parameters)}");
//输出3+4=7
反射创建泛型类对象
泛型类对象创建前,需要将泛型对象类转化为指定类型的类。
public class MyGenericClass<T>
{
public T Value { get; set; }
public MyGenericClass(T value)
{
Value = value;
}
}
class MainApplication
{
static void Main()
{
// 1. 获取泛型类型定义
Type type = typeof(MyGenericClass<>);
// 2. 构造具体的泛型类型
Type specificType = type.MakeGenericType(typeof(int));
// 3. 创建实例
object s = Activator.CreateInstance(specificType,12);
// 输出实例的值
Console.WriteLine(((dynamic)s).Value);
}
}
反射调用泛型方法
普通类的泛型方法
public static class Tool
{
/// <summary>
/// 普通类里的泛型方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="W"></typeparam>
/// <typeparam name="X"></typeparam>
/// <param name="valueT"></param>
/// <param name="valueW"></param>
/// <param name="valueX"></param>
public static void ShowInfo<T,W,X>(T valueT, W valueW, X valueX)
{
Console.WriteLine(valueT);
Console.WriteLine(valueW);
Console.WriteLine(valueX);
}
}
Type GenericType = typeof(Tool);
MethodInfo GenericMethodInfo = GenericType.GetMethod("ShowInfo");
//指定方法参数类型
var GenericMethod = GenericMethodInfo.MakeGenericMethod(new Type[] { typeof(int),typeof(string),typeof(DateTime)});
//调用方法
GenericMethod.Invoke(null,new object[] { 3, "hello", DateTime.Now });
泛型类的泛型方法
namespace ClassLibrary.Generic
{
public class MyGenericClass<T,W,S>
{
public void ShowInfo<X, Y, Z>(X valueT, Y valueW,Z valueX)
{
Console.WriteLine(valueT);
Console.WriteLine(valueW);
Console.WriteLine(valueX);
}
}
}
Type GenericType = ToolAssembly.GetType("ClassLibrary.Generic.MyGenericClass`3").MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });//因为是3个泛型参数,拿占位符才能取到实际名称
object Generic = Activator.CreateInstance(GenericType);
MethodInfo GenericMethodInfo = GenericType.GetMethod("ShowInfo").MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
GenericMethodInfo.Invoke(Generic, new object[] { 3, "hello", DateTime.Now });
注意事项
- 反射的性能开销相对较大,因此应该谨慎使用,特别是在性能敏感的应用中。
- 使用反射访问私有成员可能破坏封装性,应该避免除非有充分的理由。
- 反射可能会导致安全风险,因为它绕过了编译时的类型检查。
对象的方法

浙公网安备 33010602011771号