二十三、程序集加载与反射(Assembly Loading and Reflection)
CLR #反射 #reflect
程序集加载与反射(Assembly Loading and Reflection)
反射(Reflection)是.NET框架中一个强大的功能,允许程序在运行时动态地检查和操作类型、方法、属性等元数据。
1. 程序集加载(Assembly Loading)
在.NET中,程序集(Assembly)是代码和元数据的容器。程序集加载是反射的基础,CLR(公共语言运行时)通过Assembly类提供多种加载方式:
- Assembly.Load:根据程序集的强名称(包括名称、版本、文化和公钥标记)加载程序集。适用于需要明确版本控制的场景。
- Assembly.LoadFrom:通过文件路径或URL加载程序集。如果程序集已加载,则返回已加载的实例。
- ReflectionOnlyLoad/ReflectionOnlyLoadFrom:加载程序集仅用于元数据检查,禁止执行代码,适合分析工具或处理不受信任的程序集。
以下是一个简单的示例,展示如何使用Assembly.Load加载程序集并获取其类型:
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 加载程序集
Assembly assembly = Assembly.Load("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
// 获取程序集中的所有公共类型
Type[] types = assembly.GetExportedTypes();
foreach (Type type in types)
{
Console.WriteLine($"Type: {type.FullName}");
}
}
}
注意:使用ReflectionOnlyLoad时,需要注册回调方法以加载引用的程序集,因为CLR不会自动加载它们。
Mermaid图表:程序集加载流程
2. 发现类型(Discovering Types)
反射允许开发者在运行时发现程序集中定义的类型。常用的方法包括:
- Type.GetType:根据类型名称获取
Type对象。 - Assembly.GetExportedTypes:获取程序集中的所有公共类型。
- TypeInfo.DeclaredMembers:获取类型的成员(字段、方法、属性等)。
以下代码展示如何遍历程序集中的类型并列出其成员:
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 加载当前程序集
Assembly assembly = Assembly.GetExecutingAssembly();
// 获取所有类型
foreach (Type type in assembly.GetTypes())
{
Console.WriteLine($"Type: {type.Name}");
foreach (MemberInfo member in type.GetTypeInfo().DeclaredMembers)
{
Console.WriteLine($" Member: {member.Name} ({member.MemberType})");
}
}
}
}
关键点:为了提高性能,建议使用接口或基类进行早期绑定,而不是频繁使用反射来访问成员。
3. 调用类型成员(Invoking Members)
反射不仅可以发现类型,还可以动态调用其成员(如方法、属性、构造函数)。以下是调用成员的主要方式:
- MethodInfo.Invoke:调用方法。
- PropertyInfo.GetValue/SetValue:读取或设置属性值。
- ConstructorInfo.Invoke:调用构造函数。
示例代码展示如何动态调用方法和设置属性:
using System;
using System.Reflection;
class TestClass
{
public string Name { get; set; }
public void SayHello(string message) => Console.WriteLine($"Hello, {message}!");
}
class Program
{
static void Main()
{
// 获取类型
Type type = typeof(TestClass);
// 创建实例
object instance = Activator.CreateInstance(type);
// 设置属性
PropertyInfo prop = type.GetProperty("Name");
prop.SetValue(instance, "Grok");
// 调用方法
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(instance, new object[] { "World" });
}
}
输出:
Hello, World!
性能优化:反射调用会带来性能开销,因为它涉及字符串搜索和参数打包。为提高性能,可以:
- 使用
Delegate.CreateDelegate创建委托,缓存方法调用。 - 使用运行时句柄(如
RuntimeMethodHandle)减少内存占用。
以下是使用委托优化方法调用的示例:
using System;
using System.Reflection;
class TestClass
{
public void SayHello(string message) => Console.WriteLine($"Hello, {message}!");
}
class Program
{
static void Main()
{
Type type = typeof(TestClass);
object instance = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("SayHello");
var sayHelloDelegate = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), instance, method);
sayHelloDelegate("World"); // 更高效的调用
}
}
4. 设计支持插件的应用程序
反射在构建可扩展的应用程序(如插件系统)时非常有用。以下是设计插件系统的关键步骤:
- 定义一个独立的
HostSDK程序集,包含接口或基类。 - 插件开发者引用
HostSDK,实现接口。 - 主机应用程序动态加载插件程序集并调用其类型。
以下是一个简单的插件系统示例:
using System;
using System.IO;
using System.Reflection;
public interface IPlugin
{
void Execute();
}
class Program
{
static void Main()
{
// 加载所有.dll文件
foreach (string file in Directory.GetFiles(".", "*.dll"))
{
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes())
{
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface)
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugin.Execute();
}
}
}
}
}
建议:将接口定义在单独的程序集中,避免版本控制问题。考虑使用MEF(Managed Extensibility Framework)简化插件注册和发现。
5. 反射的性能注意事项
反射虽然强大,但性能开销较大,主要原因包括:
- 字符串搜索:元数据扫描涉及大量的字符串比较。
- 参数打包:调用方法时需要将参数打包为数组。
- 类型检查:CLR需要验证参数类型和访问权限。
优化策略:
- 使用接口或基类进行早期绑定。
- 缓存
Type、MethodInfo等对象。 - 使用运行时句柄(如
RuntimeTypeHandle)减少内存占用。
以下是使用运行时句柄的示例:
using System;
using System.Reflection;
class Program
{
static void Main()
{
Type type = typeof(string);
RuntimeTypeHandle handle = Type.GetTypeHandle(type);
// 从句柄恢复Type对象
Type restoredType = Type.GetTypeFromHandle(handle);
Console.WriteLine($"Restored Type: {restoredType.FullName}");
}
}
6. 常见面试题及解析
以下是一些与反射和程序集加载相关的常见面试题:
Q1:什么是反射?在C#中有哪些实际应用场景?
解析:反射是运行时检查和操作类型元数据的机制。常见应用包括:
- 动态加载插件或模块。
- 序列化/反序列化对象(如JSON库)。
- 开发工具(如Visual Studio的属性窗口)。
- 单元测试框架(如NUnit)动态调用测试方法。
Q2:如何优化反射的性能?
解析:反射性能开销较大,可通过以下方式优化:
- 使用接口或基类进行早期绑定。
- 缓存
Type和MemberInfo对象。 - 使用
Delegate.CreateDelegate创建委托。 - 使用运行时句柄(如
RuntimeMethodHandle)减少内存占用。
Q3:Assembly.Load和Assembly.LoadFrom有什么区别?
解析:
Assembly.Load:根据强名称加载程序集,优先从GAC(全局程序集缓存)查找,适合需要严格版本控制的场景。Assembly.LoadFrom:根据文件路径加载程序集,适合加载本地或动态下载的程序集。如果程序集已加载,返回现有实例。
Q4:如何实现一个简单的插件系统?
解析:实现插件系统需要:
- 定义一个接口(如
IPlugin)在独立的程序集中。 - 插件实现该接口并编译为单独的程序集。
- 主机应用程序使用
Assembly.LoadFrom加载插件程序集,遍历类型,实例化实现IPlugin的类并调用其方法。
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号