VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 反射基础——C#演示动态加载程序集

第一部分:C#基础入门
实例9:反射基础——动态加载程序集
实例介绍
在软件开发中,插件系统是常见的需求:主程序不需要提前知道插件的存在,运行时可以动态添加/移除插件,扩展功能。比如VS Code的插件、Photoshop的滤镜等。这种场景的核心技术就是反射中的动态加载程序集:
反射:允许程序在运行时获取自身或外部程序集的类型信息(类、接口、方法等),并动态创建实例、调用方法;
动态加载程序集:主程序通过反射加载外部.dll文件(插件),无需在编译时引用插件项目,实现“热插拔”。
本实例通过实现一个简单的插件系统,带你掌握反射的核心用法:加载程序集、获取类vb.net教程C#教程python教程SQL教程access 2010教程
型、创建实例、调用方法,理解反射的底层原理和应用场景,学会在实际项目中使用反射实现动态扩展。
需求分析
1.定义插件接口:IPlugin,包含插件的核心方法Execute();
2.创建插件实现:HelloPlugin类(实现IPlugin接口),作为外部插件程序集;
3.主程序功能:
动态加载插件程序集(HelloPlugin.dll);
从程序集中找到实现IPlugin接口的类型;
创建插件实例,调用Execute()方法;
处理反射过程中的异常(如加载失败、找不到类型等);
4.验证场景:主程序无需引用插件项目,运行时动态加载并执行插件功能。
代码实现
步骤1:定义插件接口(Shared项目)
首先创建一个共享类库项目(Shared),定义插件接口:
csharp

	// Shared/IPlugin.cs
	namespace Shared
	{
	/// <summary>
	/// 插件接口,所有插件必须实现此接口
	/// </summary>
	public interface IPlugin
	{
	/// <summary>
	/// 插件执行方法
	/// </summary>
	void Execute();
	}
	}

步骤2:创建插件实现(HelloPlugin项目)
创建另一个类库项目(HelloPlugin),引用Shared项目,实现IPlugin接口:
csharp

	// HelloPlugin/HelloPlugin.cs
	using Shared;
	using System;
	
	namespace HelloPlugin
	{
	/// <summary>
	/// 插件实现类
	/// </summary>
	public class HelloPlugin : IPlugin
	{
	public void Execute()
	{
	Console.WriteLine("=== HelloPlugin 执行成功 ===");
	Console.WriteLine("插件名称:HelloPlugin");
	Console.WriteLine("插件功能:输出欢迎信息");
	}
	}
	}

编译此项目,生成HelloPlugin.dll文件(路径记为./Plugins/HelloPlugin.dll)。
步骤3:主程序动态加载插件(MainApp项目)
创建控制台项目(MainApp),引用Shared项目,编写动态加载代码:
csharp

	// MainApp/Program.cs
	using Shared;
	using System;
	using System.Reflection;
	
	namespace MainApp
	{
	class Program
	{
	static void Main(string[] args)
	{
	// 插件程序集路径
	string pluginPath = "./Plugins/HelloPlugin.dll";
	
	try
	{
	// 1. 动态加载程序集
	Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
	Console.WriteLine($"成功加载程序集:{pluginAssembly.FullName}");
	
	// 2. 获取程序集中所有实现IPlugin接口的类型
	Type pluginType = null;
	foreach (Type type in pluginAssembly.GetTypes())
	{
	// 判断类型是否实现了IPlugin接口,且不是接口或抽象类
	if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
	{
	pluginType = type;
	break;
	}
	}
	
	if (pluginType == null)
	{
	Console.WriteLine("未找到实现IPlugin接口的类型");
	return;
	}
	Console.WriteLine($"找到插件类型:{pluginType.FullName}");
	
	// 3. 创建插件实例(反射方式)
	object pluginInstance = Activator.CreateInstance(pluginType);
	if (pluginInstance == null)
	{
	Console.WriteLine("创建插件实例失败");
	return;
	}
	
	// 4. 转换为IPlugin接口,调用Execute方法
	IPlugin plugin = pluginInstance as IPlugin;
	plugin?.Execute();
	
	// 【拓展】直接调用方法(不通过接口)
	// MethodInfo executeMethod = pluginType.GetMethod("Execute");
	// executeMethod?.Invoke(pluginInstance, null);
	
	}
	catch (FileNotFoundException ex)
	{
	Console.WriteLine($"程序集文件未找到:{ex.Message}");
	}
	catch (BadImageFormatException ex)
	{
	Console.WriteLine($"程序集格式错误(非.NET程序集):{ex.Message}");
	}
	catch (MissingMethodException ex)
	{
	Console.WriteLine($"找不到无参构造函数:{ex.Message}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"反射过程出错:{ex.Message}");
	}
	}
	}
	}

逐行讲解
核心反射代码解析

动态加载程序集:

csharp

Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);

Assembly.LoadFrom():从指定路径加载程序集文件(.dll或.exe);
返回Assembly对象:代表加载的程序集,包含程序集的所有类型信息;
注意:LoadFrom会加载程序集及其依赖项(如果依赖项在同一目录)。

查找插件类型:

csharp

	foreach (Type type in pluginAssembly.GetTypes())
	{
	if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
	{
	pluginType = type;
	break;
	}
	}

