You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET(Unity)模组依赖库多版本共存方案技术咨询

解决方案:实现Unity模组间工具库版本隔离的确定性方案

针对你遇到的Unity模组依赖版本冲突问题,以下是三个无需模组作者复杂配置的确定性实现方案,按适用场景优先级排序:

方案1:Assembly Load Context(ALC)隔离加载(Unity 2021+ 推荐)

利用.NET Core引入的Assembly Load Context实现程序集的独立加载,每个模组拥有专属的加载上下文,彻底隔离不同版本的工具库。

实现步骤(完全封装在NuGet包中,模组作者无感知)

  1. 强名称签署工具库:给每个版本的工具库生成唯一强名称(包含版本号、公钥令牌),确保程序集标识唯一。
  2. 开发引导程序集:创建UtilityBootstrapper程序集,作为NuGet包的强制依赖,提供静态加载方法:
    public static class UtilityLoader
    {
        private static readonly Dictionary<string, AssemblyLoadContext> _modAlcs = new Dictionary<string, AssemblyLoadContext>();
    
        public static T GetUtilityInstance<T>() where T : class
        {
            // 获取调用模组的程序集,确定模组目录
            var callingAssembly = Assembly.GetCallingAssembly();
            var modId = callingAssembly.GetName().Name;
            var modDir = Path.GetDirectoryName(callingAssembly.Location);
    
            // 创建或复用模组专属ALC
            if (!_modAlcs.TryGetValue(modId, out var alc))
            {
                alc = new AssemblyLoadContext(modId, isCollectible: true);
                // 添加模组目录作为加载源
                alc.Resolving += (ctx, name) =>
                {
                    if (name.Name != "UtilityLib") return null;
                    var dllPath = Path.Combine(modDir, $"{name.Name}.dll");
                    return File.Exists(dllPath) ? ctx.LoadFromAssemblyPath(dllPath) : null;
                };
                _modAlcs[modId] = alc;
            }
    
            // 加载对应版本的工具库并返回实例
            var utilityAssembly = alc.LoadFromAssemblyPath(Path.Combine(modDir, "UtilityLib.dll"));
            var targetType = utilityAssembly.GetType(typeof(T).FullName);
            return (T)Activator.CreateInstance(targetType);
        }
    }
    
  3. NuGet包自动化配置:在NuGet包的.targets文件中,自动将引导程序集和工具库DLL复制到模组输出目录,无需模组作者手动复制。
  4. 模组作者使用方式:仅需一行代码获取工具库实例,无需额外配置:
    var myUtility = UtilityLoader.GetUtilityInstance<IMyUtility>();
    

优势

  • 完全隔离不同版本的程序集,无类型冲突风险
  • 支持程序集卸载(Collectible ALC),减少内存占用
  • Unity 2021+ 原生支持,兼容性好

方案2:版本化命名空间+Facade封装(兼容所有Unity版本)

通过给每个版本的工具库分配独立命名空间,让.NET/Unity将不同版本的类型视为完全独立的实体,彻底避免冲突。

实现步骤(封装在NuGet包中,模组作者无感知)

  1. 自动版本化命名空间:使用MSBuild任务在NuGet打包时,自动给工具库的所有类型添加版本前缀命名空间:
    • 1.0.0版本:UtilityLib.v1_0_0.Feature
    • 2.0.0版本:UtilityLib.v2_0_0.Feature
      示例MSBuild任务片段(添加到工具库csproj):
    <Target Name="VersionizeNamespaces" BeforeTargets="Compile">
        <ItemGroup>
            <Compile Include="**/*.cs" />
        </ItemGroup>
        <Exec Command="dotnet tool run roslynator rewrite $(Compile) --namespace-prefix &quot;UtilityLib.v$(Version.Replace('.', '_'))&quot;" />
    </Target>
    
  2. 提供无版本Facade程序集:创建UtilityLib.Facade程序集,对外暴露统一的无版本命名空间(如UtilityLib.Feature),内部通过编译时绑定转发到对应版本的类型:
    // Facade中的类型,编译时绑定到当前版本的工具库类型
    public class Feature : UtilityLib.v2_0_0.Feature { }
    
  3. NuGet包配置:将Facade程序集设为默认引用,工具库本体设为私有依赖,自动复制到模组目录。

优势

  • 兼容所有Unity版本(包括Mono和IL2CPP)
  • 无运行时反射开销,性能稳定
  • 模组作者完全感知不到版本化命名空间的存在,使用方式与普通库一致

IL2CPP注意事项

  • 给工具库的所有类型添加[UnityEngine.Scripting.Preserve]属性,避免被IL2CPP裁剪
  • Facade程序集需与工具库版本严格对应,确保编译时绑定正确

方案3:强名称+AssemblyResolve事件拦截(旧版Unity折中方案)

针对无法使用ALC的旧版Unity,通过拦截程序集加载事件,强制从模组目录加载对应版本的工具库。

实现步骤

  1. 强名称签署工具库:确保每个版本的工具库拥有唯一的版本号和公钥令牌。
  2. 开发绑定重定向引导程序:创建UtilityBindingRedirect程序集,提供初始化方法:
    public static class UtilityBindingRedirect
    {
        public static void Initialize()
        {
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
            {
                var requestedAssembly = new AssemblyName(args.Name);
                if (requestedAssembly.Name != "UtilityLib") return null;
    
                // 获取调用模组的目录
                var callingAssembly = Assembly.GetCallingAssembly();
                var modDir = Path.GetDirectoryName(callingAssembly.Location);
                var targetDllPath = Path.Combine(modDir, $"{requestedAssembly.Name}.dll");
    
                if (!File.Exists(targetDllPath)) return null;
    
                // 验证加载的程序集是否匹配请求的版本和公钥
                var loadedAssembly = Assembly.LoadFrom(targetDllPath);
                var loadedName = loadedAssembly.GetName();
                if (loadedName.Version == requestedAssembly.Version && 
                    loadedName.GetPublicKeyToken().SequenceEqual(requestedAssembly.GetPublicKeyToken()))
                {
                    return loadedAssembly;
                }
    
                return null;
            };
        }
    }
    
  3. NuGet包配置:自动将引导程序集和工具库DLL复制到模组目录,并在模组的初始化代码中自动注入UtilityBindingRedirect.Initialize()调用(通过MSBuild任务修改模组的csproj)。

优势

  • 无需修改工具库的命名空间
  • 兼容Unity 2019及以前版本

注意事项

  • 无法实现程序集卸载,长期运行可能导致内存占用增加
  • 需确保模组的初始化代码优先调用引导程序的初始化方法

内容的提问来源于stack exchange,提问作者Morgan Touverey Quilling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:37:08