C#中的模式匹配
模式匹配是一项允许测试表达式是否出现给定模式的功能。这是一个在函数式语言中更为普遍的特性。模式匹配本质上是布尔值,这意味着有两种可能的结果:表达式与模式匹配或不匹配。此功能最初是在 C# 7.0 中引入的,然后在该语言的后续版本中经历了一系列改进。
模式匹配允许诸如类型检查(类型模式)、空值检查(常量模式)、比较(关系模式)、检查和比较属性值(属性模式)、对象解构(位置模式)和使用变量创建的表达式重用等操作( var模式)使用最小和简洁的语法来表达。此外,这些模式可以嵌套并且可以包含多个子模式。也可以使用模式组合器(和、或和非)组合模式。 C# 允许通过三种结构进行模式匹配:
1.是运算符
在 C# 7.0 之前, is运算符的唯一用途是检查对象是否与特定类型兼容。从 C# 7.0 开始, is运算符已扩展为测试表达式是否与模式匹配。
句法:
expression is pattern
2. switch 语句
就像如何使用 switch 语句通过测试一组值的表达式来执行代码分支(case)一样,它也可以通过测试表达式是否出现一组值来执行代码分支模式。
句法:
switch (expression)
{
case pattern1:
// code to be executed
// if expression matches pattern1
break;
case pattern2:
// code to be executed
// if expression matches pattern2
break;
…
case patternN:
// code to be executed
// if expression matches patternN
break;
default:
// code to be executed if expression
// does not match any of the above patterns
}
3. switch 表达式
还可以使用 switch 表达式测试一组模式,以根据模式是否匹配来选择一个值。
句法:
expression switch
{
pattern1 => value1,
pattern2 => value2,
…
patternN => valueN,
_ => defaultValue
}
C# 支持的模式
从 C# 9 开始,该语言支持以下模式。
- 类型模式
- 关系模式
- 属性模式
- 位置模式
- 变量模式
- 恒定模式
C# 还支持在模式匹配中使用以下结构:
- 变量声明
- 模式组合器( and , or和not )
- 丢弃变量 ( _ )
- 嵌套模式或子模式
类型模式
类型模式可用于检查表达式的运行时类型是否与指定类型匹配或与该类型兼容。如果表达式,即正在匹配的值与模式中指定的类型兼容,则匹配成功。类型模式还可以选择包含变量声明。如果正在测试的值与类型匹配,则它将被强制转换为该类型,然后分配给该变量。模式中的变量声明将进一步描述。
句法:
// Used in C# 9 and above
TypeName
// Used in C# 7
TypeName variable
TypeName _
例子:
C#
// C# program to illustrate the concept of Type Pattern
using System;
public class GFG{
static void PrintUppercaseIfString(object arg)
{
// If arg is a string:
// convert it to a string
// and assign it to variable message
if (arg is string message)
{
Console.WriteLine($"{message.ToUpper()}");
}
else
{
Console.WriteLine($"{arg} is not a string");
}
}
// Driver code
static public void Main()
{
string str = "Geeks For Geeks";
int number = 42;
object o1 = str;
object o2 = number;
PrintUppercaseIfString(o1);
PrintUppercaseIfString(o2);
}
}
C#
// C# program to illustrate the concept of Type Pattern Switch
using static System.Console;
// Allows using WriteLine without Console. prefix
public class Person
{
public string Name
{
get;
set;
}
}
class GFG{
static void PrintType(object obj)
{
switch (obj)
{
case Person p:
WriteLine("obj is a Person");
WriteLine($"Name of the person: {p.Name}");
break;
case int i:
WriteLine("obj is an int");
WriteLine($"Value of the int: {i}");
break;
case double d:
WriteLine("obj is a double");
WriteLine($"Value of the double: {d}");
break;
default:
WriteLine("obj is some other type");
break;
}
WriteLine(); // New line
}
// Driver code
static public void Main()
{
var person = new Person { Name = "Geek" };
PrintType(42);
PrintType(person);
PrintType(3.14);
PrintType("Hello");
}
}
C#
// Program to check if a number is positive,
// negative or zero using relational patterns
// using a switch statement
using System;
class GFG{
public static string GetNumberSign(int number)
{
switch (number)
{
case < 0:
return "Negative";
case 0:
return "Zero";
case >= 1:
return "Positive";
}
}
// Driver code
static public void Main()
{
int n1 = 0;
int n2 = -31;
int n3 = 18;
Console.WriteLine(GetNumberSign(n1));
Console.WriteLine(GetNumberSign(n2));
Console.WriteLine(GetNumberSign(n3));
}
}
C#
// Program to check if a number
// is positive, negative or zero
// using relational patterns
// with a switch expression
using System;
class GFG{
public static string GetNumberSign(int number)
{
return number switch
{
< 0 => "Negative",
0 => "Zero",
>= -1 => "Positive"
};
}
// Driver code
static public void Main()
{
int n1 = 0;
int n2 = -31;
int n3 = 18;
Console.WriteLine(GetNumberSign(n1));
Console.WriteLine(GetNumberSign(n2));
Console.WriteLine(GetNumberSign(n3));
}
}
C#
// C# program to illustrate the concept of Property Pattern
using System;
class GFG{
public static void DescribeStringLength(string str)
{
// Constant pattern, discussed further
if (str is null)
{
Console.WriteLine("Null string");
}
if (str is { Length: 0 })
{
Console.WriteLine("Empty string");
return;
}
if (str is { Length: 1 })
{
Console.WriteLine("String of length 1");
return;
}
Console.WriteLine("Length greater than 1");
return;
}
// Driver code
static public void Main()
{
DescribeStringLength("Hello!");
Console.WriteLine();
DescribeStringLength("");
Console.WriteLine();
DescribeStringLength("X");
Console.WriteLine();
}
}
C#
// C# program to illustrate the concept of Positional Pattern
using System;
// Represents two inputs to the truth table
public struct BooleanInput
{
public bool Input1
{
get;
set;
}
public bool Input2
{
get;
set;
}
public void Deconstruct(out bool input1,
out bool input2)
{
input1 = Input1;
input2 = Input2;
}
}
class GFG{
// Performs logical AND on an input object
public static bool LogicalAnd(BooleanInput input)
{
// Using switch expression
return input switch
{
(false, false) => false,
(true, false) => false,
(false, true) => false,
(true, true) => true
};
}
// Performs logical OR on an input object
public static bool LogicalOr(BooleanInput input)
{
// Using switch statement
switch (input)
{
case (false, false):
return false;
case (true, false):
return true;
case (false, true):
return true;
case (true, true):
return true;
}
}
// Driver code
static public void Main()
{
var a = new BooleanInput{Input1 = true,
Input2 = false};
var b = new BooleanInput{Input1 = true,
Input2 = true};
Console.WriteLine("Logical AND:");
Console.WriteLine(LogicalAnd(a));
Console.WriteLine(LogicalAnd(b));
Console.WriteLine("Logical OR:");
Console.WriteLine(LogicalOr(a));
Console.WriteLine(LogicalOr(b));
}
}
C#
// C# program to illustrate the concept of Positional Pattern
using System;
class GFG{
// Displays the location of a point
// by accepting its x and y coordinates
public static void LocatePoint(int x, int y)
{
Console.WriteLine($"Point ({x}, {y}):");
// Using switch statement
// Note the double parantheses
switch ((x, y))
{
case (0, 0):
Console.WriteLine("Point at origin");
break;
case (0, _): // _ will match all values for y
Console.WriteLine("Point on Y axis");
break;
case (_, 0):
Console.WriteLine("Point on X axis");
break;
default:
Console.WriteLine("Point elsewhere");
break;
}
}
// Driver code
static public void Main()
{
LocatePoint(10, 20);
LocatePoint(10, 0);
LocatePoint(0, 20);
LocatePoint(0, 0);
}
}
C#
// C# program to illustrate the concept of Constant Pattern
using System;
class GFG{
// Returns the name of the day of the week
public static string DayOfTheWeek(int day)
{
// Switch expression
return day switch
{
1 => "Sunday",
2 => "Monday",
3 => "Tuesday",
4 => "Wednesday",
5 => "Thursday",
6 => "Friday",
7 => "Saturday",
_ => "Invalid week day"
};
}
// Driver code
static public void Main()
{
Console.WriteLine(DayOfTheWeek(5));
Console.WriteLine(DayOfTheWeek(3));
}
}
C#
// C# program to illustrate the concept of Pattern Combinators
using System;
class GFG{
public static bool IsVowel(char c)
{
return char.ToLower(c) is 'a' or 'e' or 'i' or 'o' or 'u';
}
// Driver code
public static void Main()
{
Console.WriteLine(IsVowel('A'));
Console.WriteLine(IsVowel('B'));
Console.WriteLine(IsVowel('e'));
Console.WriteLine(IsVowel('x'));
Console.WriteLine(IsVowel('O'));
Console.WriteLine(IsVowel('P'));
}
}
GEEKS FOR GEEKS
42 is not a string
在上面的示例中, PrintUppercaseIfString()方法接受名为arg 的对象类型参数。 C# 中的任何类型都可以向上强制转换为对象,因为在 C# 中,所有类型都从对象派生。这称为类型统一。
自动铸造
如果arg是字符串,它将从对象向下转换为字符串,并将分配给名为message的变量。如果arg不是字符串而是不同类型,则将执行else块。因此,类型检查和强制转换都结合在一个表达式中。如果类型不匹配,则不会创建变量。
使用类型模式打开类型
与switch语句一起使用的类型模式可以帮助根据值的类型选择代码分支( case分支)。下面的代码定义了一个名为PrintType()的方法,它接受一个参数作为对象,然后为不同类型的参数打印不同的消息:
C#
// C# program to illustrate the concept of Type Pattern Switch
using static System.Console;
// Allows using WriteLine without Console. prefix
public class Person
{
public string Name
{
get;
set;
}
}
class GFG{
static void PrintType(object obj)
{
switch (obj)
{
case Person p:
WriteLine("obj is a Person");
WriteLine($"Name of the person: {p.Name}");
break;
case int i:
WriteLine("obj is an int");
WriteLine($"Value of the int: {i}");
break;
case double d:
WriteLine("obj is a double");
WriteLine($"Value of the double: {d}");
break;
default:
WriteLine("obj is some other type");
break;
}
WriteLine(); // New line
}
// Driver code
static public void Main()
{
var person = new Person { Name = "Geek" };
PrintType(42);
PrintType(person);
PrintType(3.14);
PrintType("Hello");
}
}
输出:
obj is an int
Value of the int: 42
obj is a Person
Name of the person: Geek
obj is a double
Value of the double: 3.14
obj is some other type
关系模式
C# 9 中引入了关系模式。它们帮助我们使用以下方法对值进行比较:<(小于)、<=(小于或等于)、>(大于)和 >=(大于或等于) )运算符。
句法:
< constant
<= constant
> constant
>= constant
例子:
C#
// Program to check if a number is positive,
// negative or zero using relational patterns
// using a switch statement
using System;
class GFG{
public static string GetNumberSign(int number)
{
switch (number)
{
case < 0:
return "Negative";
case 0:
return "Zero";
case >= 1:
return "Positive";
}
}
// Driver code
static public void Main()
{
int n1 = 0;
int n2 = -31;
int n3 = 18;
Console.WriteLine(GetNumberSign(n1));
Console.WriteLine(GetNumberSign(n2));
Console.WriteLine(GetNumberSign(n3));
}
}
输出:
Zero
Negative
Positive
可以使用 switch 表达式更简洁地编写上面的示例:
C#
// Program to check if a number
// is positive, negative or zero
// using relational patterns
// with a switch expression
using System;
class GFG{
public static string GetNumberSign(int number)
{
return number switch
{
< 0 => "Negative",
0 => "Zero",
>= -1 => "Positive"
};
}
// Driver code
static public void Main()
{
int n1 = 0;
int n2 = -31;
int n3 = 18;
Console.WriteLine(GetNumberSign(n1));
Console.WriteLine(GetNumberSign(n2));
Console.WriteLine(GetNumberSign(n3));
}
}
输出:
Zero
Negative
Positive
同样,关系模式也可以与is运算符一起使用:
int n = 2;
Console.WriteLine(n is <= 10); // Prints true
Console.WriteLine(n is > 5); // Prints false
这本身可能没有那么有用,因为n is <= 10与写入n <= 10 相同。然而,这种语法对于模式组合器会更方便(进一步讨论)。
属性模式
属性模式允许匹配对象上定义的属性值。所述图案指定属性的名称进行匹配,然后冒号后(:)的值,该值必须匹配。可以通过用逗号分隔来指定多个属性及其值。
句法:
{ Property1: value1, Property2 : value2, …, PropertyN: valueN }
这样的语法允许我们写:
“Geeks” is { Length: 4 }
代替:
“Geeks”.Length == 4
例子:
C#
// C# program to illustrate the concept of Property Pattern
using System;
class GFG{
public static void DescribeStringLength(string str)
{
// Constant pattern, discussed further
if (str is null)
{
Console.WriteLine("Null string");
}
if (str is { Length: 0 })
{
Console.WriteLine("Empty string");
return;
}
if (str is { Length: 1 })
{
Console.WriteLine("String of length 1");
return;
}
Console.WriteLine("Length greater than 1");
return;
}
// Driver code
static public void Main()
{
DescribeStringLength("Hello!");
Console.WriteLine();
DescribeStringLength("");
Console.WriteLine();
DescribeStringLength("X");
Console.WriteLine();
}
}
输出:
Length greater than 1
Empty string
String of length 1
位置模式
位置模式允许在括号中指定一组值,并且如果括号中的每个值与匹配对象的值匹配,则将匹配。通过解构提取对象值。位置模式基于解构模式。以下类型可以使用位置模式:
- 具有一个或多个析构函数的任何类型。如果一个类型定义了一个或多个接受一个或多个输出参数的Deconstruct()方法,则称该类型具有解构器。 Deconstruct()方法也可以定义为扩展方法。
- 元组类型(System.ValueTuple 的实例)。
- 位置记录类型。 (从 C# 9 开始)。
句法:
(constant1, constant2, …)
示例 1:具有定义Deconstruct()方法的类型的位置模式
下面的代码定义了两个函数LogicalAnd()和LogicalOr() ,它们都接受一个 BooleanInput 对象。 BooleanInput 是一个 value-type(struct),表示两个布尔输入值。这些方法使用这两个输入值并对这些值执行逻辑与和逻辑或运算。 C# 已经有逻辑 AND(&&) 和逻辑 OR(||)运算符来为我们执行这些操作。但是,此示例中的方法手动实现这些操作以演示位置模式。
C#
// C# program to illustrate the concept of Positional Pattern
using System;
// Represents two inputs to the truth table
public struct BooleanInput
{
public bool Input1
{
get;
set;
}
public bool Input2
{
get;
set;
}
public void Deconstruct(out bool input1,
out bool input2)
{
input1 = Input1;
input2 = Input2;
}
}
class GFG{
// Performs logical AND on an input object
public static bool LogicalAnd(BooleanInput input)
{
// Using switch expression
return input switch
{
(false, false) => false,
(true, false) => false,
(false, true) => false,
(true, true) => true
};
}
// Performs logical OR on an input object
public static bool LogicalOr(BooleanInput input)
{
// Using switch statement
switch (input)
{
case (false, false):
return false;
case (true, false):
return true;
case (false, true):
return true;
case (true, true):
return true;
}
}
// Driver code
static public void Main()
{
var a = new BooleanInput{Input1 = true,
Input2 = false};
var b = new BooleanInput{Input1 = true,
Input2 = true};
Console.WriteLine("Logical AND:");
Console.WriteLine(LogicalAnd(a));
Console.WriteLine(LogicalAnd(b));
Console.WriteLine("Logical OR:");
Console.WriteLine(LogicalOr(a));
Console.WriteLine(LogicalOr(b));
}
}
输出:
Logical AND:
False
True
Logical OR:
True
True
示例 2:对元组使用位置模式
System.ValueTuple 的任何实例都可以用于位置模式。 C# 提供了一种使用括号:() 创建元组的速记语法。通过将一组已声明的变量包装在括号中,可以快速创建元组。在下面的示例中, LocatePoint()方法接受两个表示点的 x 和 y 坐标的参数,然后使用一对额外的括号在switch关键字之后创建一个元组。外括号是switch语句语法的一部分,内括号使用x和y变量创建元组。
C#
// C# program to illustrate the concept of Positional Pattern
using System;
class GFG{
// Displays the location of a point
// by accepting its x and y coordinates
public static void LocatePoint(int x, int y)
{
Console.WriteLine($"Point ({x}, {y}):");
// Using switch statement
// Note the double parantheses
switch ((x, y))
{
case (0, 0):
Console.WriteLine("Point at origin");
break;
case (0, _): // _ will match all values for y
Console.WriteLine("Point on Y axis");
break;
case (_, 0):
Console.WriteLine("Point on X axis");
break;
default:
Console.WriteLine("Point elsewhere");
break;
}
}
// Driver code
static public void Main()
{
LocatePoint(10, 20);
LocatePoint(10, 0);
LocatePoint(0, 20);
LocatePoint(0, 0);
}
}
输出:
Point (10, 20):
Point elsewhere
Point (10, 0):
Point on X axis
Point (0, 20):
Point on Y axis
Point (0, 0):
Point at origin
恒定模式
常量模式是模式的最简单形式。它由一个常数值组成。检查正在匹配的表达式是否等于该常量。常数可以是:
- 数字、布尔值、字符或字符串字面量。
- 一个枚举 价值
- 空值。
- 一个常量字段。
常量模式通常作为其他模式的一部分作为子模式出现(进一步讨论),但它也可以单独使用。
与is运算符一起使用的常量模式的一些示例是:
expression is 2 // int literal
expression is "Geeks" // string literal
expression is System.DayOfWeek.Monday // enum
expression is null // null
请注意最后一个示例,其中模式为null 。这意味着模式匹配提供了另一种检查对象是否为空的方法。此外, expression is null可能比典型的expression == null更具可读性和直观性。
在switch语句的上下文中,常量模式看起来与没有模式匹配的常规switch语句相同。
例子:
下面的示例在名为DayOfTheWeek()的方法中使用具有常量模式的switch表达式,该方法根据传递给它的数字返回星期几的名称。
C#
// C# program to illustrate the concept of Constant Pattern
using System;
class GFG{
// Returns the name of the day of the week
public static string DayOfTheWeek(int day)
{
// Switch expression
return day switch
{
1 => "Sunday",
2 => "Monday",
3 => "Tuesday",
4 => "Wednesday",
5 => "Thursday",
6 => "Friday",
7 => "Saturday",
_ => "Invalid week day"
};
}
// Driver code
static public void Main()
{
Console.WriteLine(DayOfTheWeek(5));
Console.WriteLine(DayOfTheWeek(3));
}
}
输出:
Thursday
Tuesday
var模式
var模式的工作原理与其他模式略有不同。 var模式匹配始终成功,这意味着匹配结果始终为真。 var模式的目的不是测试模式的表达式,而是将表达式分配给变量。这允许在连续表达式中重用变量。 var模式是类型模式的更一般形式。但是,没有指定类型;使用var代替,因此没有类型检查并且匹配总是成功的。
句法:
var varName
var (varName1, varName2, …)
考虑以下代码,其中必须比较DateTime对象的日期和月份:
var now = DateTime.Now;
if (now.Month > 6 && now.Day > 15)
{
// Do Something
}
这可以使用var模式写在一行中:
if (DateTime.Now is var now && now.Month > 6 && now.Day > 15)
{
// Do Something
}
模式组合器/逻辑模式
C# 9 还引入了模式组合器。模式组合器允许将多个模式组合在一起。以下是模式组合器:
- 负模式:不
- 连接模式:和
- 析取模式或
Combinator | Keyword | Description | Example |
---|---|---|---|
Negative Pattern | not | Inverts a pattern match | not 2 not < 10 not null |
Conjunctive Pattern | and | Matches if both the patterns match | > 0 and < 10 { Year: 2002 } and { Month: 1 } not int and not double |
Disjunctive Pattern | or | Matches if at least one of the patterns match | “Hi” or “Hello” or “Hey” null or (0, 0) { Year: 2004 } or { Year: 2002 } |
模式组合器很像逻辑运算符(!、&&、||),但操作数是模式而不是条件或布尔表达式。组合器使模式匹配更加灵活,还有助于节省一些击键次数。
更简单的比较
通过将模式组合器与关系模式一起使用,可以将一个表达式与多个其他值进行比较,而无需一遍又一遍地重复该表达式。例如,请考虑以下事项:
int number = 42;
if (number > 10 && number < 50 && number != 35)
{
// Do Something
}
使用模式匹配和组合器,这可以简化:
int number = 42;
if (number is > 10 and < 50 and not 35)
{
// Do Something
}
可以观察到,变量名编号不需要重复;比较可以无缝链接。
检查值是否为非空
在上面的常量模式部分,讨论了检查值是否为空的另一种方法。模式组合器提供了一个对应物,允许检查一个值是否不为空:
if (expression is not null)
{
}
例子:
以下示例定义了一个名为 I sVowel()的方法,该方法使用or模式组合器来组合多个常量模式,以检查字符是否为元音:
C#
// C# program to illustrate the concept of Pattern Combinators
using System;
class GFG{
public static bool IsVowel(char c)
{
return char.ToLower(c) is 'a' or 'e' or 'i' or 'o' or 'u';
}
// Driver code
public static void Main()
{
Console.WriteLine(IsVowel('A'));
Console.WriteLine(IsVowel('B'));
Console.WriteLine(IsVowel('e'));
Console.WriteLine(IsVowel('x'));
Console.WriteLine(IsVowel('O'));
Console.WriteLine(IsVowel('P'));
}
}
输出:
True
False
True
False
True
False
变量声明
一些模式支持在模式后声明一个变量。
类型模式:类型模式中的变量声明是一种在一个步骤中结合类型检查和强制转换的便捷方式。
考虑以下:
object o = 42;
if (o is int)
{
int i = (int) o;
//...
}
这可以使用变量声明简化为一个步骤:
object o = 42;
if (o is int i)
{
//...
}
位置和属性模式:位置和属性模式还允许在模式后声明变量:
if (DateTime.Now is { Month: 12 } now)
{
// Do something with now
}
var p = (10, 20);
if (p is (10, 20) coords)
{
// Do something with coords
}
在这里, p和coords包含相同的值,并且coords可能几乎没有任何用处。但上述语法是合法的,有时可能对定义Deconstruct()方法的对象有用。
注意:使用or和not模式组合符时不允许变量声明,但允许使用and 。
变量丢弃
有时,在模式匹配期间分配给变量的值可能没有用。变量丢弃允许忽略此类变量的值。丢弃使用下划线 ( _ ) 表示。
在类型模式中:当使用带有变量声明的类型模式时,如下例所示:
switch (expression)
{
case int i:
Console.WriteLine("i is an integer");
break;
...
}
从不使用变量i 。所以,它可以被丢弃:
case int _:
从 C# 9 开始,可以使用没有变量的类型模式,这也允许去掉下划线:
case int:
在位置模式:当使用位置模式,丢弃可以用作字符来在特定的位置相匹配的所有值。例如,在上面的Point例子中,如果我们想在x坐标为0时匹配,而y坐标无关紧要,意味着无论y坐标是什么,模式都必须匹配,以下可以完毕:
point is (0, _) // will match for all values of y
没有组合器的多个模式
可以在没有组合器的情况下一起使用类型模式、位置模式和属性模式。
句法:
type-pattern positional-pattern property-pattern variable-name
考虑Point结构:
struct Point
{
public int X { get; set; }
public int Y { get; set; }
public string Name { get; set; }
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
...
object o = Point() { X = 10, Y = 20, Name = "A" };
代替:
if (o is Point p and (10, _) and { Y: 20}) {..}
可以写出以下内容:
if (o is Point (10, _) { Y: 20 } p) {..}
可以省略一种或多种图案。但是,必须至少有一个模式,并且多个模式一起使用时的顺序必须与上述相同。例如,以下是非法的:
if (o is (10, _) Point { Y: 20 } p)
嵌套模式/子模式
一个模式可以由几个子模式组成。除了能够使用模式组合器组合多个模式之外,C# 还允许单个模式由多个内部模式组成。
例子:
考虑上面的Point结构和以下对象point :
Point point = new Point() { X = 10, Y = 20, Name = "B" };
属性模式中的类型模式和var模式
if (point is { X: int x, Y: var y }) { .. }
在位置模式中键入 Pattern 和var Pattern
if (point is (int x, var y)) { .. }
位置模式和属性模式中的关系模式
switch (point)
{
case (< 10, <= 15):
..
break;
case { X: < 10, Y: <= 15 }:
..
break;
..
}
对于下一个示例,请考虑以下对象:
var p1 = new Point { X = 0, Y = 1, Name = "X" };
var p2 = new Point { X = 0, Y = 2, Name = "Y" };
位置模式中的属性模式
if ((p1, p2) is ({ X: 0 }, { X: 0 })) { .. }