在 .net core 中为 Roslyn 动态选择引用 [英] Choose references dynamically in .net core for Roslyn
问题描述
TL;DR
如何让运行时在 .NET Core 5 中为在运行时编译的涉及 .NET 4.7.2 代码的 C# 插件选择正确的程序集?
How do I let the runtime choose the right assemblies in .NET Core 5 for C# plugins compiled at runtime that involve .NET 4.7.2 code?
背景
我有一个 .NET 4.7.2 应用程序,根据一些可配置的插件,该应用程序的某些模块的行为有所不同.我在运行时编译 C# 插件的 .NET 4.7.2 程序集中有以下代码.
I have a .NET 4.7.2 app on which certain modules behave differently based on some configurable plugins. I have the following code in a .NET 4.7.2 assembly that compiles C# plugins at runtime.
public OperationResult<Assembly> CompileClass(string code, string[] references, string fileName, bool generateInMemory = true, bool includeDebugInformation = true)
{
OperationResult<Assembly> result = new OperationResult<Assembly> { Success = true };
try
{
string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
bool keepSoureceFilesAfterCompiling = false;
#if (DEBUG)
keepSoureceFilesAfterCompiling = true;
#endif
if (!Directory.Exists(pluginsFolder))
{
Directory.CreateDirectory(pluginsFolder);
}
using (CSharpCodeProvider compiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } }))
{
CompilerParameters parameters = new CompilerParameters()
{
GenerateInMemory = generateInMemory,
IncludeDebugInformation = includeDebugInformation,
OutputAssembly = Path.Combine(pluginsFolder, fileName) + ".dll",
CompilerOptions = "/debug:full",
TempFiles = new TempFileCollection { KeepFiles = keepSoureceFilesAfterCompiling }
};
parameters.ReferencedAssemblies.AddRange(references);
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);
var errors = new StringBuilder();
foreach (CompilerError error in compiledCode.Errors)
{
errors.AppendLine($"Error in line {error.Line}, Column {error.Column}: {error.ErrorText}");
}
if (!string.IsNullOrEmpty(errors.ToString()))
{
result.HandleFailure(errors.ToString());
}
result.ResultObject = compiledCode.CompiledAssembly;
}
}
catch (Exception ex)
{
LogService.Current.LogError(ex);
}
return result;
}
我现在正在尝试将代码(缓慢地)升级到 .NET 5.0,我从 UnitTests(没有其他项目可以参考的项目之一)开始.我写了以下代码
I'm now trying to upgrade to the code (slowly) to .NET 5.0 and I started with the UnitTests (one of the projects to which no other project has a reference). I've written the following code
public OperationResult<Assembly> CompileClassWithRoslyn(string code, List<string> referenceAssemblies, string assemblyName)
{
OperationResult<Assembly> result = new OperationResult<Assembly>();
try
{
//Set file name, location and referenced assemblies
string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);
if (!referenceAssemblies.Any(a => a.Contains("mscorlib")))
{
referenceAssemblies.Add("mscorlib.dll");
}
var references = trustedAssembliesPaths.Where(p => referenceAssemblies.Contains(Path.GetFileName(p)))
.Select(p => MetadataReference.CreateFromFile(p))
.ToList();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
IEnumerable<Diagnostic> failures = emitResult.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
result.HandleFailure(failures.Select(f => f.GetMessage()));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
}
}
}
catch (Exception ex)
{
return result.HandleFailure(ex);
}
return result;
}
在旧代码中,在
parameters.ReferencedAssemblies.AddRange(references);
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);
程序集由运行时自动按名称选择.在新代码中,mscorlib 无法正确解析,因为出现错误:
the assemblies are chosen automatically by name by the runtime. In the new code mscorlib doesn't resolve correctly because I'm get an error:
错误 CS0518:未定义或导入预定义类型System.Object"
Error CS0518: Predefined type 'System.Object' is not defined or imported
推荐答案
在使用 Roslyn 针对 .net5 进行编译时,挑战与针对旧的 .net 框架进行编译有很大不同,因为您必须引用引用程序集 不是实现程序集.许多技巧会让您引用System.Private.CoreLib.dll,即一个实现程序集,从而将您引向糟糕的方向.例如MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
When compiling with Roslyn against .net5, the challenge is quite different from compiling against the legacy .net framework, because you have to reference reference assemblies not implementation assemblies. Many tips will lead you in the bad direction by letting you reference System.Private.CoreLib.dll, that is an implementation assembly. e.g. MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
下面的代码引用了 .net 5 的所有(VB 除外)引用程序集
The code below references all (VB excepted) reference assemblies of .net 5
foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.NETCore.App.Ref5.0.0
ef
et5.0", "*.dll"))
{
if (!dll.Contains("VisualBasic"))
references.Add(MetadataReference.CreateFromFile(dll));
}
如果您使用 Windows 窗体兼容包 (net5.0-windows
),请添加以下程序集:
If you use the Windows Forms compatibility pack (net5.0-windows
), add these assemblies:
foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.WindowsDesktop.App.Ref5.0.0
ef
et5.0", "*.dll"))
{
if (!dll.Contains("VisualBasic") && !dll.Contains("PresentationFramework") && !dll.Contains("ReachFramework"))
references.Add(MetadataReference.CreateFromFile(dll));
}
通过这些参考
- 编译没有错误
- 生成的程序集可以在其他项目中使用而不会抱怨缺少引用(例如 System.Private.CoreLib.dll)
框架的所有程序集?窥探生成的代码时,您将看到仅引用了所需的程序集.
All assemblies of the framework? When spying into the generated code, you will see that only the needed assemblies are referenced.
如果编译必须在上述目录不存在的机器上运行,可能的解决方案是:
If the compilation has to run on machines where the above-mentioned directories don't exist, a possible solution is:
- 将所有这些参考程序集嵌入为嵌入式资源
- 使用
Assembly.GetExecutingAssembly().GetManifestResourceStream
将这些嵌入的资源读取为stream
- 用这些流填充
byte[]
- 使用
references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll)));
添加引用
- embed all these reference assemblies as embedded resources
- use
Assembly.GetExecutingAssembly().GetManifestResourceStream
to read these embedded resources asstream
- fill
byte[]
with those streams - use
references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll)));
to add references
这篇关于在 .net core 中为 Roslyn 动态选择引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!