C#?模式匹配完全指南
前言
自從 2017 年 C# 7.0 版本開始引入聲明模式和常數(shù)模式匹配開始,到 2022 年的 C# 11 為止,最后一個(gè)板塊列表模式和切片模式匹配也已經(jīng)補(bǔ)齊,當(dāng)初計(jì)劃的模式匹配內(nèi)容已經(jīng)基本全部完成。
C# 在模式匹配方面下一步計(jì)劃則是支持活動(dòng)模式(active pattern),這一部分將在本文最后進(jìn)行介紹,而在介紹未來的模式匹配計(jì)劃之前,本文主題是對(duì)截止 C# 11 模式匹配的(不)完全指南,希望能對(duì)各位開發(fā)者們提升代碼編寫效率、可讀性和質(zhì)量有所幫助。
模式匹配
要使用模式匹配,首先要了解什么是模式。在使用正則表達(dá)式匹配字符串時(shí),正則表達(dá)式自己就是一個(gè)模式,而對(duì)字符串使用這段正則表達(dá)式進(jìn)行匹配的過程就是模式匹配。而在代碼中也是同樣的,我們對(duì)對(duì)象采用某種模式進(jìn)行匹配的過程就是模式匹配。
C# 11 支持的模式有很多,包含:
- 聲明模式(declaration pattern)
- 類型模式(type pattern)
- 常數(shù)模式(constant pattern)
- 關(guān)系模式(relational pattern)
- 邏輯模式(logical pattern)
- 屬性模式(property pattern)
- 位置模式(positional pattern)
- var 模式(var pattern)
- 丟棄模式(discard pattern)
- 列表模式(list pattern)
- 切片模式(slice pattern)
而其中,不少模式都支持遞歸,也就意味著可以模式嵌套模式,以此來實(shí)現(xiàn)更加強(qiáng)大的匹配功能。
模式匹配可以通過 switch
表達(dá)式來使用,也可以在普通的 switch
語句中作為 case
使用,還可以在 if
條件中通過 is
來使用。本文主要在 switch
表達(dá)式中使用模式匹配。
那么接下來就對(duì)這些模式進(jìn)行介紹。
實(shí)例:表達(dá)式計(jì)算器
為了更直觀地介紹模式匹配,我們接下來利用模式匹配來編寫一個(gè)表達(dá)式計(jì)算器。
為了編寫表達(dá)式計(jì)算器,首先我們需要對(duì)表達(dá)式進(jìn)行抽象:
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public abstract T Eval(params (string Name, T Value)[] args); }
我們用上面這個(gè) Expr<T>
來表示一個(gè)表達(dá)式,其中 T
是操作數(shù)的類型,然后進(jìn)一步將表達(dá)式分為常數(shù)表達(dá)式 ConstantExpr
、參數(shù)表達(dá)式 ParameterExpr
、一元表達(dá)式 UnaryExpr
、二元表達(dá)式 BinaryExpr
和三元表達(dá)式 TernaryExpr
。最后提供一個(gè) Eval
方法,用來計(jì)算表達(dá)式的值,該方法可以傳入一個(gè) args
來提供表達(dá)式計(jì)算所需要的參數(shù)。
有了一、二元表達(dá)式自然也需要運(yùn)算符,例如加減乘除等,我們也同時(shí)定義 Operator
來表示運(yùn)算符:
public abstract record Operator { public record UnaryOperator(Operators Operator) : Operator; public record BinaryOperator(BinaryOperators Operator) : Operator; }
然后設(shè)置允許的運(yùn)算符,其中前三個(gè)是一元運(yùn)算符,后面的是二元運(yùn)算符:
public enum Operators { [Description("~")] Inv, [Description("-")] Min, [Description("!")] LogicalNot, [Description("+")] Add, [Description("-")] Sub, [Description("*")] Mul, [Description("/")] Div, [Description("&")] And, [Description("|")] Or, [Description("^")] Xor, [Description("==")] Eq, [Description("!=")] Ne, [Description(">")] Gt, [Description("<")] Lt, [Description(">=")] Ge, [Description("<=")] Le, [Description("&&")] LogicalAnd, [Description("||")] LogicalOr, }
你可以能會(huì)好奇對(duì) T
的運(yùn)算能如何實(shí)現(xiàn)邏輯與或非,關(guān)于這一點(diǎn),我們直接使用 0
來代表 false
,非 0
代表 true
。
接下來就是分別實(shí)現(xiàn)各類表達(dá)式的時(shí)間!
常數(shù)表達(dá)式
常數(shù)表達(dá)式很簡(jiǎn)單,它保存一個(gè)常數(shù)值,因此只需要在構(gòu)造方法中將用戶提供的值存儲(chǔ)下來。它的 Eval
實(shí)現(xiàn)也只需要簡(jiǎn)單返回存儲(chǔ)的值即可:
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public class ConstantExpr : Expr<T> { public ConstantExpr(T value) => Value = value; public T Value { get; } public void Deconstruct(out T value) => value = Value; public override T Eval(params (string Name, T Value)[] args) => Value; } }
參數(shù)表達(dá)式
參數(shù)表達(dá)式用來定義表達(dá)式計(jì)算過程中的參數(shù),允許用戶在對(duì)表達(dá)式執(zhí)行 Eval
計(jì)算結(jié)果的時(shí)候傳參,因此只需要存儲(chǔ)參數(shù)名。它的 Eval
實(shí)現(xiàn)需要根據(jù)參數(shù)名在 args
中找出對(duì)應(yīng)的參數(shù)值:
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public class ParameterExpr : Expr<T> { public ParameterExpr(string name) => Name = name; public string Name { get; } public void Deconstruct(out string name) => name = Name; // 對(duì) args 進(jìn)行模式匹配 public override T Eval(params (string Name, T Value)[] args) => args switch { // 如果 args 有至少一個(gè)元素,那我們把第一個(gè)元素拿出來存為 (name, value), // 然后判斷 name 是否和本參數(shù)表達(dá)式中存儲(chǔ)的參數(shù)名 Name 相同。 // 如果相同則返回 value,否則用 args 除去第一個(gè)元素剩下的參數(shù)繼續(xù)匹配。 [var (name, value), .. var tail] => name == Name ? value : Eval(tail), // 如果 args 是空列表,則說明在 args 中沒有找到名字和 Name 相同的參數(shù),拋出異常 [] => throw new InvalidOperationException($"Expected an argument named {Name}.") }; } }
模式匹配會(huì)從上往下依次進(jìn)行匹配,直到匹配成功為止。
上面的代碼中你可能會(huì)好奇 [var (name, value), .. var tail]
是個(gè)什么模式,這個(gè)模式整體看是列表模式,并且列表模式內(nèi)組合使用聲明模式、位置模式和切片模式。例如:
[]
:匹配一個(gè)空列表。[1, _, 3]
:匹配一個(gè)長(zhǎng)度是 3,并且首尾元素分別是 1、3 的列表。其中_
是丟棄模式,表示任意元素。[_, .., 3]
:匹配一個(gè)末元素是 3,并且 3 不是首元素的列表。其中..
是切片模式,表示任意切片。[1, ..var tail]
:匹配一個(gè)首元素是 1 的列表,并且將除了首元素之外元素的切片賦值給tail
。其中var tail
是var
模式,用于將匹配結(jié)果賦值給變量。[var head, ..var tail]
:匹配一個(gè)列表,將它第一個(gè)元素賦值給head
,剩下元素的切片賦值給tail
,這個(gè)切片里可以沒有元素。[var (name, value), ..var tail]
:匹配一個(gè)列表,將它第一個(gè)元素賦值給(name, value)
,剩下元素的切片賦值給tail
,這個(gè)切片里可以沒有元素。其中(name, value)
是位置模式,用于將第一個(gè)元素的解構(gòu)結(jié)果根據(jù)位置分別賦值給name
和value
,也可以寫成(var name, var value)
。
一元表達(dá)式
一元表達(dá)式用來處理只有一個(gè)操作數(shù)的計(jì)算,例如非、取反等。
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public class UnaryExpr : Expr<T> { public UnaryExpr(UnaryOperator op, Expr<T> expr) => (Op, Expr) = (op, expr); public UnaryOperator Op { get; } public Expr<T> Expr { get; } public void Deconstruct(out UnaryOperator op, out Expr<T> expr) => (op, expr) = (Op, Expr); // 對(duì) Op 進(jìn)行模式匹配 public override T Eval(params (string Name, T Value)[] args) => Op switch { // 如果 Op 是 UnaryOperator,則將其解構(gòu)結(jié)果賦值給 op,然后對(duì) op 進(jìn)行匹配,op 是一個(gè)枚舉,而 .NET 中的枚舉值都是整數(shù) UnaryOperator(var op) => op switch { // 如果 op 是 Operators.Inv Operators.Inv => ~Expr.Eval(args), // 如果 op 是 Operators.Min Operators.Min => -Expr.Eval(args), // 如果 op 是 Operators.LogicalNot Operators.LogicalNot => Expr.Eval(args) == T.Zero ? T.One : T.Zero, // 如果 op 的值大于 LogicalNot 或者小于 0,表示不是一元運(yùn)算符 > Operators.LogicalNot or < 0 => throw new InvalidOperationException($"Expected an unary operator, but got {op}.") }, // 如果 Op 不是 UnaryOperator _ => throw new InvalidOperationException("Expected an unary operator.") }; } }
上面的代碼中,首先利用了 C# 元組可作為左值的特性,分別使用一行代碼就做完了構(gòu)造方法和解構(gòu)方法的賦值:(Op, Expr) = (op, expr)
和 (op, expr) = (Op, Expr)
。如果你好奇能否利用這個(gè)特性交換多個(gè)變量,答案是可以!
在 Eval
中,首先將類型模式、位置模式和聲明模式組合成 UnaryOperator(var op)
,表示匹配 UnaryOperator
類型、并且能解構(gòu)出一個(gè)元素的東西,如果匹配則將解構(gòu)出來的那個(gè)元素賦值給 op
。
然后我們接著對(duì)解構(gòu)出來的 op
進(jìn)行匹配,這里用到了常數(shù)模式,例如 Operators.Inv
用來匹配 op
是否是 Operators.Inv
。常數(shù)模式可以使用各種常數(shù)對(duì)對(duì)象進(jìn)行匹配。
這里的 > Operators.LogicalNot
和 < 0
則是關(guān)系模式,分別用于匹配大于 Operators.LogicalNot
的值和小于 0
的指。然后利用邏輯模式 or
將兩個(gè)模式組合起來表示或的關(guān)系。邏輯模式除了 or
之外還有 and
和 not
。
由于我們?cè)谏厦娓F舉了枚舉中所有的一元運(yùn)算符,因此也可以將 > Operators.LogicalNot or < 0
換成丟棄模式 _
或者 var 模式 var foo
,兩者都用來匹配任意的東西,只不過前者匹配到后直接丟棄,而后者聲明了個(gè)變量 foo
將匹配到的值放到里面:
op switch { // ... _ => throw new InvalidOperationException($"Expected an unary operator, but got {op}.") }
或
op switch { // ... var foo => throw new InvalidOperationException($"Expected an unary operator, but got {foo}.") }
二元表達(dá)式
二元表達(dá)式用來表示操作數(shù)有兩個(gè)的表達(dá)式。有了一元表達(dá)式的編寫經(jīng)驗(yàn),二元表達(dá)式如法炮制即可。
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public class BinaryExpr : Expr<T> { public BinaryExpr(BinaryOperator op, Expr<T> left, Expr<T> right) => (Op, Left, Right) = (op, left, right); public BinaryOperator Op { get; } public Expr<T> Left { get; } public Expr<T> Right { get; } public void Deconstruct(out BinaryOperator op, out Expr<T> left, out Expr<T> right) => (op, left, right) = (Op, Left, Right); public override T Eval(params (string Name, T Value)[] args) => Op switch { BinaryOperator(var op) => op switch { Operators.Add => Left.Eval(args) + Right.Eval(args), Operators.Sub => Left.Eval(args) - Right.Eval(args), Operators.Mul => Left.Eval(args) * Right.Eval(args), Operators.Div => Left.Eval(args) / Right.Eval(args), Operators.And => Left.Eval(args) & Right.Eval(args), Operators.Or => Left.Eval(args) | Right.Eval(args), Operators.Xor => Left.Eval(args) ^ Right.Eval(args), Operators.Eq => Left.Eval(args) == Right.Eval(args) ? T.One : T.Zero, Operators.Ne => Left.Eval(args) != Right.Eval(args) ? T.One : T.Zero, Operators.Gt => Left.Eval(args) > Right.Eval(args) ? T.One : T.Zero, Operators.Lt => Left.Eval(args) < Right.Eval(args) ? T.One : T.Zero, Operators.Ge => Left.Eval(args) >= Right.Eval(args) ? T.One : T.Zero, Operators.Le => Left.Eval(args) <= Right.Eval(args) ? T.One : T.Zero, Operators.LogicalAnd => Left.Eval(args) == T.Zero || Right.Eval(args) == T.Zero ? T.Zero : T.One, Operators.LogicalOr => Left.Eval(args) == T.Zero && Right.Eval(args) == T.Zero ? T.Zero : T.One, < Operators.Add or > Operators.LogicalOr => throw new InvalidOperationException($"Unexpected a binary operator, but got {op}.") }, _ => throw new InvalidOperationException("Unexpected a binary operator.") }; } }
同理,也可以將 < Operators.Add or > Operators.LogicalOr
換成丟棄模式或者 var 模式。
三元表達(dá)式
三元表達(dá)式包含三個(gè)操作數(shù):條件表達(dá)式 Cond
、為真的表達(dá)式 Left
、為假的表達(dá)式 Right
。該表達(dá)式中會(huì)根據(jù) Cond
是否為真來選擇取 Left
還是 Right
,實(shí)現(xiàn)起來較為簡(jiǎn)單:
public abstract partial class Expr<T> where T : IBinaryNumber<T> { public class TernaryExpr : Expr<T> { public TernaryExpr(Expr<T> cond, Expr<T> left, Expr<T> right) => (Cond, Left, Right) = (cond, left, right); public Expr<T> Cond { get; } public Expr<T> Left { get; } public Expr<T> Right { get; } public void Deconstruct(out Expr<T> cond, out Expr<T> left, out Expr<T> right) => (cond, left, right) = (Cond, Left, Right); public override T Eval(params (string Name, T Value)[] args) => Cond.Eval(args) == T.Zero ? Right.Eval(args) : Left.Eval(args); } }
完成。我們用了僅僅幾十行代碼就完成了全部的核心邏輯!這便是模式匹配的強(qiáng)大之處:簡(jiǎn)潔、直觀且高效。
表達(dá)式判等
至此為止,我們已經(jīng)完成了所有的表達(dá)式構(gòu)造、解構(gòu)和計(jì)算的實(shí)現(xiàn)。接下來我們?yōu)槊恳粋€(gè)表達(dá)式實(shí)現(xiàn)判等邏輯,即判斷兩個(gè)表達(dá)式(字面上)是否相同。
例如 a == b ? 2 : 4
和 a == b ? 2 : 5
不相同,a == b ? 2 : 4
和 c == d ? 2 : 4
不相同,而 a == b ? 2 : 4
和 a == b ? 2 : 4
相同。
為了實(shí)現(xiàn)該功能,我們重寫每一個(gè)表達(dá)式的 Equals
和 GetHashCode
方法。
常數(shù)表達(dá)式
常數(shù)表達(dá)式判等只需要判斷常數(shù)值是否相等即可:
public override bool Equals(object? obj) => obj is ConstantExpr(var value) && value == Value; public override int GetHashCode() => Value.GetHashCode();
參數(shù)表達(dá)式
參數(shù)表達(dá)式判等只需要判斷參數(shù)名是否相等即可:
public override bool Equals(object? obj) => obj is ParameterExpr(var name) && name == Name; public override int GetHashCode() => Name.GetHashCode();
一元表達(dá)式
一元表達(dá)式判等,需要判斷被比較的表達(dá)式是否是一元表達(dá)式,如果也是的話則判斷運(yùn)算符和操作數(shù)是否相等:
public override bool Equals(object? obj) => obj is UnaryExpr({ Operator: var op }, var expr) && (op, expr).Equals((Op.Operator, Expr)); public override int GetHashCode() => (Op, Expr).GetHashCode();
上面的代碼中用到了屬性模式 { Operator: var op }
,用來匹配屬性的值,這里直接組合了聲明模式將屬性 Operator
的值賦值給了 expr
。另外,C# 中的元組可以組合起來進(jìn)行判等操作,因此不需要寫 op.Equals(Op.Operator) && expr.Equals(Expr)
,而是可以直接寫 (op, expr).Equals((Op.Operator, Expr))
。
二元表達(dá)式
和一元表達(dá)式差不多,區(qū)別在于這次多了一個(gè)操作數(shù):
public override bool Equals(object? obj) => obj is BinaryExpr({ Operator: var op }, var left, var right) && (op, left, right).Equals((Op.Operator, Left, Right)); public override int GetHashCode() => (Op, Left, Right).GetHashCode();
三元表達(dá)式
和二元表達(dá)式差不多,只不過運(yùn)算符 Op
變成了操作數(shù) Cond
:
public override bool Equals(object? obj) => obj is TernaryExpr(var cond, var left, var right) && cond.Equals(Cond) && left.Equals(Left) && right.Equals(Right); public override int GetHashCode() => (Cond, Left, Right).GetHashCode();
到此為止,我們?yōu)樗械谋磉_(dá)式都實(shí)現(xiàn)了判等。
一些工具方法
我們重載一些 Expr<T>
的運(yùn)算符方便我們使用:
public static Expr<T> operator ~(Expr<T> operand) => new UnaryExpr(new(Operators.Inv), operand); public static Expr<T> operator !(Expr<T> operand) => new UnaryExpr(new(Operators.LogicalNot), operand); public static Expr<T> operator -(Expr<T> operand) => new UnaryExpr(new(Operators.Min), operand); public static Expr<T> operator +(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Add), left, right); public static Expr<T> operator -(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Sub), left, right); public static Expr<T> operator *(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Mul), left, right); public static Expr<T> operator /(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Div), left, right); public static Expr<T> operator &(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.And), left, right); public static Expr<T> operator |(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Or), left, right); public static Expr<T> operator ^(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Xor), left, right); public static Expr<T> operator >(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Gt), left, right); public static Expr<T> operator <(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Lt), left, right); public static Expr<T> operator >=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Ge), left, right); public static Expr<T> operator <=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Le), left, right); public static Expr<T> operator ==(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Eq), left, right); public static Expr<T> operator !=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Ne), left, right); public static implicit operator Expr<T>(T value) => new ConstantExpr(value); public static implicit operator Expr<T>(string name) => new ParameterExpr(name); public static implicit operator Expr<T>(bool value) => new ConstantExpr(value ? T.One : T.Zero); public override bool Equals(object? obj) => base.Equals(obj); public override int GetHashCode() => base.GetHashCode();
由于重載了 ==
和 !=
,編譯器為了保險(xiǎn)起見提示我們重寫 Equals
和 GetHashCode
,這里實(shí)際上并不需要重寫,因此直接調(diào)用 base
上的方法保持默認(rèn)行為即可。
然后編寫兩個(gè)擴(kuò)展方法用來方便構(gòu)造三元表達(dá)式,和從 Description
中獲取運(yùn)算符的名字:
public static class Extensions { public static Expr<T> Switch<T>(this Expr<T> cond, Expr<T> left, Expr<T> right) where T : IBinaryNumber<T> => new Expr<T>.TernaryExpr(cond, left, right); public static string? GetName<T>(this T op) where T : Enum => typeof(T).GetMember(op.ToString()).FirstOrDefault()?.GetCustomAttribute<DescriptionAttribute>()?.Description; }
由于有參數(shù)表達(dá)式參與時(shí)需要我們提前提供參數(shù)值才能調(diào)用 Eval
進(jìn)行計(jì)算,因此我們寫一個(gè)交互式的 Eval
來在計(jì)算過程中遇到參數(shù)表達(dá)式時(shí)提示用戶輸入值,起名叫做 InteractiveEval
:
public T InteractiveEval() { var names = Array.Empty<string>(); return Eval(GetArgs(this, ref names, ref names)); } private static T GetArg(string name, ref string[] names) { Console.Write($"Parameter {name}: "); string? str; do { str = Console.ReadLine(); } while (str is null); names = names.Append(name).ToArray(); return T.Parse(str, NumberStyles.Number, null); } private static (string Name, T Value)[] GetArgs(Expr<T> expr, ref string[] assigned, ref string[] filter) => expr switch { TernaryExpr(var cond, var left, var right) => GetArgs(cond, ref assigned, ref assigned).Concat(GetArgs(left, ref assigned,ref assigned)).Concat(GetArgs(right, ref assigned, ref assigned)).ToArray(), BinaryExpr(_, var left, var right) => GetArgs(left, ref assigned, ref assigned).Concat(GetArgs(right, ref assigned, refassigned)).ToArray(), UnaryExpr(_, var uexpr) => GetArgs(uexpr, ref assigned, ref assigned), ParameterExpr(var name) => filter switch { [var head, ..] when head == name => Array.Empty<(string Name, T Value)>(), [_, .. var tail] => GetArgs(expr, ref assigned, ref tail), [] => new[] { (name, GetArg(name, ref assigned)) } }, _ => Array.Empty<(string Name, T Value)>() };
這里在 GetArgs
方法中,模式 [var head, ..]
后面跟了一個(gè) when head == name
,這里的 when
用來給模式匹配指定額外的條件,僅當(dāng)條件滿足時(shí)才匹配成功,因此 [var head, ..] when head == name
的含義是,匹配至少含有一個(gè)元素的列表,并且將頭元素賦值給 head
,且僅當(dāng) head == name
時(shí)匹配才算成功。
最后我們?cè)僦貙?ToString
方法方便輸出表達(dá)式,就全部大功告成了。
測(cè)試
接下來讓我測(cè)試測(cè)試我們編寫的表達(dá)式計(jì)算器:
Expr<int> a = 4; Expr<int> b = -3; Expr<int> x = "x"; Expr<int> c = !((a + b) * (a - b) > x); Expr<int> y = "y"; Expr<int> z = "z"; Expr<int> expr = (c.Switch(y, z) - a > x).Switch(z + a, y / b); Console.WriteLine(expr); Console.WriteLine(expr.InteractiveEval());
運(yùn)行后得到輸出:
((((! ((((4) + (-3)) * ((4) - (-3))) > (x))) ? (y) : (z)) - (4)) > (x)) ? ((z) + (4)) : ((y) / (-3))
然后我們給 x
、y
和 z
分別設(shè)置成 42、27 和 35,即可得到運(yùn)算結(jié)果:
Parameter x: 42
Parameter y: 27
Parameter z: 35
-9
再測(cè)測(cè)表達(dá)式判等邏輯:
Expr<int> expr1, expr2, expr3; { Expr<int> a = 4; Expr<int> b = -3; Expr<int> x = "x"; Expr<int> c = !((a + b) * (a - b) > x); Expr<int> y = "y"; Expr<int> z = "z"; expr1 = (c.Switch(y, z) - a > x).Switch(z + a, y / b); } { Expr<int> a = 4; Expr<int> b = -3; Expr<int> x = "x"; Expr<int> c = !((a + b) * (a - b) > x); Expr<int> y = "y"; Expr<int> z = "z"; expr2 = (c.Switch(y, z) - a > x).Switch(z + a, y / b); } { Expr<int> a = 4; Expr<int> b = -3; Expr<int> x = "x"; Expr<int> c = !((a + b) * (a - b) > x); Expr<int> y = "y"; Expr<int> w = "w"; expr3 = (c.Switch(y, w) - a > x).Switch(w + a, y / b); } Console.WriteLine(expr1.Equals(expr2)); Console.WriteLine(expr1.Equals(expr3));
得到輸出:
True
False
活動(dòng)模式
在未來,C# 將會(huì)引入活動(dòng)模式,該模式允許用戶自定義模式匹配的方法,例如:
static bool Even<T>(this T value) where T : IBinaryInteger<T> => value % 2 == 0;
上述代碼定義了一個(gè) T
的擴(kuò)展方法 Even
,用來匹配 value
是否為偶數(shù),于是我們便可以這么使用:
var x = 3; var y = x switch { Even() => "even", _ => "odd" };
此外,該模式還可以和解構(gòu)模式結(jié)合,允許用戶自定義解構(gòu)行為,例如:
static bool Int(this string value, out int result) => int.TryParse(value, out result);
然后使用的時(shí)候:
var x = "3"; var y = x switch { Int(var result) => result, _ => 0 };
即可對(duì) x
這個(gè)字符串進(jìn)行匹配,如果 x
可以被解析為 int
,就取解析結(jié)果 result
,否則取 0。
后記
模式匹配極大的方便了我們編寫出簡(jiǎn)潔且可讀性高的高質(zhì)量代碼,并且會(huì)自動(dòng)幫我們做窮舉檢查,防止我們漏掉情況。此外,使用模式匹配時(shí),編譯器也會(huì)幫我們優(yōu)化代碼,減少完成匹配所需要的比較次數(shù),最終減少分支并提升運(yùn)行效率。
本文中的例子為了覆蓋到全部的模式,不一定采用了最優(yōu)的寫法,這一點(diǎn)各位讀者們也請(qǐng)注意。
本文中的表達(dá)式計(jì)算器全部代碼可以前往我的 GitHub 倉(cāng)庫(kù)獲?。篽ttps://github.com/hez2010/PatternMatchingExpr
到此這篇關(guān)于C# 模式匹配完全指南的文章就介紹到這了,更多相關(guān)C# 模式匹配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c#實(shí)現(xiàn)萬年歷示例分享 萬年歷農(nóng)歷查詢
這篇文章主要介紹了c#實(shí)現(xiàn)萬年歷的方法,可以顯示農(nóng)歷、節(jié)氣、節(jié)日、星座、星宿、屬相、生肖、閏年月、時(shí)辰,大家參考使用吧2014-01-01c# 判斷指定文件是否存在的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了c# 判斷指定文件是否存在的簡(jiǎn)單實(shí)現(xiàn),需要的朋友可以參考下2014-02-02C# Access數(shù)據(jù)庫(kù)增刪查改的簡(jiǎn)單方法
這篇文章主要介紹了C# Access數(shù)據(jù)庫(kù)增刪查改的簡(jiǎn)單方法,有需要的朋友可以參考一下2014-01-01在Form_Load里面調(diào)用Focus無效的解決方法
在調(diào)用Form_Load的時(shí)候,F(xiàn)orm其實(shí)還沒有進(jìn)入展示階段,自然Focus()調(diào)用也就沒效果了。2013-02-02