引用非项目DLL时CSharpCompilation执行失败问题排查
C#动态编译依赖CsvHelper运行时异常问题解析
背景
我有一个需要编译执行的C#文件code.txt,它依赖NuGet的CsvHelper包。我准备了两个完全相同的项目,唯一区别是其中一个安装了CsvHelper NuGet包,另一个没有安装。
问题
- 安装了CsvHelper NuGet包的项目,编译后的代码可以正常执行;
- 未安装NuGet包的项目,仅手动引用所需的DLL及依赖DLL后,代码能成功编译,但运行时抛出以下异常:
FileNotFoundException: 无法加载文件或程序集'CsvHelper, Version=30.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823'或其依赖项,系统找不到指定文件。
无NuGet安装时的CSharpCompilation代码
MetadataReference[] References = new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Data.DataRow).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Data.DataRowComparer).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Drawing.Bitmap).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Net.Http.HttpClient).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Numerics.BigInteger).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Windows.Forms.Button).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Xml.XmlAttribute).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Xml.Linq.XDocument).Assembly.Location), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\CsvHelper.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\Microsoft.Bcl.AsyncInterfaces.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\Microsoft.Bcl.HashCode.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.Buffers.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.Memory.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.Numerics.Vectors.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.Runtime.CompilerServices.Unsafe.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.Threading.Tasks.Extensions.dll")), MetadataReference.CreateFromFile(Path.GetFullPath(@"..\..\DLL's\System.ValueTuple.dll")) }; string code = File.ReadAllText(Path.GetFullPath(@"..\..\Code.txt")); SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code); Compilation compilation = CSharpCompilation.Create( Path.GetRandomFileName(), new[] { syntaxTree }, References, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Debug )); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); foreach (Diagnostic diagnostic in failures) { Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); } } else { ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); Type type = assembly.GetType("NSImport.ImportTesting"); object obj = Activator.CreateInstance(type); type.InvokeMember("FuncMain", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] { }); } }
Code.txt代码
using System; using System.Globalization; using System.IO; using System.Text; using System.Windows.Forms; using CsvHelper.Configuration; using CsvHelper; namespace NSImport { internal class ImportTesting { public void FuncMain() { string filename = GetInputFileName(); var configuration = new CsvConfiguration(CultureInfo.InvariantCulture) { Encoding = Encoding.UTF8, Delimiter = ",", HasHeaderRecord = true, //default value in any case Quote = '"' //default value in any case }; } private string GetInputFileName() { OpenFileDialog ofd = new OpenFileDialog() { InitialDirectory = @"C:\", Title = "Select input file", DefaultExt = "csv", Filter = "CSV File (*.csv)|*.csv|All Files (*.*)|*.*", FilterIndex = 1, CheckFileExists = true, CheckPathExists = true, Multiselect = false }; if (ofd.ShowDialog() == DialogResult.OK) return ofd.FileName; else return string.Empty; } } }
疑问解答
1. 为何引用的DLL需要成为运行应用的一部分?
C#编译阶段仅需依赖DLL提供的类型定义来校验代码语法和类型合法性,不会把依赖DLL的实现逻辑嵌入到你的输出程序集里。运行时CLR需要加载这些依赖DLL,才能找到代码中调用的类、方法的具体实现,找不到就会抛出文件缺失异常。简单说:编译看定义,运行要实现。
2. 是否可以在不将引用DLL纳入项目的情况下执行代码?
可以,有两种可行方案:
- 合并程序集:用ILMerge、Costura.Fody这类工具把依赖DLL的内容嵌入到你的输出程序集里,运行时无需额外的DLL文件;
- 指定加载路径:确保CLR能在运行时找到DLL,比如把DLL复制到程序工作目录、注册到全局程序集缓存(GAC),或者通过
AppDomain.AssemblyResolve事件手动从指定路径加载DLL。
你当前的问题是编译时引用了DLL,但运行时这些DLL不在CLR的自动查找路径里,所以报错。如果在加载动态编译的程序集前,先把依赖DLL加载到内存,或者把DLL复制到当前工作目录,就能解决问题。
3. 针对模糊的FileNotFoundException,最佳排查方式是什么?
- 启用程序集绑定日志:打开Visual Studio开发者命令提示符,运行
fuslogvw.exe(程序集绑定日志查看器),配置记录失败的绑定请求。它会详细显示CLR尝试加载的程序集版本、查找路径、失败原因,是最直接的排查工具; - 校验DLL版本与签名:确认手动引用的CsvHelper.dll版本是30.0.0.0,且公钥Token与异常信息一致,避免版本不匹配或使用未签名的版本;
- 检查工作目录与DLL路径:用
Directory.GetCurrentDirectory()确认程序运行时的工作目录,验证DLL的实际路径是否正确、文件是否存在; - 手动加载测试:在代码中先调用
Assembly.LoadFrom(@"DLL路径\CsvHelper.dll"),如果加载失败会抛出更具体的异常,比如依赖项缺失、版本不兼容等。
内容的提问来源于stack exchange,提问作者Sigh-AniDe
相关产品推荐
相关产品推荐

