C# 重載條件邏輯運算符(&& 和 ||)

  • 2020 年 2 月 10 日
  • 筆記

C# 重載條件邏輯運算符(&& 和 ||)

發佈於 2018-10-16 21:04 更新於 2018-12-14 01:54

在微軟的官方文檔中,規定 &&|| 運算符不可被重載,但允許通過重載 &|truefalse 實現間接重載。

本文將介紹重載方法和原理。感謝 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 operators true and false. 類型不能直接重載條件邏輯運算符(&&||),但通過重載常規邏輯運算符 &| 及運算符 truefalse 可以達到同樣的效果。

也就是說,在官方的概念中,&&|| 是允許被重載的,只是不能直接重載。

原因在於,&&|| 是短路運算符(Circuit Operator),具有短路求值特性。具體來說,A && B 運算中,如果 Afalse,那麼 B 的值便不會計算;同樣的,A || B 中,如果 Atrue,那麼 B 的值也不會計算。

於是,如果允許自定義 &&|| 運算符,那麼必然會導致這個運算符重載的方法有兩個參數傳入,於是這兩個參數一定會被計算值;這樣就無法實現短路求值了。於是對於 &&|| 的重載採用的方案是重載 &| 運算符,然後重載 truefalse 運算符來指定短路求值。

試錯實驗

我們寫一個類型進行實驗:

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;

改為相同的類型後,還會繼續提示需要定義 truefalse 運算符。

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 行由於 ab 沒有共有字符,所以得到 null

第 7 行的執行過程是這樣的:

  1. a 求值,即 a 本身;
  2. a 進行 truthy / falsy 邏輯判斷,得到 truthy;
  3. 由於 a 為 truthy,對於 && 運算符而言,可以對 b 求值,於是對 b 求值得到 b 本身;
  4. ab 進行 & 運算,得到 ` ,也就是 null`。

第 8 行的執行過程是這樣的:

  1. a 求值,即 a 本身;
  2. a 進行 truthy / falsy 邏輯判斷,得到 truthy;
  3. 由於 a 為 truthy,對於 || 運算符而言,已無需對 b 求值,最終得到的結果為 a,也就是 A

參考資料

本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/overload-conditional-and-and-or-

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請 與我聯繫 ([email protected])