.NET Framework 4网站加载过多程序集引发CS1647编译错误求助
问题背景
我们维护一个大型CRM WebForms应用,主站通过iframe承载其他模块,站点启动时会加载所有程序集,目前已加载约1850个DLL。新增带有"SITE_NAME"标识的DLL时,触发以下编译错误:
编译错误说明:服务请求所需资源编译期间出错,请查看以下详细错误信息并修改源代码。
编译器错误信息:CS1647: 表达式过长或复杂,无法编译
源错误:[无相关源代码行]
源文件: 行: 0
版本信息:Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.7.3062.0
详细编译输出仅显示所有程序集路径,目前只能通过删除现有文件来避免新增DLL时的错误。我们怀疑问题出在筛选程序集的代码上:
var ourAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList().FindAll(a => a.FullName.Contains("SITE_NAME"));
尝试过拆分变量处理程序集、调整IIS文件大小限制,但都无效;新增不含"SITE_NAME"的DLL不会触发错误。由于代码库庞大,我们需要无需大幅改动的解决方案,相关完整方法代码如下:
public static SystemVersionInfo GetSystemVersionInfo(out MessageSet msg) { msg = null; SystemVersionInfo toReturn = new SystemVersionInfo(); try { var ourAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList().FindAll(a => a.FullName.Contains("SITE_NAME")); List<LibInfo> SITEAssembliesInfo = new List<LibInfo>(); ourAssemblies.ForEach(a => { SITEAssembliesInfo.Add(new LibInfo() { name = a.GetName().Name, version = a.GetName().Version.ToString(), BuildDate = a.GetLinkerTime() }); }); toReturn.SITELibs = SITEAssembliesInfo.OrderBy(l => l.name).ToList(); List<LibInfo> otherAssembliesInfo = new List<LibInfo>(); var otherAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList().FindAll(a => !a.FullName.Contains("SITE_NAME")); otherAssemblies.ForEach(a => { otherAssembliesInfo.Add(new LibInfo() { name = a.GetName().Name, version = a.GetName().Version.ToString() }); }); toReturn.ThirdPartyLibs = otherAssembliesInfo.OrderBy(l => l.name).ToList(); } catch (Exception ex) { msg = MessageCreate.CreateErrorMessage(0, ex, "GetSystemVersionInfo", "SITE_NAME.THIS_APP.WEB.dll"); } return toReturn; }
问题分析
CS1647错误本质是编译器处理的表达式复杂度超出限制。当程序集数量达到1850个时,两次调用FindAll并配合lambda表达式的链式操作,会让JIT编译器生成的表达式树规模过大,最终触发编译失败。同时重复调用AppDomain.CurrentDomain.GetAssemblies()也会加剧资源占用和处理复杂度。
解决方案
以下是无需大幅改动代码的优化方案:
1. 单次遍历程序集,避免重复操作
将两次FindAll合并为一次遍历,同时缓存AssemblyName减少重复调用,降低编译器需要处理的表达式复杂度:
public static SystemVersionInfo GetSystemVersionInfo(out MessageSet msg) { msg = null; SystemVersionInfo toReturn = new SystemVersionInfo(); try { // 仅获取一次全量程序集列表 var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); List<LibInfo> SITEAssembliesInfo = new List<LibInfo>(); List<LibInfo> otherAssembliesInfo = new List<LibInfo>(); // 单次遍历完成分类,替代两次FindAll foreach (var a in allAssemblies) { try { // 缓存AssemblyName,避免重复调用GetName() var assemblyName = a.GetName(); if (a.FullName.Contains("SITE_NAME")) { SITEAssembliesInfo.Add(new LibInfo() { name = assemblyName.Name, version = assemblyName.Version.ToString(), BuildDate = a.GetLinkerTime() }); } else { otherAssembliesInfo.Add(new LibInfo() { name = assemblyName.Name, version = assemblyName.Version.ToString() }); } } catch (Exception) { // 跳过无法访问的程序集(如动态生成的临时程序集) continue; } } toReturn.SITELibs = SITEAssembliesInfo.OrderBy(l => l.name).ToList(); toReturn.ThirdPartyLibs = otherAssembliesInfo.OrderBy(l => l.name).ToList(); } catch (Exception ex) { msg = MessageCreate.CreateErrorMessage(0, ex, "GetSystemVersionInfo", "SITE_NAME.THIS_APP.WEB.dll"); } return toReturn; }
2. 优化程序集匹配逻辑(可选)
如果"SITE_NAME"是程序集名称的固定前缀,改用assemblyName.Name.StartsWith("SITE_NAME")替代FullName.Contains,减少字符串匹配的复杂度,同时避免FullName中版本、公钥等额外信息的干扰。
3. 过滤无效程序集(可选)
跳过动态生成的程序集和无本地文件的程序集,进一步减少处理量:
foreach (var a in allAssemblies) { // 跳过动态程序集和无本地路径的程序集 if (a.IsDynamic || string.IsNullOrEmpty(a.Location)) continue; // 后续分类逻辑... }
4. 启用站点预编译(可选)
对于WebForms应用,启用ASP.NET预编译可以提前编译站点资源,避免运行时编译的表达式复杂度问题:
- 使用
aspnet_compiler.exe工具预编译站点 - 在Visual Studio发布时选择"预编译"选项
方案有效性说明
- 单次遍历替代两次
FindAll,大幅降低了JIT编译器需要处理的表达式树规模 - 缓存
AssemblyName减少了重复反射调用的开销 - 增加异常捕获跳过无效程序集,避免单个程序集的问题影响整体逻辑
内容的提问来源于stack exchange,提问作者Muhammad Nazeer