pluginAssembly.GetTypes():获取程序集中所有的公共类型;
typeof(IPlugin).IsAssignableFrom(type):判断类型是否实现了IPlugin接口;
!type.IsInterface && !type.IsAbstract:排除接口和抽象类,只保留可实例化的具体类。

创建实例:

csharp

object pluginInstance = Activator.CreateInstance(pluginType);

Activator.CreateInstance():通过反射创建类型的实例;
要求类型必须有无参公共构造函数(否则会抛出MissingMethodException);
如果需要带参数的构造函数,可以用CreateInstance(type, param1, param2)。

调用接口方法:

csharp

IPlugin plugin = pluginInstance as IPlugin;
plugin?.Execute();

转换为IPlugin接口:利用接口的多态性,避免直接使用反射调用方法(更安全、性能更高);
plugin?.Execute():空安全调用,防止实例转换失败导致空引用异常。

直接调用方法(拓展):

csharp

MethodInfo executeMethod = pluginType.GetMethod("Execute");
executeMethod?.Invoke(pluginInstance, null);

Type.GetMethod("Execute"):获取类型中名为Execute的无参方法;
MethodInfo.Invoke():调用方法,参数为实例对象和方法参数数组(无参则为null);
适用场景:当没有接口时,直接调用类型的方法(但不推荐,因为耦合度高)。
基础知识拓展

  1. 程序集加载方式对比
    .NET提供了多种加载程序集的方法,核心区别在于加载上下文和依赖处理:
    方法 作用 特点
    Assembly.Load() 从GAC、应用程序目录或配置文件中加载程序集(按程序集名称) 优先从GAC加载,依赖项自动加载,适合加载系统程序集或已知名称的程序集
    Assembly.LoadFrom() 从指定路径加载程序集 加载程序集及其依赖项(依赖项需在同一目录),会缓存程序集
    Assembly.LoadFile() 从指定路径加载程序集(不加载依赖项) 不缓存程序集,同一文件多次加载会生成多个Assembly对象,适合加载独立程序集
    注意:.NET Core 3.0+推荐使用AssemblyLoadContext(ALC)加载程序集,支持卸载(解决传统反射无法卸载程序集的问题)。
  2. 反射性能优化
    反射的性能比直接调用稍差(因为需要运行时解析类型和方法),但可以通过以下方式优化:
    缓存Type和MethodInfo:将常用的Type、MethodInfo、PropertyInfo缓存到字典中,避免重复查找;
    使用接口或泛型:通过接口转换后调用方法(如实例中的IPlugin),比直接反射调用快2-5倍;
    使用表达式树:动态生成Lambda表达式调用方法,性能接近直接调用;
    避免频繁创建实例:如果插件实例可复用,缓存实例而不是每次创建。
  3. 反射的应用场景
    插件系统:如VS Code插件、游戏Mod系统;
    序列化/反序列化:如Newtonsoft.Json、System.Text.Json通过反射读取对象属性;
    ORM框架:如Entity Framework Core通过反射映射数据库表和实体类;
    依赖注入(DI):如Autofac、Microsoft.Extensions.DependencyInjection通过反射创建服务实例;
    测试框架:如xUnit、NUnit通过反射发现测试方法。
  4. 反射的权限问题
    反射需要ReflectionPermission权限(.NET Framework中),在部分受限制的环境(如ASP.NET Medium Trust)中可能无法使用。.NET Core后权限模型简化,但仍需注意:
    反射可以访问私有成员(通过BindingFlags.NonPublic),但会破坏封装性,需谨慎使用;
    对于internal成员,需要在程序集上添加[assembly: InternalsVisibleTo("MainApp")]特性才能通过反射访问。
    扩展思考

如何卸载程序集?

传统反射中,Assembly加载后默认无法卸载(因为AppDomain缓存);
.NET Core 3.0+使用AssemblyLoadContext(ALC):创建自定义ALC加载程序集,使用完毕后销毁ALC即可卸载程序集;
示例代码(ALC方式):
csharp

	var alc = new AssemblyLoadContext("PluginALC", isCollectible: true);
	Assembly pluginAssembly = alc.LoadFromAssemblyPath(pluginPath);
	// 使用完毕后卸载
	alc.Unload();

如何处理带参数的构造函数?

使用Activator.CreateInstance(type, param1, param2):传入构造函数的参数;
示例:object instance = Activator.CreateInstance(type, "参数1", 123);

如何访问私有成员?

使用BindingFlags指定访问权限:
csharp

// 获取私有方法
MethodInfo privateMethod = type.GetMethod("PrivateMethod", BindingFlags.Instance | BindingFlags.NonPublic);
// 调用私有方法
privateMethod.Invoke(instance, null);

总结
反射是.NET中动态扩展的核心技术,动态加载程序集是反射最典型的应用场景。通过反射,我们可以实现插件系统、热更新等灵活的功能,但也需要注意:
优点:灵活性高,无需提前引用程序集,支持动态扩展;
缺点:性能稍差(可通过缓存优化),类型安全需要手动保证(如接口转换);
最佳实践:优先使用接口+反射的方式(如实例中的IPlugin),既保证灵活性,又兼顾性能和安全。
掌握反射,你就能写出更具扩展性的程序,应对复杂的业务需求(如插件化架构、动态配置等)。

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49410.html


相关教程