使用Roslyn脚本化C#时如何调用不包含在运行时中的程序集

使用Roslyn的CSharpScript类实现C#代码的脚本化时,包含在运行时内的程序集可以直接调用,而不包含在运行时的程序集,即使在项目中已经引用了也不可以调用。本文简单描述了使用Roslyn的CSharpScript类实现C#代码的脚本化时,如何调用不包含在运行时的程序集。

情景再现

首先创建一个C#类库,代码很简单,提供一个静态公共方法,打印一个字符串

namespace ClassLibrary1
{
    public class Class1
    {
        public static void TestMethod()
        {
            Console.WriteLine("Class1:TestMethod");
        }
    }
}

然后在创建一个C#控制台,引用类库,然后分别通过普通方式和脚本化的方式调用TestMethod

using ClassLibrary1;
using Microsoft.CodeAnalysis.CSharp.Scripting;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Class1.TestMethod();
            var code2 = @"Class1.TestMethod();";
            try
            {
                CSharpScript.Create(code2).RunAsync().GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
}

结果如下

Class1::TestMethod.Call directly
Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,1): error CS0103: 当前上下文中不存在名称“Class1”
   在 Microsoft.CodeAnalysis.Scripting.ScriptBuilder.ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, DiagnosticFormatter formatter) 位置 /_/src/Scripting/Core/ScriptBuilder.cs:行号 105
   在 Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, Boolean emitDebugInformation, CancellationToken cancellationToken) 位置 /_/src/Scripting/Core/ScriptBuilder.cs:行号 93
   在 Microsoft.CodeAnalysis.Scripting.Script`1.GetExecutor(CancellationToken cancellationToken) 位置 /_/src/Scripting/Core/Script.cs:行号 392
   在 Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken) 位置 /_/src/Scripting/Core/Script.cs:行号 492
   在 ConsoleApp1.Program.Main(String[] args) 位置 D:\Projects\CSharp Projects\WpfApp1\ConsoleApp1\Program.cs:行号 15

可以看到,CSharpScript找不到Class1,即使我们加上using ClassLibrary1;结果也是一样。

问题解决

要让CSharpScript能够找到我们的程序集,需要使用CSharpScript.Create方法的options参数传入一个ScriptOptions类型的对象,并且利用ScriptOptions的WithRefrences方法添加程序集引用。

修改后的代码如下:

using System.Reflection;
using ClassLibrary1;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Class1.TestMethod("Call directly");
            var code2 = @"
using ClassLibrary1;
Class1.TestMethod(""Call with scripting"");
";
            var assembly = Assembly.Load("ClassLibrary1");
            //如果当前的AppDomain中没有加载过该程序集,则需要使用Assembly.LoadFrom来加载
            //确保程序集的路径正确,以及相关的依赖项也在正确的位置
            //var assembly = Assembly.LoadFrom("ClassLibrary1.dll");
            var options = ScriptOptions.Default.WithReferences(assembly);
            CSharpScript.Create(code2, options).RunAsync().GetAwaiter().GetResult();
        }
    }
}

运行后的结果如下:

Class1::TestMethod.Call directly
Class1::TestMethod.Call with scripting