背景与需求分析
最近在开发一个工控机环境部署工具时,遇到了一个差异化的需求:不同版本的Web项目需要执行不同的系统环境检测流程。如果每次部署都要根据配置文件动态调用不同的检测方法,有的需要实例化对象,有的要调用静态方法,甚至还要处理异步任务,就会将维护工作搞的很繁重。
传统的硬编码方式显然无法满足这种灵活需求,于是我决定采用.NET的反射机制。作为NET核心功能之一,反射就像程序的"自省"能力,允许我们在运行时探索类型信息、动态创建对象并执行方法。
设计思路
整个方案围绕三个核心环节展开:
- 配置驱动:用JSON定义要执行的方法
- 动态解析:通过反射加载程序集、查找类型和方法
- 智能执行:自动处理同步/异步方法调用
开发工具
- 开发环境: Visual Studio 2022
- 目标框架: .NET 8
- 依赖库 :
System.Reflection
、System.Text.Json
(用于JSON配置解析)
实现思路
1.动态方法配置定义:声明方法反射的配置,比如方法名称、是否需要创建实例、是否异步任务 2.动态方法解析实现:根据传入配置解析程序集、解析类型、获取方法、创建实例等 3.动态方法执行实现:根据获取到的方法、实例、方法执行方式,通过Method.invoke完成方法调用 4.执行方法测试:配置一个文件,测试方法执行
代码实现解析
1.配置模型设计
csharp
/// <summary>
/// 方法反射配置
/// </summary>
public class MethodReflectConfig
{
/// <summary>
/// 执行函数名称
/// 程序集名称::命名空间.类名称::方法名称
/// </summary>
public string Function { get; set; }
/// <summary>
/// 是否需要创建新实例
/// </summary>
public bool Instance { get; set; }
}
2.反射解析器实现
csharp
/// <summary>
///解析方法和方法类型,同时返回创建实例
/// </summary>
/// <param name="functionPath"> 程序集名称::命名空间.类名称::方法名称,
/// 例如 YourDLLName::YourNamespace.SystemDetector::GetSystemInfoAsync
/// </param>
/// <param name="bInstance">是否需要创建新实例</param>
/// <returns></returns>
/// <exception cref="FormatException"></exception>
/// <exception cref="TypeLoadException"></exception>
/// <exception cref="MissingMethodException"></exception>
public static (object instance, Type classtype, MethodInfo method) ParseMethod(MethodReflectConfig config)
{
//解析函数路径
var parts = config.Function.Split("::");
if (parts.Length != 3) throw new FormatException("函数路径格式错误");
//加载程序集
var assemblyName = parts[0].Trim();
var assembly = Assembly.Load(assemblyName)
?? throw new TypeLoadException($"未能加载程序集: {parts[0]}");
//获取类型
var className = parts[1].Trim();
var classtype = assembly.GetType(className)
?? throw new TypeLoadException($"未能找到类型: {parts[1]}");
// 创建实例(如果需要)
object instance = config.Instance ?
Activator.CreateInstance(type) :
null;
// 获取方法
const BindingFlags flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var method = type.GetMethod(parts[2].Trim(), flags)
?? throw new MissingMethodException($"方法 {parts[2]} 未找到");
//返回结果
return (instance, classtype, method);
}
3.动态方法执行实现
csharp
/// <summary>
/// 执行动态方法
/// </summary>
/// <param name="config"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public async Task<object> ExecuteMethodAsync(MethodReflectConfig config, object[] parameters)
{
var (instance, _, method) = AssemblyHelper.ParseMethod(config);
// 获取默认参数(适用于可选参数)
var paramInfos = method.GetParameters();
parameters = parameters ?? new object[paramInfos.Length];
for (int i = 0; i < paramInfos.Length; i++)
{
if (i >= parameters.Length || parameters[i] == null)
{
if (paramInfos[i].HasDefaultValue)
{
parameters[i] = paramInfos[i].DefaultValue;
}
}
}
var methodResult = method.Invoke(instance, parameters);
//处理异步方法
if (methodResult is Task task)
{
await task.ConfigureAwait(false);
// 处理有返回值的Task<T>
if (task.GetType().IsGenericType)
{
return task.GetType().GetProperty("Result")?.GetValue(task);
}
return null; // 无返回值的Task
}
// 处理同步方法返回值
return methodResult;
}
实战测试
1.上个配置文件 (1)创建配置文件,存储名称为 xx.json (2)由于有中文名称,注意文件存储为utf-8格式 (3)如果不想自己手写或者写代码生成json文件,将类定义传递给deepseek或者豆包等助手,让他帮你自动生成
json
{
"Steps": [
{
//名称
"Name": "检测系统信息",
//描述
"Description": "检测系统信息",
"FunctionConfig":{
//方法路径
"Function": "DLLName::YourNameSpace.ClassName::FunctiongName1"
},
//执行失败是否终止流程
"HaltOnError": false
},
{
"Name": "检测系统注册",
"Description": "检测系统注册",
"FunctionConfig":{
"Function": "DLLName::YourNameSpace.ClassName::FunctiongName1"
},
"HaltOnError": false
},
{
"Name": "检测磁盘信息",
"Description": "检测磁盘信息",
"FunctionConfig":{
"Function": "DLLName::YourNameSpace.ClassName::FunctiongName1"
},
"FunctionConfig2":{
"Function": "DLLName::YourNameSpace.ClassName::FunctiongName1",
"Instance":true
},
"HaltOnError": false
}
]
}
2.自己写一个载入代码 (1)jsonhelper读取json文件为执行对象列表steps (2)按顺序执行对象函数列表 (3)此处CallBack为回调,根据自己需求定义
csharp
/// <summary>
/// 执行系统体检
/// </summary>
/// <returns></returns>
public async Task ExecuteDetectFlowAsync(Action<string, object, float> CallBack)
{
var steps = LoadConfigFromJson("xx.json");
int totalSteps = steps.Count;
for (int index = 0; index < totalSteps; index++)
{
var step = steps[index];
try
{
// 执行第一步检测
var result1 = await ExecuteMethodAsync(step.FunctionConfig, null);
using (var disposableResult = result1 as IDisposable)
{
_WriteModuleLog($"[{step.Name}] Result1:{JsonHelper.ToJson(disposableResult)}");
// 执行可选的第二步处理
object finalResult = disposableResult;
if (step.FunctionConfig2 != null)
{
var parameters = new object[] { disposableResult, _configManager.GetSysDiagnosisConfig() };
finalResult = await ExecuteMethodAsync(step.FunctionConfig2, parameters);
_WriteModuleLog($"[{step.Name}] Result2:{JsonHelper.ToJson(finalResult)}");
}
CallBack(step.Name, finalResult, (float)(index + 1) / totalSteps);
}
}
catch (Exception ex)
{
SeriLogHelper.LogException(ex, $"[{step.Name}] 执行失败: {ex.Message}");
if (step.HaltOnError) break;
}
}
}
总结与扩展思考
通过反射机制实现动态方法调用,显著提升了系统的灵活性和可配置性,适应了我当前项目的需求。
- 适用场景:适合插件化架构、流程动态编排等场景,但频繁调用时需谨慎性能损耗。
- 扩展方向:支持依赖注入、方法参数自动绑定(如从环境变量或数据库读取参数)。
总结与扩展思考
通过反射机制实现动态方法调用,显著提升了系统的灵活性和可配置性,适应了我当前项目的需求。
- 适用场景:适合插件化架构、流程动态编排等场景,但频繁调用时需谨慎性能损耗。
- 扩展方向:支持依赖注入、方法参数自动绑定(如从环境变量或数据库读取参数)。
最终目标:在灵活性与性能之间找到平衡,为动态化需求提供可靠的技术支撑。