Roslyn语法的模式匹配之EasySyntax增加模式匹配支持

发布时间:2026/7/5 3:26:23
Roslyn语法的模式匹配之EasySyntax增加模式匹配支持 一、先看一个模式匹配的Case该Case是一个使用模式匹配检测回文算法,只有一行代码这是不是你见过最简洁的检测回文代码但是这里面用到了不少模式匹配有逻辑模式、列表模式、var模式和切片模式模式匹配不仅仅是语法糖,在.net中有很高的地位所以SourceGenerator非常有必要支持生成模式匹配的代码为此开源项目EasySyntax全面增加模式匹配的支持public static bool IsPalindrome(ReadOnlySpanchar input) input is not [var first, ..var middle, var last] || (first last IsPalindrome(middle));二、 模式匹配的主要类型1. 类型模式检查表达式的运行时类型2. 声明模式检查表达式的运行时类型如果匹配成功请将表达式结果分配给声明的变量3. 常量模式测试表达式结果是否等于指定的常量4. 关系模式将表达式结果与指定的常量进行比较5. var模式匹配任何表达式并将其结果分配给声明的变量6. 丢弃模式也叫弃元模式匹配任何表达式7. 逻辑模式测试表达式是否与模式的逻辑组合匹配通过not、or及and进行组合8. 带括号模式可在任何模式两边加上括号用来强调或更改 逻辑模式中的优先级9. 列表模式测试元素序列是否与相应的嵌套模式匹配用于数组和集合类型10. 切片模式切片模式只能显示在列表模式中,不能单独使用切片模匹配一个子列表切片模式中可以嵌套子模式11. 属性模式测试表达式的属性或字段是否与嵌套模式匹配12. 位置模式解构表达式结果并测试结果是否与嵌套模式匹配用于元组、record类型或其他定义了Deconstruct的类型其中逻辑模式、列表模式、属性模式和位置模式是可嵌套其他模式的复杂模式三、 在 C# 中以下场景支持模式匹配is 表达式switch 语句switch 表达式四、简单的模式匹配1. 类型模式1.1 类型模式的Caseif(fruit is Apple) return I like Apple!;1.2 EasySyntax语法实现使用IsType扩展方法var fruit SyntaxFactory.IdentifierName(fruit); var appleType SyntaxFactory.IdentifierName(Apple); var statement fruit.IsType(appleType) .If() .Add(SyntaxGenerator.Literal(I like Apple!).Return()) .Build();1.3 Roslyn原始语法使用SyntaxFactory.TypePattern构造类型模式var fruit SyntaxFactory.IdentifierName(fruit); var appleType SyntaxFactory.IdentifierName(Apple); var statement SyntaxFactory.IfStatement( SyntaxFactory.IsPatternExpression(fruit, SyntaxFactory.TypePattern(appleType)), SyntaxFactory.ReturnStatement(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(I like Apple!))));1.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#declaration-and-type-patterns2. 声明模式2.1 声明模式的Caseif (fruit is Apple apple) MakeApplePie(apple);2.2 EasySyntax 语法实现使用VariablePattern扩展方法构造声明模式var fruit SyntaxFactory.IdentifierName(fruit); var appleType SyntaxFactory.IdentifierName(Apple); var apple SyntaxFactory.IdentifierName(apple); var makeApplePieMethod SyntaxFactory.IdentifierName(MakeApplePie); var statement fruit.Is(appleType.VariablePattern(apple.Identifier)) .If() .AddPatter(makeApplePieMethod.Invocation([apple])) .Build();2.3 Roslyn原始语法使用SyntaxFactory.DeclarationPattern构造声明模式var fruit SyntaxFactory.IdentifierName(fruit); var appleType SyntaxFactory.IdentifierName(Apple); var apple SyntaxFactory.IdentifierName(apple); var makeApplePieMethod SyntaxFactory.IdentifierName(MakeApplePie); var statement SyntaxFactory.IfStatement( SyntaxFactory.IsPatternExpression(fruit, SyntaxFactory.DeclarationPattern(appleType, SyntaxFactory.SingleVariableDesignation(apple.Identifier))), SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression(makeApplePieMethod, SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(apple))))));2.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#declaration-and-type-patterns3. 常量模式3.1 常量模式的Caseobj is null3.2 EasySyntax 语法实现使用ToPattern扩展方法把表达式转化为常量模式var obj SyntaxFactory.IdentifierName(obj); var expression obj.Is(SyntaxGenerator.NullLiteral.ToPattern();3.3 Roslyn原始语法使用SyntaxFactory.ConstantPattern把表达式转化为常量模式var obj SyntaxFactory.IdentifierName(obj); var expression SyntaxFactory.IsPatternExpression(obj, SyntaxFactory.ConstantPattern( SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)));3.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#constant-pattern4. 关系模式4.1 关系模式的Case! 34.2 EasySyntax 语法实现GreaterThanPattern扩展方法GreaterOrEqualPattern扩展方法LessThanPattern扩展方法LessOrEqualPattern扩展方法EqualPattern扩展方法NotEqualPattern扩展方法var pattern SyntaxGenerator.NotEqualPattern(3);4.3 Roslyn原始语法使用SyntaxFactory.RelationalPattern构造关系模式var pattern SyntaxFactory.RelationalPattern( SyntaxFactory.Token(SyntaxKind.ExclamationEqualsToken), SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(3)));4.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#relational-patterns5. var模式5.1 var模式的CaseGetScores(id) is var scores scores.Average() 605.2 EasySyntax 语法实现使用SyntaxGenerator.VarPattern构造var模式var getScoresMethod SyntaxFactory.IdentifierName(GetScores); var scores SyntaxFactory.IdentifierName(scores); var id SyntaxFactory.IdentifierName(id); var expression getScoresMethod.Invocation([id]) .Is(SyntaxGenerator.VarPattern(scores.Identifier)) .LogicalAnd(scores.Access(Average).Invocation().GreaterOrEqual(SyntaxGenerator.Literal(60)));5.3 Roslyn原始语法使用SyntaxFactory.VarPattern构造var模式var getScoresMethod SyntaxFactory.IdentifierName(GetScores); var scores SyntaxFactory.IdentifierName(scores); var id SyntaxFactory.IdentifierName(id); var expression0 SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, SyntaxFactory.IsPatternExpression( SyntaxFactory.InvocationExpression(getScoresMethod, SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList( SyntaxFactory.Argument(id)))), SyntaxFactory.VarPattern(SyntaxFactory.SingleVariableDesignation(scores.Identifier))), SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, scores, SyntaxFactory.IdentifierName(Average))), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(60))));5.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#var-pattern6. 丢弃模式丢弃模式常用于switch表达式也可用于列表和位置模式6.1 丢弃模式的Casedate.Day switch { 15 今夜是月圆之夜, _ 月圆要等到十五 }6.2 Roslyn原始语法使用SyntaxFactory.DiscardPattern()声明丢弃模式以下case结合了EasySyntax的SwitchExpression语法var date SyntaxFactory.IdentifierName(date); var expression date.Access(Day) .SwitchExpression() .Case(SyntaxGenerator.Literal(15), SyntaxGenerator.Literal(今夜是月圆之夜)) .Case(SyntaxFactory.DiscardPattern(), SyntaxGenerator.Literal(月圆要等到十五)) .Switch.Build();6.3 EasySyntax语法可以进一步简化SwitchExpression中的Default封装了SyntaxFactory.DiscardPattern()Default是最后一个分支,可以安全的直接Buildvar date SyntaxFactory.IdentifierName(date); var expression date.Access(Day) .SwitchExpression() .Case(SyntaxGenerator.Literal(15), SyntaxGenerator.Literal(今夜是月圆之夜)) .Default(SyntaxGenerator.Literal(月圆要等到十五)) .Build();6.4 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#discard-pattern五、逻辑模式逻辑模式嵌套了其他模式逻辑模式通过and、or及not组装其他模式1. 逻辑模式的Case该Case通过and组装了1个常量模式和1个关系模式0 and 102. EasySyntax 语法实现And扩展方法Or扩展方法Not扩展方法var pattern SyntaxGenerator.GreaterThanPattern(0) .And(SyntaxGenerator.LessThanPattern(10));3. Roslyn原始语法使用SyntaxFactory.BinaryPattern构造逻辑模式var pattern SyntaxFactory.BinaryPattern(SyntaxKind.AndPattern, SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.GreaterThanToken), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0))), SyntaxFactory.Token(SyntaxKind.AndKeyword), SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.LessThanToken), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(10))));4. 参考官方阅读https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns#logical-patterns六、带括号模式可在任何模式两边加上括号用来强调或更改逻辑模式中的优先级1 带括号模式的Case该Case通过括号确保2个类型模式组成的or逻辑模式是一个整体input is not (float or double)2 EasySyntax 语法实现使用Parenthesized扩展方法转化为带括号模式var input SyntaxFactory.IdentifierName(input); var pattern SyntaxFactory.TypePattern(SyntaxGenerator.FloatType) .Or(SyntaxFactory.TypePattern(SyntaxGenerator.DoubleType)) .Parenthesized() .Not(); var expression input.Is(pattern);3 Roslyn原始语法使用SyntaxFactory.ParenthesizedPattern构造带括号模式var input SyntaxFactory.IdentifierName(input); var pattern SyntaxFactory.UnaryPattern( SyntaxFactory.ParenthesizedPattern( SyntaxFactory.BinaryPattern( SyntaxKind.OrPattern, SyntaxFactory.TypePattern( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword))), SyntaxFactory.Token(SyntaxKind.OrKeyword), SyntaxFactory.TypePattern( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)))))); var expression SyntaxFactory.IsPatternExpression(input, pattern);4. 参考官方阅读模式 - 使用 is 和 switch 表达式进行模式匹配。 - C# reference | Microsoft Learn七、列表模式测试元素序列是否与相应的嵌套模式匹配用于字符串、数组和集合类型1.列表模式的Case该Case由3个模式组成前两个是常量模式第三个是丢弃模式name is [曾, 国, _ ]2.EasySyntax语法实现使用ListPatternBuilder组装多个模式使用Add方法增加了曾和国两个常量模式再使用Add方法增加了一个丢弃模式var name SyntaxFactory.IdentifierName(name); var pattern new ListPatternBuilder() .Add(SyntaxGenerator.Literal(曾)) .Add(SyntaxGenerator.Literal(国)) .Add(SyntaxFactory.DiscardPattern()) .Build(); var expression name.Is(pattern);3.Roslyn原始语法使用SyntaxFactory.ListPattern构造列表模式var name SyntaxFactory.IdentifierName(name); var pattern SyntaxFactory.ListPattern(SyntaxFactory.SeparatedListPatternSyntax([ SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.CharacterLiteralExpression, SyntaxFactory.Literal(曾))), SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.CharacterLiteralExpression, SyntaxFactory.Literal(国))), SyntaxFactory.DiscardPattern()])); var expression name.Is(pattern);4. 参考官方阅读模式 - 使用 is 和 switch 表达式进行模式匹配。 - C# reference | Microsoft Learn八、切片模式切片模式只能显示在列表模式中,不能单独使用切片模匹配一个子列表切片模式中可以嵌套子模式1.切片模式的Case其中.. var middle就是切片模式该Case定义了一个使用切片模式和列表模式实现的回文算法通过模式提取首字母、尾字母和中间切片如果首尾字母相同再用中间切片递归调用该算法非常简洁高效static bool IsPalindrome(ReadOnlySpanchar input) input is not [var first, .. var middle, var last] || (first last IsPalindrome(middle));2.EasySyntax语法实现使用Slice扩展方法定义切片模式用于把原模式转化为切片模式使用Add方法增加了first和last的var模式使用Add方法增加了middle的切片模式var input SyntaxFactory.IdentifierName(input); var inputType SyntaxGenerator.Generic(ReadOnlySpan, SyntaxGenerator.CharType); var isPalindromeMethod SyntaxFactory.IdentifierName(IsPalindrome); var first SyntaxFactory.IdentifierName(first); var middle SyntaxFactory.IdentifierName(middle); var last SyntaxFactory.IdentifierName(last); var middleSlice SyntaxGenerator.VarPattern(middle.Identifier) .Slice(); var pattern new ListPatternBuilder() .Add(SyntaxGenerator.VarPattern(first.Identifier)) .Add(middleSlice) .Add(SyntaxGenerator.VarPattern(last.Identifier)) .Build(); var expression1 input.Is(pattern.Not()); var expression2 first.Equal(last).LogicalAnd(isPalindromeMethod.Invocation([middle])); var body expression1.LogicalOr(expression2.Parenthesized()); var method SyntaxGenerator.BoolType.Method(isPalindromeMethod.Identifier, inputType.Parameter(input.Identifier)) .Static() .WithExpressionBody(body);3.Roslyn原始语法实现切片模式使用SyntaxFactory.SlicePattern构造切片模式以下代码等效EasySyntax语法的SyntaxGenerator.VarPattern(middle.Identifier).Slice()SyntaxFactory.SlicePattern(SyntaxGenerator.VarPattern(middle.Identifier));4. 参考官方阅读模式 - 使用 is 和 switch 表达式进行模式匹配。 - C# reference | Microsoft Learn九、属性模式由属性名(或字段名)及其模式组成可以同时定义多个属性(或字段)1.属性模式的Case该Case包含了两个属性Month使用常量模式Day使用关系模式{ Month: 10, Day: 7 }2.EasySyntax语法实现使用PropertyPatternBuilder组装多个属性(或字段)使用Add方法增加了属性Month的常量模式使用Add方法增加了属性Day的关系模式var nationalDays new PropertyPatternBuilder(null) .Add(Month, SyntaxGenerator.Literal(10)) .Add(Day, SyntaxGenerator.LessOrEqualPattern(7)) .Build();3.Roslyn原始语法使用SyntaxFactory.RecursivePattern构造属性模式var nationalDays SyntaxFactory.RecursivePattern( null, null, SyntaxFactory.PropertyPatternClause(SyntaxFactory.SeparatedList([ SyntaxFactory.Subpattern( SyntaxFactory.NameColon(Month), SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(10)))), SyntaxFactory.Subpattern( SyntaxFactory.NameColon(Day), SyntaxFactory.RelationalPattern( SyntaxFactory.Token(SyntaxKind.LessThanEqualsToken), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(7))))])), null);4. 属性模式可选参数可以定义一个可选的类型参数,相当于类型模式还可以定义一个可选的变量名,相当于声明模式4.1 EasySyntax的Case该case的类型参数为Food该case的变量名为food使用Add方法增加了属性ExpirationDate的关系模式var thing SyntaxFactory.IdentifierName(thing); var food SyntaxFactory.IdentifierName(food); var foodType SyntaxFactory.IdentifierName(Food); var now SyntaxGenerator.DateTimeType.Access(Now); var pattern new PropertyPatternBuilder(foodType, food.Identifier) .Add(ExpirationDate, SyntaxGenerator.GreaterThanPattern(now)) .Build(); var method foodType.Nullable().Method(FindFoot, SyntaxGenerator.ObjectType.Parameter(thing.Identifier)) .ToBuilder() .If(thing.Is(pattern)) .Return(food) .Return(SyntaxGenerator.NullLiteral);4.2 该Case生成以下代码该Case描述一个查找食物的场景先确认该东西是否为食物再确认是否在保质期内Food? FindFoot(object thing) { if (thing is Food { ExpirationDate: DateTime.Now } food) return food; return null; }5. 参考官方阅读模式 - 使用 is 和 switch 表达式进行模式匹配。 - C# reference | Microsoft Learn十、位置模式解构表达式结果并测试结果是否与嵌套模式匹配用于元组、record类型或其他定义了Deconstruct的类型位置模式功能很强大也比较复杂1.位置模式的简单Casepoint is (0, 0)2.EasySyntax语法实现使用PositionalPatternBuilder组装参数使用Add方法增加了2个参数的关系模式var point SyntaxFactory.IdentifierName(point); var pattern new PositionalPatternBuilder(null) .Add(SyntaxGenerator.Literal(0)) .Add(SyntaxGenerator.Literal(0)) .Build(); var isOrigin point.Is(pattern);3.Roslyn原始语法使用SyntaxFactory.RecursivePattern构造位置模式var point SyntaxFactory.IdentifierName(point); var pattern SyntaxFactory.RecursivePattern(null, SyntaxFactory.PositionalPatternClause(SyntaxFactory.SeparatedList([ SyntaxFactory.Subpattern( SyntaxFactory.ConstantPattern( SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)))), SyntaxFactory.Subpattern( SyntaxFactory.ConstantPattern( SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0))))])), null, null); var isOrigin SyntaxFactory.IsPatternExpression(point, pattern0);4. 位置模式可选参数可以定义一个可选的type(类型参数),相当于类型模式还可以定义一个可选的name(变量名),相当于声明模式4.1 EasySyntax的Case该case的类型参数为Point2D和Point3D该case的变量名为ppattern1是二维点,使用Add方法增加了2个参数的关系模式pattern2是三维点,使用Add方法增加了3个参数的关系模式最后用Default定义丢弃模式var point2DType SyntaxFactory.IdentifierName(Point2D); var point3DType SyntaxFactory.IdentifierName(Point3D); var point SyntaxFactory.IdentifierName(point); var p SyntaxFactory.IdentifierName(p); var pattern1 new PositionalPatternBuilder(point2DType, p.Identifier) .Add(SyntaxGenerator.GreaterThanPattern(0)) .Add(SyntaxGenerator.GreaterThanPattern(0)) .Build(); var pattern2 new PositionalPatternBuilder(point3DType, p.Identifier) .Add(SyntaxGenerator.GreaterThanPattern(0)) .Add(SyntaxGenerator.GreaterThanPattern(0)) .Add(SyntaxGenerator.GreaterThanPattern(0)) .Build(); var body point.SwitchExpression() .Case(pattern1, p.Access(ToString).Invocation()) .Case(pattern2, p.Access(ToString).Invocation()) .Default(SyntaxGenerator.StringType.Access(Empty)) .Build(); var method SyntaxGenerator.StringType.Method(PrintIfAllCoordinatesArePositive, SyntaxGenerator.ObjectType.Parameter(point.Identifier)) .WithExpressionBody(body);4.2 该Case生成以下代码如果是二维的点且每个坐标值都大于0或者是三维的点且每个坐标值都大于0否则返回空string PrintIfAllCoordinatesArePositive(object point) point switch { Point2D( 0, 0) p p.ToString(), Point3D( 0, 0, 0) p p.ToString(), _ string.Empty };5. 位置模式支持属性名增加属性名能提高代码的可读性5.1 EasySyntax的Case使用NamedPositionalPatternBuilder来支持属性名NamedPositionalPatternBuilder也支持type和name可选参数使用NamedPositionalPatternBuilder也可以轻松重写4.的Case,限与篇幅就不举例了使用Add方法增加X和Y两个参数的常量模式var point SyntaxFactory.IdentifierName(point); var pattern new NamedPositionalPatternBuilder(null) .Add(X, SyntaxGenerator.Literal(0)) .Add(Y, SyntaxGenerator.Literal(0)) .Build(); var isOrigin point.Is(pattern);5.2 该Case生成以下代码增加属性名X和Y使得代码可读性明显的提高point is (X: 0, Y: 0)6. 位置模式还可以附加属性模式不是嵌套,是属性模式作为位置模式的可选参数6.1 例如以下代码WeightedPoint是record,支持解构,另外还有一个属性WeightWeight属性不能通过解构函数提取这种情况需要特殊的位置模式来匹配public record WeightedPoint(int X, int Y) { public int Weight { get; set; } }6.2 EasySyntax的Case使用RecursivePatternBuilder来定义含属性的位置模式RecursivePatternBuilder也支持type和name可选参数NamedRecursivePatternBuilder能实现位置参数属性名表示及属性模式,限与篇幅本文不展开使用builder.Positional增加两个位置模式使用builder.Property增加一个属性模式var point SyntaxFactory.IdentifierName(point); var builder new RecursivePatternBuilder(null); builder.Positional.Add(SyntaxGenerator.GreaterOrEqualPattern(0)) .Add(SyntaxGenerator.GreaterOrEqualPattern(0)); builder.Property.Add(Weight, SyntaxGenerator.GreaterThanPattern(0)); var isInDomain point.Is(builder.Build());6.3 该Case生成以下代码point is ( 0, 0) { Weight: 0 }