Compiler 编译时工具集之一。在类型为 string 的字段、属性或方法参数中编写 LINQ Lambda 表达式,并在编译期进行语义校验。
工具集规划:
Compiler.ExpressionInString(本包)、Compiler.Sql(后续)等。
dotnet add package Compiler.ExpressionInStringNuGet 包会自动引入 Roslyn Analyzer(Compiler.ExpressionInString.Analyzers)。若在解决方案内以项目引用方式使用,请确保 Analyzer 项目以 OutputItemType=Analyzer 被引用,否则 IDE/编译期校验不会生效。
[ExpressionInString<TReturn>]到[ExpressionInString<T1, ..., T9, TReturn>]共 10 种 Attribute(0~9 个 Lambda 参数 + 1 个返回值类型)- 最后一个泛型参数始终是 返回值类型
- 可应用于 字段、属性、方法参数
- Roslyn Analyzer 编译期校验(诊断码
EIS001) - IDE 成员补全:在表达式字符串内输入
x.时提供属性/方法补全(Visual Studio / Rider) string.ToExpression<...>()运行时解析为Expression<Func<...>>
using System.Linq.Expressions;
using Compiler.ExpressionInString;
public class Student
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
// 方法参数:0 个 Lambda 参数,返回 int
void Query0([ExpressionInString<int>] string expr)
{
Expression<Func<int>> lambda = expr.ToExpression<int>();
}
// 方法参数:1 个 Lambda 参数,返回 bool
void Query1([ExpressionInString<Student, bool>] string expr)
{
Expression<Func<Student, bool>> lambda = expr.ToExpression<Student, bool>();
}
// 方法参数:第二个 Lambda 参数从相邻方法参数推断
void Query2<TArg>(
[ExpressionInString<Student, FromParameter, bool>] string expr,
TArg arg)
{
Expression<Func<Student, TArg, bool>> lambda = expr.ToExpression<Student, TArg, bool>();
}
// 字段 / 属性:FromParameter 从定义该成员的类型的泛型参数按顺序推断
class Repository<TArg>
{
[ExpressionInString<Student, FromParameter, bool>]
public string Filter { get; set; } = "(x, arg) => x.Id > 0";
}Analyzer 会将字符串内的 Lambda 嵌入合成 C# 代码进行编译,并把 Roslyn 报错映射回字符串字面量内部,在 IDE 中以红色波浪线标出。
| 项目 | 说明 |
|---|---|
| 诊断码 | EIS001 |
| 严重级别 | Error(阻止生成) |
| 标题 | Invalid expression string |
| 消息内容 | 转发 Roslyn 编译器原始错误信息(中文/英文取决于 IDE 语言设置) |
仅对字符串字面量进行分析,包括:
- 方法调用中传给带 Attribute 的参数
- 带 Attribute 的字段 / 属性初始化器
- 对带 Attribute 的字段 / 属性的赋值(右侧为字面量)
以下情况不会触发校验:
- 变量、字段、属性间接传入(非字面量)
- 字符串插值(
$"...") - 拼接表达式(
"a" + "b") - 空字符串或仅空白字符
- 能精确定位时,波浪线只覆盖表达式内的出错片段(例如错误的成员名
.Nick),而非整段字符串 - 成员访问错误会尽量包含前导点号,显示为
.Nick而非Nick - 无法映射到字面量内部时,回退为标红整个字符串
无效成员访问 — 属性/字段名不存在:
void Query([ExpressionInString<Student, bool>] string expr)
{
Query("x => x.No > 0");
// ^^^ EIS001: 'Student' does not contain a definition for 'No'
}返回值类型不匹配 — 与 Attribute 最后一个泛型参数不一致:
void Query([ExpressionInString<int>] string expr)
{
Query("() => \"not int\"");
// ^^^^^^^^^^^^^^^ EIS001: cannot convert 'string' to 'int'
}Lambda 参数数量或类型不匹配 — 与 Attribute 声明的参数类型不一致:
void Query([ExpressionInString<Student, bool>] string expr)
{
Query("() => true");
// ^^^^^^^^^^ EIS001: delegate 'Func<Student, bool>' does not take 0 arguments
}FromParameter 推断后的成员访问错误 — 方法参数或类型泛型参数解析后,同样做完整语义检查:
void Query<TArg>([ExpressionInString<Student, FromParameter, bool>] string expr, TArg arg)
{
Query("(x, arg) => x.Id == arg.Id", new { Id = 1 }); // 合法
Query("(x, arg) => x.Id == arg.Missing", new { Id = 1 });
// ^^^^^^^ EIS001: 匿名类型/泛型参数上不存在该成员
}- 编译期(Analyzer):基于 Attribute 声明的类型做语义校验,不执行 Lambda
- 运行时(
ToExpression):使用System.Linq.Dynamic.Core将字符串解析为Expression<Func<...>>
两者使用不同的解析引擎,但语义规则一致。通过 EIS001 校验的表达式,在绝大多数情况下可正常调用 ToExpression;若运行时仍失败,请检查泛型类型参数是否在运行时可解析。
[StringSyntax("CSharp")] 无法用于 C# 表达式高亮。StringSyntaxAttribute 仅内置支持 Json、Regex、Xml 等语法,不包含 C#。
Roslyn 对 C# 嵌入字符串使用独立的 lang 注释,在字符串字面量前添加即可:
// 方法调用
Query1(
/*lang=c#*/
"x => x.Id > 0");
Query2(
/*lang=c#*/
"(x, arg) => x.Id == arg.Id",
new { Id = 0 });
// 字段 / 属性初始化或赋值
[ExpressionInString<Student, bool>]
public string Filter { get; set; } =
/*lang=c#*/
"x => x.Id > 0";| IDE | 支持情况 |
|---|---|
| Visual Studio 2022 | 支持 /*lang=c#*/ / // lang=c#(需较新的 Roslyn 工具链) |
| JetBrains Rider | 支持 /*lang=c#*/;也可在设置中配置 ExpressionInStringAttribute 的注入规则,或使用 LanguageInjectionAttribute |
| VS Code / Cursor | C# 扩展对嵌入语言支持有限,高亮效果因扩展版本而异 |
说明:
lang注释必须写在实际字符串字面量之前(调用点、初始化器、赋值处),写在参数/属性声明上无效- 语法高亮与
EIS001校验相互独立:无lang注释仍可正常报错;有lang注释不会自动启用校验 - 本包提供的
ExpressionInStringCompletionProvider负责成员补全(x.),与语法高亮是互补能力
- Attribute 中不能使用
dynamic,请用FromParameter占位 - 在方法参数上,
FromParameter表示从相邻方法参数推断类型 - 在字段或属性上,
FromParameter表示从定义该成员的类型泛型参数按顺序推断类型 - 未约束的泛型参数上访问成员(如
arg.Id)可能在校验期报错,需添加泛型约束或避免访问该成员
dotnet build Compiler.sln
dotnet test tests/Compiler.ExpressionInString.Tests
dotnet pack src/Compiler.ExpressionInString/Compiler.ExpressionInString.csproj -c Release