Compiler.ExpressionInString
1.0.2
dotnet add package Compiler.ExpressionInString --version 1.0.2
NuGet\Install-Package Compiler.ExpressionInString -Version 1.0.2
<PackageReference Include="Compiler.ExpressionInString" Version="1.0.2" />
<PackageVersion Include="Compiler.ExpressionInString" Version="1.0.2" />
<PackageReference Include="Compiler.ExpressionInString" />
paket add Compiler.ExpressionInString --version 1.0.2
#r "nuget: Compiler.ExpressionInString, 1.0.2"
#:package Compiler.ExpressionInString@1.0.2
#addin nuget:?package=Compiler.ExpressionInString&version=1.0.2
#tool nuget:?package=Compiler.ExpressionInString&version=1.0.2
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/FromParameterN从调用点已绑定的方法形参类型推断(优先使用构造后方法的Parameters[i].Type及已推断的泛型实参替换,必要时回退到实参表达式类型) FromParameter按顺序消费第 1、2、3… 个调用点形参;FromParameterN按索引选取第 N 个- 非顺序选取示例:
[ExpressionInString<Student, FromParameter2, FromParameter1, bool>]表示第 2 个 lambda 参数取自第 2 个形参、第 3 个取自第 1 个 - 在字段或属性上,
FromParameter按顺序从类型泛型参数推断;FromParameterN按索引选取第 N 个类型泛型参数(无调用点形参时的回退语义) - 字段/属性上通过
FromParameter推断出的未约束泛型参数访问成员(如arg.Id)会在校验期报错,需添加泛型约束;Analyzer 会将where约束传播到合成校验代码中
构建与测试
dotnet build Compiler.sln
dotnet test tests/Compiler.ExpressionInString.Tests
dotnet pack src/Compiler.ExpressionInString/Compiler.ExpressionInString.csproj -c Release
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Linq.Dynamic.Core (>= 1.6.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.