c#动态计算表达式

在 C# 中,虽然没有内置的 eval 函数,但你可以使用 Roslyn 提供的脚本编译和执行功能来动态执行 C# 代码。这提供了一种类似于 eval 的功能。Roslyn 是微软的开源编译器平台,允许你动态地编译和执行 C# 代码。

安装包

在 C# 中,虽然没有内置的 eval 函数,但你可以使用 Roslyn 提供的脚本编译和执行功能来动态执行 C# 代码。这提供了一种类似于 eval 的功能。Roslyn 是微软的开源编译器平台,允许你动态地编译和执行 C# 代码。

你需要安装 Microsoft.CodeAnalysis.CSharp.Scripting NuGet 包来使用这个功能。以下是一个示例,展示如何使用 Roslyn 来动态执行 C# 表达式:

1
dotnet add package Microsoft.CodeAnalysis.CSharp.Scripting

使用 Roslyn 动态执行代码

以下是一个示例,展示如何使用 Roslyn 的脚本编译和执行功能来动态执行 C# 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class Program
{
public static async Task Main(string[] args)
{
string code = @"
int Multiply(int x, int y) => x * y;
Multiply(a, b)
";

var globals = new Globals { a = 3, b = 5 };

var result = await CSharpScript.EvaluateAsync<int>(code, ScriptOptions.Default, globals);

Console.WriteLine(result); // 输出 15
}

public class Globals
{
public int a;
public int b;
}
}

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace Insurance.Api.Common.Helper
{
public class NewCsEvalHelper
{
public static object Evaluate(string text)
{
return CSharpScript.EvaluateAsync(text, ScriptOptions.Default).Result;
}
}
}

自定义 ScriptOptions

ScriptOptions.Default 是 Roslyn 脚本执行功能中的一个静态属性,提供了默认的脚本执行选项。它定义了一些基本配置,用于控制脚本的编译和执行行为。以下是一些默认选项的关键点:

引用的程序集:默认情况下,脚本执行环境会自动引用一些常用的程序集,如 System、System.Core 等。
导入的命名空间:默认情况下,脚本执行环境会自动导入一些常用的命名空间,如 System、System.Linq 等。
其他编译选项:例如,允许调试信息,启用/禁用某些编译器特性等。
不过,有时候你可能需要自定义这些选项,以便满足具体需求。例如,你可能需要添加额外的程序集引用,或者导入额外的命名空间。这时,你可以使用 ScriptOptions.Default 作为基准,并在其基础上进行修改。

示例:自定义 ScriptOptions
以下示例展示了如何自定义 ScriptOptions,以便在脚本中使用额外的程序集和命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class Program
{
public static async Task Main(string[] args)
{
string code = @"
using System;
using System.Collections.Generic;
List<int> numbers = new List<int> { a, b, 10 };
numbers.Sum()
";

var globals = new Globals { a = 3, b = 5 };

var scriptOptions = ScriptOptions.Default
.AddReferences(typeof(System.Linq.Enumerable).Assembly) // 添加 System.Linq 引用
.AddImports("System.Linq"); // 导入 System.Linq 命名空间

var result = await CSharpScript.EvaluateAsync<int>(code, scriptOptions, globals);

Console.WriteLine(result); // 输出 18 (3 + 5 + 10)
}

public class Globals
{
public int a;
public int b;
}
}

使用 ScriptOptions 的更多选项

除了添加引用和导入命名空间,ScriptOptions 还提供了其他一些配置选项:

  • WithEmitDebugInformation:指定是否生成调试信息。
  • WithFilePath:指定脚本文件路径,用于调试信息。
  • WithFileEncoding:指定脚本文件的编码。
  • WithLanguageVersion:指定脚本使用的 C# 语言版本。
    以下是一个更复杂的示例,展示如何使用这些选项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class Program
{
public static async Task Main(string[] args)
{
string code = "a + b";

var globals = new Globals { a = 3, b = 5 };

var scriptOptions = ScriptOptions.Default
.WithEmitDebugInformation(true) // 生成调试信息
.WithFilePath("script.csx") // 指定脚本文件路径
.WithFileEncoding(System.Text.Encoding.UTF8) // 指定文件编码
.WithLanguageVersion(LanguageVersion.CSharp8); // 使用 C# 8.0 版本

var result = await CSharpScript.EvaluateAsync<int>(code, scriptOptions, globals);

Console.WriteLine(result); // 输出 8
}

public class Globals
{
public int a;
public int b;
}
}

通过自定义 ScriptOptions,你可以灵活地控制脚本的编译和执行行为,以满足不同的需求

安全性注意事项

使用动态代码执行功能时,务必注意安全性,尤其是在处理用户输入时。确保执行的代码是可信的,并采取适当的措施来防止代码注入和其他安全漏洞。