手工打造運算子多載過程

來源:互聯網
上載者:User

除了J#外,所有微軟支援的.NET開發語言現在均支援運算子多載,因此純粹為C#簡化寫法一樣特性現已成為一種.NET開發中值得研究的一項重要語言特性。有人認為運算子多載其實就是簡化寫法,滿足類比基本類型操作的小功能,沒有必要深究。但我覺得要多思考一層,為什麼我們總希望類比基本類型的操作?因為運算子多載能夠將操作中綴化,能夠自動推測靜態過程的主體。

首先是操作中綴化。函數調用其實是一種首碼操作,函數(代表操作)總是在參數(代表運算元)之前寫出。這樣執行序列操作時啟動並執行順序其實和書寫的順序相反:

H(x,y)
G(H(x, y), z)
F(G(H(x, y), z), w)

序列啟動並執行順序是H->G->F但是卻要反過來寫,二元參數距離函數名越來越遠。我們按照電腦執行的順序思考,卻要反過來寫,多少有些不爽。成員函數擴充方法的寫法則是將運算元(對象)寫在前面:

x.H(y)
x.H(y).G(z)
x.H(y).G(z).F(w)

這樣就將書寫的順序正過來了。這是一個甚好的方案,但是在不具備擴充方法的今天,有些事情是成員函數做不了的。比如在我的VBF裡,我希望Functor<T, bool>可以進行And, Or等邏輯運算,而Functor<T, int>之間只能進行算術運算,Functor<T, string>之間只能進行串連運算,而且規則還不一樣……但是成員函數沒有根據型別參數選取不同重載的能力,也就是說.NET泛型無法進行特化操作。在.NET中具有編譯期類型判定的機制只有兩個:函數根據參數類型的重載和使用者自訂隱式轉換(相當於根據傳回型別重載)。我們可以用Functor<,>類型的靜態方法來實現根據型別參數不同的不同重載。但是靜態方法不但要寫全類型的名字,還是首碼操作,使用起來讓人甚為不爽,這時就會發現,運算子多載是我們夢寐以求的東西。

Type.op_Operator(x, y) '靜態方法
x op y '運算子寫法

以上兩種是等價的,可以看到運算子多載不僅可以通過x,y的類型推測靜態方法的調用主體Type,還可以將操作轉化為中綴寫法——比尾碼更適合表現二元運算。既然這麼完美,我們能不能這樣寫呢?

Class Functor(Of T, U)
    Public Shared Operator And( _
        x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _
        As Functor(Of T, Boolean)
    End Operator
End Class

很遺憾,這樣會編譯錯誤。作為運算子多載過程,其參數至少有一個必須是定義運算子的類型。在編譯器看來,必須是Functor(Of T, U),兩個型別參數都必須是該泛型類定義的。就在我對此大感抱怨時,我偶然在C#編譯器的原始碼(見Rotor)中看到了它識別運算子的規則,其中並沒有這些限制,只有兩條規則——方法必須是靜態,特定名稱的方法;方法必須帶有specialname屬性。那麼我們完全可以騙過編譯器,不用它提供的Operator關鍵字來聲明運算子多載過程,而是使用自己編寫特定名稱的方法,並加以specialname的手法來打造運算子多載過程:

Imports System.Runtime.CompilerServices

Class Functor(Of T, U)
    <SpecialName()> _
    Public Shared Function op_BitwiseAnd( _
        x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _
        As Functor(Of T, Boolean)
    End Function
End Class

System.Runtime.CompilerServices.SpecialNameAttribute是一個指示編譯器為聲明成員添加specialname的特殊屬性,C#和VB編譯器都支援。op_BitwiseAnd是VB和C#等語言所識別的與操作運算子程序名稱。這樣寫完以後編譯成類庫,再以引用DLL的方式引用它,你就會看到編譯器將他識別成了我們要的運算子多載過程。當你在Functor<T, int>這樣的類型上使用And操作時,編譯器會告訴你不支援該運算子,僅在Functor<T, bool>上才能進行這一操作,編譯錯誤資訊準確無誤,真是太棒了。

在我們結束前,我們還可以看看如此手工打造還能突破哪些編譯器人為的限制:

可重載Protected和Private的運算子(儘管這樣做幾乎沒有意義)
可不成對重載比較子(=, >, >=, <=, <, <>)
可以讓移位元運算符的第二個運算元不是int(>>和<<樣子很好看,但是有了這個限制我們就不能拿它來幹別的事情,現在好了)
可以在C#中重載僅VB支援的運算子,也可以在VB中重載僅C#支援的運算子(當然要到對方語言中才會生效)
可以讓使用者自訂顯式轉換支援泛型型別參數之間更加神奇的寫法
用了這種手法,似乎還可以重載諸如operator+(int, int)之類的運算子,但它們並不能生效。

.NET語言編譯器中每一項特性,都可能有隱藏在其表面之下的深層次用途。善加研究後常能發現原來所認識不到的功能。我當然不是在推薦大家亂用運算子多載,只是一種思考,一種新的靈感。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.