C#反射疑问:加载多个DLL时第二个DLL输出与第一个一致的原因
问题描述
制作简单模组加载器时,加载单个DLL正常,但加载多个DLL时出现异常:第二个DLL的输出与第一个完全相同,尽管两个DLL的MainMod类中Start方法实现完全不同。
加载器核心代码:
private static void Main() { string[] modFolders = [ "Q:\\VisualStudio\\Projects\\C#\\ModLoader\\ModsFolder\\Mod1", "Q:\\VisualStudio\\Projects\\C#\\ModLoader\\ModsFolder\\Mod2" ]; foreach (string modFolder in modFolders) { string dllFile = Directory.GetFiles(modFolder, "*.dll*").First(); Assembly assembly = Assembly.LoadFrom(dllFile); Type type = assembly.GetType($"MainMod"); if (type is not null) { object instance = Activator.CreateInstance(type); MethodInfo method = type.GetMethod("Start"); object result = method.Invoke(instance, null); Console.WriteLine($"result: {result}"); } } }
第一个DLL代码:
public class MainMod { public static int Start() => 100; }
第二个DLL代码:
public class MainMod { public static string Start() => "Text"; }
程序实际输出:
result: 100 result: 100
问题原因
两个DLL中的类都使用了无命名空间的MainMod名称,.NET应用程序域中不允许存在完全同名的类型。当加载第一个DLL后,MainMod类型已被加载到当前应用程序域,后续加载的第二个DLL中同名无命名空间的类型会被忽略,导致始终调用第一个DLL的Start方法。
另外注意:原代码中调用静态方法Start时,Activator.CreateInstance(type)是多余的,静态方法无需实例即可调用。
解决方案
1. 为模组类添加唯一命名空间(推荐)
给每个模组的类添加独立命名空间,确保类型全局唯一:
第一个DLL修改为:
namespace Mod1 { public class MainMod { public static int Start() => 100; } }
第二个DLL修改为:
namespace Mod2 { public class MainMod { public static string Start() => "Text"; } }
同时修改加载器中获取类型的代码,指定完整的命名空间+类型名:
// 可以根据模组标识动态获取命名空间,示例中直接指定 Type type = assembly.GetType($"Mod1.MainMod"); // 对应第一个模组 // 第二个模组对应 Mod2.MainMod
2. 使用Assembly.LoadFile替代Assembly.LoadFrom
Assembly.LoadFrom会共享已加载的依赖类型,而Assembly.LoadFile会将程序集作为独立文件加载,不共享类型(但仍需注意,若存在完全同名类型仍可能有冲突,仅作为临时方案):
Assembly assembly = Assembly.LoadFile(dllFile);
3. 为每个模组创建独立应用程序域(进阶隔离方案)
通过创建独立的应用程序域加载每个模组,实现完全隔离,避免类型冲突:
private static void Main() { string[] modFolders = [ "Q:\\VisualStudio\\Projects\\C#\\ModLoader\\ModsFolder\\Mod1", "Q:\\VisualStudio\\Projects\\C#\\ModLoader\\ModsFolder\\Mod2" ]; foreach (string modFolder in modFolders) { string dllFile = Directory.GetFiles(modFolder, "*.dll").First(); AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(dllFile) }; AppDomain domain = AppDomain.CreateDomain($"ModDomain_{Path.GetFileNameWithoutExtension(dllFile)}", null, setup); try { ModProxy proxy = (ModProxy)domain.CreateInstanceAndUnwrap( typeof(ModProxy).Assembly.FullName, typeof(ModProxy).FullName ); object result = proxy.InvokeStartMethod(dllFile); Console.WriteLine($"result: {result}"); } finally { AppDomain.Unload(domain); } } } // 跨域代理类,必须继承MarshalByRefObject public class ModProxy : MarshalByRefObject { public object InvokeStartMethod(string dllPath) { Assembly assembly = Assembly.LoadFrom(dllPath); Type type = assembly.GetType("MainMod"); if (type != null) { MethodInfo method = type.GetMethod("Start"); return method.Invoke(null, null); // 静态方法无需实例 } return null; } }
内容的提问来源于stack exchange,提问作者Accis
相关产品推荐
相关产品推荐

