Skip to content

yadong-wang/Compiler.ExpressionInString

Repository files navigation

Compiler.ExpressionInString

Compiler 编译时工具集之一。在类型为 string 的字段、属性或方法参数中编写 LINQ Lambda 表达式,并在编译期进行语义校验。

工具集规划:Compiler.ExpressionInString(本包)、Compiler.Sql(后续)等。

安装

dotnet add package Compiler.ExpressionInString

NuGet 包会自动引入 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";
}

编译错误提示(EIS001)

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

About

Compiler 编译时工具集之一。在类型为 string 的字段、属性或方法参数中编写 LINQ Lambda 表达式,并在编译期进行语义校验。

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages