





















- 该Case是一个使用模式匹配检测回文算法,只有一行代码
- 这是不是你见过最简洁的检测回文代码
- 但是这里面用到了不少模式匹配
- 有逻辑模式、列表模式、var模式和切片模式
- 模式匹配不仅仅是语法糖,在.net中有很高的地位
- 所以SourceGenerator非常有必要支持生成模式匹配的代码
- 为此开源项目EasySyntax全面增加模式匹配的支持
public static bool IsPalindrome(ReadOnlySpan<char> input)
=> input is not [var first, ..var middle, var last] || (first == last && IsPalindrome(middle));
- 检查表达式的运行时类型
- 检查表达式的运行时类型,如果匹配成功,请将表达式结果分配给声明的变量
- 测试表达式结果是否等于指定的常量
- 将表达式结果与指定的常量进行比较
- 匹配任何表达式并将其结果分配给声明的变量
- 匹配任何表达式
- 测试表达式是否与模式的逻辑组合匹配
- 通过not、or、and及括号进行组合
- 可在任何模式两边加上括号
- 用来强调或更改 逻辑模式中的优先级
- 测试元素序列是否与相应的嵌套模式匹配
- 用于数组和集合类型
- 切片模式只能显示在列表模式中,不能单独使用
- 切片模匹配一个子列表
- 切片模式中可以嵌套子模式
- 测试表达式的属性或字段是否与嵌套模式匹配
- 解构表达式结果并测试结果是否与嵌套模式匹配
- 用于元组、record类型或其他定义了Deconstruct的类型
其中逻辑模式、列表模式、属性模式和位置模式是可嵌套其他模式的复杂模式
- is 表达式
- switch 语句
- switch 表达式
if(fruit is Apple)
return "I like Apple!";
- 使用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();
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!"))));
if (fruit is Apple apple)
MakeApplePie(apple);
- 使用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();
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))))));
obj is null
- 使用ToPattern扩展方法把表达式转化为常量模式
var obj = SyntaxFactory.IdentifierName("obj");
var expression = obj.Is(SyntaxGenerator.NullLiteral.ToPattern();
var obj = SyntaxFactory.IdentifierName("obj");
var expression = SyntaxFactory.IsPatternExpression(obj,
SyntaxFactory.ConstantPattern(
SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)));
!= 3
- GreaterThanPattern扩展方法
- GreaterOrEqualPattern扩展方法
- LessThanPattern扩展方法
- LessOrEqualPattern扩展方法
- EqualPattern扩展方法
- NotEqualPattern扩展方法
var pattern = SyntaxGenerator.NotEqualPattern(3);
var pattern = SyntaxFactory.RelationalPattern(
SyntaxFactory.Token(SyntaxKind.ExclamationEqualsToken),
SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(3)));
GetScores(id) is var scores && scores.Average() >= 60
- GreaterThanPattern扩展方法
- GreaterOrEqualPattern扩展方法
- LessThanPattern扩展方法
- LessOrEqualPattern扩展方法
- EqualPattern扩展方法
- NotEqualPattern扩展方法
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)));
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))));
- 丢弃模式常用于switch表达式
- 也可用于列表和位置模式
date.Day switch
{
15 => "今夜是月圆之夜",
_ => "月圆要等到十五"
}
- 使用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();
- SwitchExpression中的Default封装了SyntaxFactory.DiscardPattern()
- Default是最后一个分支,可以安全的直接Build
var date = SyntaxFactory.IdentifierName("date");
var expression = date.Access("Day")
.SwitchExpression()
.Case(SyntaxGenerator.Literal(15), SyntaxGenerator.Literal("今夜是月圆之夜"))
.Default(SyntaxGenerator.Literal("月圆要等到十五"))
.Build();
- 逻辑模式嵌套了其他模式
- 逻辑模式通过and、or和not及括号组装其他模式
- 该Case通过and组装了两个关系模式
0 and < 10
- And扩展方法
- Or扩展方法
- Not扩展方法
var pattern = SyntaxGenerator.GreaterThanPattern(0)
.And(SyntaxGenerator.LessThanPattern(10));
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))));
- 可在任何模式两边加上括号
- 用来强调或更改逻辑模式中的优先级
- 该Case通过and组装了两个关系模式
input is not (float or double)
- 使用Parenthesized扩展方法转化为带括号模式
var input = SyntaxFactory.IdentifierName("input");
var pattern = SyntaxFactory.TypePattern(SyntaxGenerator.FloatType)
.Or(SyntaxFactory.TypePattern(SyntaxGenerator.DoubleType))
.Parenthesized()
.Not();
var expression = input.Is(pattern);
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);
- 测试元素序列是否与相应的嵌套模式匹配
- 用于字符串、数组和集合类型
- 该Case由3个模式组成
- 前两个是常量模式
- 第三个是丢弃模式
name is ['曾', '国', _ ]
- 使用ListPatternBuilder组装多个模式
var name = SyntaxFactory.IdentifierName("name");
var pattern = new ListPatternBuilder()
.Add(SyntaxGenerator.Literal('曾'))
.Add(SyntaxGenerator.Literal('国'))
.Add(SyntaxFactory.DiscardPattern())
.Build();
var expression = name.Is(pattern);
var name = SyntaxFactory.IdentifierName("name");
var pattern = SyntaxFactory.ListPattern(SyntaxFactory.SeparatedList<PatternSyntax>([
SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.CharacterLiteralExpression,
SyntaxFactory.Literal('曾'))),
SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.CharacterLiteralExpression,
SyntaxFactory.Literal('国'))),
SyntaxFactory.DiscardPattern()]));
var expression = name.Is(pattern);
- 切片模式只能显示在列表模式中,不能单独使用
- 切片模匹配一个子列表
- 切片模式中可以嵌套子模式
- 其中.. var middle就是切片模式
- 该Case定义了一个使用切片模式和列表模式实现的回文算法
- 通过模式提取首字母、尾字母和中间切片
- 如果首尾字母相同再用中间切片递归调用该算法
- 非常简洁高效
static bool IsPalindrome(ReadOnlySpan<char> input)
=> input is not [var first, .. var middle, var last] || (first == last && IsPalindrome(middle));
- 使用Slice扩展方法定义切片模式
- 用于把原模式转化为切片模式
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);
- 以下代码等效EasySyntax语法的SyntaxGenerator.VarPattern(middle.Identifier).Slice()
SyntaxFactory.SlicePattern(SyntaxGenerator.VarPattern(middle.Identifier));
- 由属性名(或字段名)及其模式组成
- 可以同时定义多个属性(或字段)
- 该Case包含了两个属性
- Month使用常量模式
- Day使用关系模式
{ Month: 10, Day: <= 7 }
- 使用PropertyPatternBuilder组装多个属性(或字段)
var nationalDays = new PropertyPatternBuilder(null)
.Add("Month", SyntaxGenerator.Literal(10))
.Add("Day", SyntaxGenerator.LessOrEqualPattern(7))
.Build();
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);
- 可以定义一个可选的类型参数,相当于类型模式
- 还可以定义一个可选的变量名,相当于声明模式
- 该case的类型参数为Food
- 该case的变量名为food
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);
- 该Case描述一个查找食物的场景
- 先确认该东西是否为食物
- 再确认是否在保质期内
Food? FindFoot(object thing)
{
if (thing is Food { ExpirationDate: > DateTime.Now } food)
return food;
return null;
}
- 解构表达式结果并测试结果是否与嵌套模式匹配
- 用于元组、record类型或其他定义了Deconstruct的类型
- 位置模式功能很强大也比较复杂
point is (0, 0)
- 使用PropertyPatternBuilder组装多个属性(或字段)
var point = SyntaxFactory.IdentifierName("point");
var pattern = new PositionalPatternBuilder(null)
.Add(SyntaxGenerator.Literal(0))
.Add(SyntaxGenerator.Literal(0))
.Build();
var isOrigin = point.Is(pattern);
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);
- 可以定义一个可选的type(类型参数),相当于类型模式
- 还可以定义一个可选的name(变量名),相当于声明模式
- 该case的类型参数为Point2D和Point3D
- 该case的变量名为p
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);
- 如果是二维的点且每个坐标值都大于0
- 或者是三维的点且每个坐标值都大于0
- 否则返回空
string PrintIfAllCoordinatesArePositive(object point) => point switch
{
Point2D(> 0, > 0) p => p.ToString(),
Point3D(> 0, > 0, > 0) p => p.ToString(),
_ => string.Empty
};
- 增加属性名能提高代码的可读性
- 使用NamedPositionalPatternBuilder来支持属性名
- NamedPositionalPatternBuilder也支持type和name可选参数
- 使用NamedPositionalPatternBuilder也可以轻松重写4.的Case,限与篇幅就不举例了
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);
- 增加属性名X和Y使得代码可读性明显的提高
point is (X: 0, Y: 0)
- 不是嵌套,是属性模式作为位置模式的可选参数
- WeightedPoint是结构体,另外还有一个属性Weight
public record WeightedPoint(int X, int Y)
{
public int Weight { get; set; }
}
- 使用RecursivePatternBuilder来定义含属性模式的位置模式
- RecursivePatternBuilder也支持type和name可选参数
- NamedRecursivePatternBuilder能实现位置参数属性名表示及属性模式,限与篇幅本文不展开
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());
point is (>= 0, >= 0) { Weight: > 0 }
源码托管地址: https://github.com/donetsoftwork/Hand.Generators ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/hand.-generators
如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。