Roslyn - 使用 CSharpCompilation 编译程序集以在 CSharpCompilation 编译的另一个程序中使用

Posted

技术标签:

【中文标题】Roslyn - 使用 CSharpCompilation 编译程序集以在 CSharpCompilation 编译的另一个程序中使用【英文标题】:Roslyn - Use CSharpCompilation to compile assembly to be used in another program compiled by CSharpCompilation 【发布时间】:2020-10-12 21:02:12 【问题描述】:

我正在尝试使用 CSharpCompilation 编译一个程序集,其中包含一个简单的类,然后我可以在另一个也通过 CSharpCompilation 编译的程序中引用它。我有这个代码:

命名空间编译测试

    课堂节目
    
        静态无效主要(字符串 [] 参数)
        
            HashSet 引用的Assemblies = new HashSet()
            
                typeof(object).Assembly,
                Assembly.Load(new AssemblyName("Microsoft.CSharp")),
                Assembly.Load(new AssemblyName("netstandard")),
                Assembly.Load(new AssemblyName("System.Runtime")),
                Assembly.Load(new AssemblyName("System.Linq")),
                Assembly.Load(new AssemblyName("System.Linq.Expressions"))
            ;
            
            字符串问候语 = @"
命名空间 TestLibraryAssembly

    公共静态类问候语
    
        公共静态字符串 GetGreeting(字符串名称)
        
            返回“”你好,“”+名称+“”!“”;
        
    

";
            
            CSharpCompilation 编译1 = CSharpCompilation.Create(
                "TestLibraryAssembly",
                新的 []
                
                    CSharpSyntaxTree.ParseText(greetingClass)
                ,
                referencedAssemblies.Select(assembly => MetadataReference.CreateFromFile(assembly.Location)).ToList(),
                新 CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
            MemoryStream memoryStream1 = new MemoryStream();
            EmitResult emitResult1 = 编译1.Emit(memoryStream1);
            memoryStream1.Position = 0;
            MetadataReference testLibraryReference = MetadataReference.CreateFromStream(memoryStream1);

            字符串程序代码 = @"
使用 TestLibraryAssembly;

命名空间测试程序

    公开课程序
    
        公共无效主要()
        
            字符串问候 = Greeting.GetGreeting(""Name"");
        
    

";
            
            CSharpCompilation 编译2 = CSharpCompilation.Create(
                "测试程序",
                新的 []
                
                    CSharpSyntaxTree.ParseText(programCode)
                ,
                引用的程序集
                    .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
                    .Concat(new List  testLibraryReference ).ToList(),
                新 CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
            MemoryStream memoryStream2 = new MemoryStream();
            EmitResult emitResult2 = 编译2.Emit(memoryStream2);
            memoryStream2.Position = 0;
            
            汇编程序Assembly = Assembly.Load(memoryStream2.ToArray());
            类型 programType = programAssembly.GetType("TestProgram.Program");
            MethodInfo 方法 = programType.GetMethod("Main");
            对象实例 = Activator.CreateInstance(programType);
            方法。调用(实例,空);
        
    

但是,当我运行它时,我得到了这个错误:

未处理的异常。 System.Reflection.TargetInvocationException:调用的目标已引发异常。

---> System.IO.FileNotFoundException:无法加载文件或程序集“TestLibraryAssembly,Version=0.0.0.0,Culture=neutral,PublicKeyToken=null”。系统找不到指定的文件。

文件名:'TestLibraryAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'

我该如何解决这个错误?

【问题讨论】:

【参考方案1】:

您的示例中的问题是激活器 (Activator.CreateInstance(programType);) 如何尝试解决依赖关系。它会在磁盘上查找文件。

解决此问题的一种方法是将文件保存在磁盘上,然后使用Activator.CreateInstanceFrom 创建实例。

您可以在这里找到一个正在执行此操作的 sn-p:

using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Playground

    public static class Program
    
        private const string firstClass =
@"
namespace A

    public class Foo
    
        public int Bar() => 21;
    
";

        private const string secondClass =
@"using A;

namespace B

    public class Test
    
        public int GetValue() => new Foo().Bar();
    

";

        public static void Main()
        
            var firstAssemblyFileName = Path.Combine(Path.GetTempPath(), "A.dll");
            var secondAssemblyFileName = Path.Combine(Path.GetTempPath(), "B.dll");

            var compilation = CreateCompilation(CSharpSyntaxTree.ParseText(firstClass), "A");
            var secondCompilation = CreateCompilation(CSharpSyntaxTree.ParseText(secondClass), "B")
                .AddReferences(compilation.ToMetadataReference());

            compilation.Emit(firstAssemblyFileName);
            secondCompilation.Emit(secondAssemblyFileName);

            dynamic testType = Activator.CreateInstanceFrom(secondAssemblyFileName, "B.Test").Unwrap();
            var value = testType.GetValue();
        

        private static CSharpCompilation CreateCompilation(SyntaxTree tree, string name) =>
            CSharpCompilation
                .Create(name, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
                .AddSyntaxTrees(tree);
    

【讨论】:

【参考方案2】:

由于您使用的是netstandard,因此没有引用System.IO

将此行添加到referencedAssembliesHashSet 声明部分:

Assembly.Load("System.IO.FileSystem")

已编辑:

或者更安全的使用:

typeof(File).Assembly

【讨论】:

以上是关于Roslyn - 使用 CSharpCompilation 编译程序集以在 CSharpCompilation 编译的另一个程序中使用的主要内容,如果未能解决你的问题,请参考以下文章

使用 Roslyn 编译时自动解析依赖关系

确定是不是使用 Roslyn 读取私有字段

使用 Roslyn 编译 C# 项目时,如何避免完全重新编译?

无法让 Roslyn 使用 .NET 5 项目

使用 Roslyn 编译器服务

使用 Roslyn 编译器服务