C# 重載條件邏輯運算符(&& 和 ||)
- 2020 年 2 月 10 日
- 筆記
C# 重載條件邏輯運算符(&& 和 ||)
發佈於 2018-10-16 21:04 更新於 2018-12-14 01:54
在微軟的官方文檔中,規定 && 和 || 運算符不可被重載,但允許通過重載 &、|、true 和 false 實現間接重載。
本文將介紹重載方法和原理。感謝 Opportunity 的指導。
條件邏輯運算符是可以重載的
在微軟的官方文檔 true Operator (C# Reference) - Microsoft Docs 中,解釋了 && 和 || 這兩個條件邏輯運算符的重載方法:
A type cannot directly overload the Caseal logical operators (
&&and||), but an equivalent effect can be achieved by overloading the regular logical operators and operatorstrueandfalse. 類型不能直接重載條件邏輯運算符(&&和||),但通過重載常規邏輯運算符&、|及運算符true和false可以達到同樣的效果。
也就是說,在官方的概念中,&& 和 || 是允許被重載的,只是不能直接重載。
原因在於,&& 和 || 是短路運算符(Circuit Operator),具有短路求值特性。具體來說,A && B 運算中,如果 A 是 false,那麼 B 的值便不會計算;同樣的,A || B 中,如果 A 是 true,那麼 B 的值也不會計算。
於是,如果允許自定義 && 和 || 運算符,那麼必然會導致這個運算符重載的方法有兩個參數傳入,於是這兩個參數一定會被計算值;這樣就無法實現短路求值了。於是對於 && 和 || 的重載採用的方案是重載 & 和 | 運算符,然後重載 true 和 false 運算符來指定短路求值。
試錯實驗
我們寫一個類型進行實驗:
using System; namespace Walterlv.Demo { public class Case { public static bool operator &(Case a, Case b) { throw new NotImplementedException(); } } }直接使用 & 是沒有問題的,但如果使用 && 就會提示錯誤。
var a = new Case(); var b = new Case(); if (a && b) { }Error CS0217: In order to be applicable as a short circuit operator a user-defined logical operator (『Case.operator &(Case, Case)』) must have the same return type and parameter types Error CS0217: 為了可以像短路運算符一樣應用,用戶定義的邏輯運算符(「Case.operator &(Case, Case)」)的返回類型和參數類型必須相同
也就是說,本身重載 & 運算符的時候允許返回不同的類型;但如果希望 && 運算符在此重載下也生效,就必須確保 & 的返回類型與參數中的類型相同。
public static Case operator &(Case a, Case b) { throw new NotImplementedException(); }var a = new Case(); var b = new Case(); var c = a && b;
改為相同的類型後,還會繼續提示需要定義 true 和 false 運算符。
Error CS0218: In order for 『Case.operator &(Case, Case)』 to be applicable as a short circuit operator, its declaring type 『Case』 must define operator true and operator false
重載 && 和 ||
以下程式碼中,true 表示字元串中包含大寫字母,false 表示字元串中不包含大寫字母(null 和沒有大小寫的區域也屬於不包含大寫字母)。& 運算符僅留下兩者共有的字元;| 則取所有字元。
public class Case { private string _value; public Case(string value) { _value = value; } public static Case operator &(Case a, Case b) { if (a is null || b is null) return null; if (a._value is null || b._value is null) return new Case(null); return new Case(new string(b._value.Except(a._value.Except(b._value)).ToArray())); } public static Case operator |(Case a, Case b) => new Case(a._value + b._value); public static bool operator true(Case a) => a?._value != null && !a._value.ToLower(CultureInfo.CurrentCulture).Equals(a._value); public static bool operator false(Case a) => a?._value == null || a._value.ToLower(CultureInfo.CurrentCulture).Equals(a._value); public override string ToString() => _value; }測試重載了條件邏輯運算符的類型
我們測試以上程式碼所用的程式碼如下:
var a = new Case("A"); var b = new Case("b"); Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(a ? "a 是 truthy" : "a 是 falsy"); Console.WriteLine(b ? "b 是 truthy" : "b 是 falsy"); Console.WriteLine(a & b); Console.WriteLine(a | b); Console.WriteLine(a && b); Console.WriteLine(a || b);以上各個 Console.WriteLine 的輸出為:
[1] A [2] b [3] a 是 truthy [4] b 是 falsy [5] [6] Ab [7] [8] A
注意,空行其實指的是輸出 null。
truthy 和 falsy
剛剛的測試程式碼中,我們使用了 truthy 和 falsy 概念,而這是邏輯判斷概念:
- 如果在邏輯判斷中,對象與
true等價,但其數值上並非true(不等於true),那麼稱此對象為 truthy; - 如果在邏輯判斷中,對象與
false等價,但其數值上並非false(不等於false),那麼稱此對象為 falsy。
對以上測試輸出的解釋
第 5 行由於 a 和 b 沒有共有字元,所以得到 null。
第 7 行的執行過程是這樣的:
- 對
a求值,即a本身; - 對
a進行 truthy / falsy 邏輯判斷,得到 truthy; - 由於
a為 truthy,對於&&運算符而言,可以對 b 求值,於是對b求值得到b本身; - 對
a和b進行&運算,得到 `,也就是null`。
第 8 行的執行過程是這樣的:
- 對
a求值,即a本身; - 對
a進行 truthy / falsy 邏輯判斷,得到 truthy; - 由於
a為 truthy,對於||運算符而言,已無需對b求值,最終得到的結果為a,也就是A。
參考資料
- C# 中那些可以被重載的操作符 - walterlv - 請閱讀文章末尾的評論
- true Operator (C# Reference) - Microsoft Docs
- JavaScript: Truthy? Falsy? - 格物致知
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請 與我聯繫 ([email protected]) 。
