我們建立了一個 School 物件,其中包含了教師列表和學生列表。現在,我們需要計算教師平均年齡和學生平均年齡。
//建立物件
School school = new School()
{
Name = "小菜學園",
Teachers = new List<Teacher>()
{
new Teacher() {Name="波老師",Age=26},
new Teacher() {Name="倉老師",Age=28},
new Teacher() {Name="悠老師",Age=30},
},
Students= new List<Student>()
{
new Student() {Name="小趙",Age=22},
new Student() {Name="小錢",Age=23},
new Student() {Name="小孫",Age=24},
},
//這兩個值如何計算?
TeachersAvgAge = "",
StudentsAvgAge = "",
};
如果我們將計算教師平均年齡的公式交給使用者定義,那麼使用者可能會定義一個字串來表示:
Teachers.Sum(Age)/Teachers.Count
或者可以通過lambda來表示:
teachers.Average(teacher => teacher.Age)
此時我們就獲得了字串型別的表示式,如何進行解析呢?
這種方式是使用 Expression 類手動構建表示式,雖然不符合我們的實際需求,但是它是Dynamic.Core底層實現的方式。Expression 類的檔案地址為::https://learn.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression?view=net-6.0
// 建立參數列達式
var teachersParam = Expression.Parameter(typeof(Teacher[]), "teachers");
// 建立變數表示式
var teacherVar = Expression.Variable(typeof(Teacher), "teacher");
// 建立 lambda 表示式
var lambdaExpr = Expression.Lambda<Func<Teacher[], double>>(
Expression.Block(
new[] { teacherVar }, // 定義變數
Expression.Call(
typeof(Enumerable),
"Average",
new[] { typeof(Teacher) },
teachersParam,
Expression.Lambda(
Expression.Property(
teacherVar, // 使用變數
nameof(Teacher.Age)
),
teacherVar // 使用變數
)
)
),
teachersParam
);
// 編譯表示式樹為委託
var func = lambdaExpr.Compile();
var avgAge = func(teachers);
System.Linq.Dynamic.Core 是一個開源庫,它提供了在執行時構建和解析 Lambda 表示式樹的功能。它的原理是使用 C# 語言本身的語法和型別系統來表示表示式,並通過解析和編譯程式碼字串來生成表示式樹。
// 構造 lambda 表示式的字串形式
string exprString = "teachers.Average(teacher => teacher.Age)";
// 解析 lambda 表示式字串,生成表示式樹
var parameter = Expression.Parameter(typeof(Teacher[]), "teachers");
var lambdaExpr = DynamicExpressionParser.ParseLambda(new[] { parameter }, typeof(double), exprString);
// 編譯表示式樹為委託
var func = (Func<Teacher[], double>)lambdaExpr.Compile();
// 計算教師平均年齡
var avgAge = func(teachers);
使用此動態 LINQ 庫,我們可以執行以下操作:
普通的功能此處不贅述,如果感興趣,可以從下文提供檔案地址去尋找使用案例。
可以通過在靜態幫助程式/實用工具類中定義一些其他邏輯來擴充套件動態 LINQ 的分析功能。為了能夠做到這一點,有幾個要求:
[DynamicLinqType]
public static class Utils
{
public static int ParseAsInt(string value)
{
if (value == null)
{
return 0;
}
return int.Parse(value);
}
public static int IncrementMe(this int values)
{
return values + 1;
}
}
此類有兩個簡單的方法:
當輸入字串為 null 時返回整數值 0,否則將字串解析為整數
使用擴充套件方法遞增整數值
用法:
var query = new [] { new { Value = (string) null }, new { Value = "100" } }.AsQueryable();
var result = query.Select("Utils.ParseAsInt(Value)");
除了以上新增[DynamicLinqType]屬性這樣的方法,我們還可以在設定中新增。
public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
{
public override HashSet<Type> GetCustomTypes() =>
new[] { typeof(Utils)}.ToHashSet();
}
System.Linq.Dynamic.Core中 DynamicExpressionParser 和 ExpressionParser 都是用於解析字串表示式並生成 Lambda 表示式樹的類,但它們之間有一些不同之處。
ExpressionParser 類支援解析任何合法的 C# 表示式,並生成對應的表示式樹。這意味著您可以在表示式中使用各種運運算元、方法呼叫、屬性存取等特性。
DynamicExpressionParser 類則更加靈活和通用。它支援解析任何語言的表示式,包括動態語言和自定義 DSL(領域特定語言)
我們先看ExpressionParser這個類,它用於解析字串表示式並生成 Lambda 表示式樹。
我只抽取重要的和自己感興趣的屬性和方法。
public class ExpressionParser
{
//字串解析器的設定,比如區分大小寫、是否自動解析型別、自定義型別解析器等
private readonly ParsingConfig _parsingConfig;
//查詢指定型別中的方法資訊,通過反射獲取MethodInfo
private readonly MethodFinder _methodFinder;
//用於幫助解析器識別關鍵字、操作符和常數值
private readonly IKeywordsHelper _keywordsHelper;
//解析字串表示式中的文字,用於從字串中讀取字元、單詞、數位等
private readonly TextParser _textParser;
//解析字串表示式中的數位,用於將字串轉換為各種數位型別
private readonly NumberParser _numberParser;
//用於幫助生成和操作表示式樹
private readonly IExpressionHelper _expressionHelper;
//用於查詢指定名稱的型別資訊
private readonly ITypeFinder _typeFinder;
//用於建立型別轉換器
private readonly ITypeConverterFactory _typeConverterFactory;
//用於儲存解析器內部使用的變數和選項。這些變數和選項不應該由外部程式碼存取或修改
private readonly Dictionary<string, object> _internals = new();
//用於儲存字串表示式中使用的符號和值。例如,如果表示式包含 @0 預留位置,則可以使用 _symbols["@0"] 存取其值。
private readonly Dictionary<string, object?> _symbols;
//表示外部傳入的引數和變數。如果表示式需要參照外部的引數或變數,則應該將它們新增到 _externals 中。
private IDictionary<string, object>? _externals;
/// <summary>
/// 使用TextParser將字串解析為指定的結果型別.
/// </summary>
/// <param name="resultType"></param>
/// <param name="createParameterCtor">是否建立帶有相同名稱的建構函式</param>
/// <returns>Expression</returns>
public Expression Parse(Type? resultType, bool createParameterCtor = true)
{
_resultType = resultType;
_createParameterCtor = createParameterCtor;
int exprPos = _textParser.CurrentToken.Pos;
//解析條件運運算元表示式
Expression? expr = ParseConditionalOperator();
//將返回的表示式提升為指定型別
if (resultType != null)
{
if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null)
{
throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType));
}
}
//驗證最後一個標記是否為 TokenId.End,否則丟擲語法錯誤異常
_textParser.ValidateToken(TokenId.End, Res.SyntaxError);
return expr;
}
// ?: operator
private Expression ParseConditionalOperator()
{
int errorPos = _textParser.CurrentToken.Pos;
Expression expr = ParseNullCoalescingOperator();
if (_textParser.CurrentToken.Id == TokenId.Question)
{
......
}
return expr;
}
// ?? (null-coalescing) operator
private Expression ParseNullCoalescingOperator()
{
Expression expr = ParseLambdaOperator();
......
return expr;
}
// => operator - Added Support for projection operator
private Expression ParseLambdaOperator()
{
Expression expr = ParseOrOperator();
......
return expr;
}
}