CSharp Script

939 阅读2分钟

Roslyn

Roslyn 是微软公司开源的 .NET 编译器。

编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API。

Roslyn不仅仅可以直接编译输出,而且开放了编译的API,使得代码脚本化成为了可能。

关于Roslyn,不做过多介绍,想要了解的各位请移步官方说明地址:github.com/dotnet/rosl…

本文主要介绍的是如何加载C#脚本,并在C#程序中调用并执行脚本

第一个C#脚本

话不多说直接进入正题,实现一个基本的控制台打印脚本。

第一步,安装Microsoft.CodeAnalysis.CSharp.Scripting包。

第二步,在文本Script.txt中准备一段C#脚本

using System;

public void Script1()
{
    Console.WriteLine("Hello C# Script");
}

第三步,创建脚本并执行脚本

using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

var code = File.ReadAllText(@"C:\Users\Palink\Desktop\script.txt");
var script1 = CSharpScript.RunAsync(code, ScriptOptions.Default).Result;
Task.Run(() => script1.ContinueWithAsync("Script1()"));

Console.ReadKey();

控制台输出结果如下: image

引用其他类库

一般的如果我们需要使用其他程序集的类我们需要在脚本配置选项ScriptOptions里添加该程序集的引用。如我们需要使用System.IO.File我么可以这么做。

var scriptOptions = ScriptOptions.Default
    .AddReferences(typeof(System.IO.File).Assembly);

注意:这里使用了AddReferences方法,使用WithReferences会覆盖25个默认的程序集引用。

接下来就只需要在脚本Run的时候传入该配置即可

var script1 = CSharpScript.RunAsync(code, scriptOptions)
    .Result;

我们来回顾下第一个脚本,在调用System名称空间下的Console.WriteLine("Hello C# Script")时似乎没有像使用System.IO.File一样添加引用。这是因为上面的脚本使用了默认的脚本配置选项ScriptOptions.Default,配置选项默认为我们添加了C#常用25个程序集,其中就包含了System.Console。详细的元数据引用可以查看ScriptOptions.DefaultMetadataReferences属性。

好了,我们开始脚本改造工作

using System;
using System.IO;

public void Script1()
{
    var txt = File.ReadAllText(@"C:\Users\Palink\Desktop\script.txt");
    Console.WriteLine(txt);
}

由于引入了File所以我们需要添加File的程序集,改造下C#代码,结果如下

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

var scriptOptions = ScriptOptions.Default
    .AddReferences(typeof(File).Assembly);
var code = File.ReadAllText(@"C:\Users\Palink\Desktop\script.txt");
var script1 = CSharpScript.RunAsync(code, scriptOptions)
    .Result;
Task.Run(() =>
    script1.ContinueWithAsync("Script1()"));

Console.ReadKey();

执行结果如下: image

脚本字段

将上面脚本的文件路径用字段path代替

using System;
using System.IO;

string path;

public void Script1()
{
    var txt = File.ReadAllText(path);
    Console.WriteLine(txt);
}

在c#中对path字段赋值

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

var scriptOptions = ScriptOptions.Default
    .AddReferences(typeof(File).Assembly);
var code = File.ReadAllText(@"C:\Users\Palink\Desktop\script.txt");
var script1 = CSharpScript.RunAsync(code, scriptOptions)
    .Result;

var path = script1.Variables.ItemRef(0);
path.Value = @"C:\Users\Palink\Desktop\script.txt";
script1.Variables.SetItem(0, path);

Task.Run(() =>
    script1.ContinueWithAsync("Script1()"));

Console.ReadKey();

执行结果同上。

注意脚本中不能包含命名空间的定义

优势:通过加载C#脚本的方式去执行脚本代码避免程序重新编译的麻烦。

弊端:加载脚本时间较长、安装包很重很重(超过5M的大小)