第 40 講:面向?qū)ο缶幊蹋ㄊ航涌诘娘@式和隱式實現(xiàn)
C# 接口做得非常棒的地方在于,接口分兩種實現(xiàn)模式。它避免了一些復(fù)雜的問題。
Part 1 接口成員的隱式實現(xiàn)
之前我們舉例的時候說明了接口的基本用法。
這種標準的實現(xiàn),稱為接口的隱式實現(xiàn)(Implicit Implementation)。所謂的隱式實現(xiàn),就是“非定向?qū)崿F(xiàn)接口的成員”。因為接口可以實現(xiàn)多個,因此方法可能有非常多。我們?nèi)绻凑漳撤N語法和機制去指定基接口的名字和對應(yīng)的方法名的話,顯然寫起來就很麻煩。因此,我們?yōu)榱吮苊膺@樣的實現(xiàn)過程,我們總是使用隱式實現(xiàn)。
隱式實現(xiàn)也就是非定向?qū)崿F(xiàn)接口的成員。如果接口多起來的話,編譯器也會自動去尋找接口實現(xiàn)過程,然后自動提示你有什么接口成員實現(xiàn)了,哪些又沒有實現(xiàn)。因此隱式實現(xiàn)接口是相當方便的實現(xiàn)模式。
Part 2 接口成員的顯式實現(xiàn)
當然,在極少數(shù)情況下,我們不得不使用顯式實現(xiàn)來避免一些東西。
假設(shè)我們有兩個接口 IA
和 IB
,巧就巧在,它們都只有一個接口成員,而且都是方法,而且它們的簽名就差返回值不一樣。假設(shè)它們倆是這樣的:
因為之前說過,只有返回值不同的兩個方法是不構(gòu)成重載的。因此如果我們要實現(xiàn)這兩個接口成員的,所以光靠隱式接口實現(xiàn)是做不到的:
顯然我們肯定會用這樣的方式去實現(xiàn)??蓡栴}就在于,它們倆不構(gòu)成重載,語法是通不過的。這咋辦呢?難道我們就不能用接口了嗎?
其實不是。C# 提供了另外一種實現(xiàn)模式:接口的顯式實現(xiàn)(Explicit Implementation)。
這里“其它的部分”指的是,接口成員類型對應(yīng)的、需要追加的部分。比如說方法的話,“其它的部分”就包括一對小括號,里面是參數(shù)表列;如果是“屬性”的話,那么“其它的部分”就是一對大括號,然后里面是 get;
、set;
或者 get; set;
了。
使用顯式接口實現(xiàn),我們需要先考慮這些不構(gòu)成重載的成員,到底哪個使用起來更多。顯然,decimal
作為返回值類型的這個版本更好一些,可能使用更多一些(畢竟 decimal
類型比 int
類型表達的數(shù)據(jù)范圍更廣),因此我們用 decimal
這個版本的更多。
考慮這個,是因為顯式接口實現(xiàn)是用在“使用情況較少”的那一方的。換句話說,因為 decimal
這個版本用得多,那么我們需要用顯式接口實現(xiàn)的寫法的,是 int
這個版本的。語法如下:
請注意第 9 行代碼。原始寫法 public int SumUp()
被改寫成了 int IA.SumUp()
。這就是顯式接口實現(xiàn)的語法。其它的地方則都不發(fā)生任何變動。要改寫的地方只有簽名這一行代碼而已。
顯式接口實現(xiàn)里是不寫訪問修飾符和 abstract
關(guān)鍵字的,想必這一點我也不用說明了吧。不過,override
也是不寫的,因為我們這里給出實現(xiàn),本身就是一種重寫的行為。它和類里的重寫不同:類里的重寫因為可能可以給方法添加 new
修飾符而不是 override
修飾符,因此有兩種可能;而這里只可能是 override
。
Part 3 類型實現(xiàn)接口下的訪問級別問題
我們來思考一點。雖然在類型的派生關(guān)系下,我們有一個很復(fù)雜的表格來表示所有的級別關(guān)系情況哪些可以那些不行,但接口是不是也遵循這個點呢?實際上接口可以有很多,我可以指定它私下實現(xiàn)一些接口,但不用暴露在外,因此接口比基類類型還要復(fù)雜一些。
首先我們要說一個比較奇特的冷知識。接口是可以嵌套的。雖然我們在程序設(shè)計的時候基本上用不到這個點,所以很少有人知道,但接口確實在 C# 里也可以嵌套;而且,接口里的嵌套類型甚至不一定是接口,還可以是一個類;而普通類型里也可以嵌套一個接口,……啊這。
問題不在這里。我只是想告訴你,既然可以嵌套類型,那么訪問修飾級別就可以有很多情況。
我們先來說一下最簡單的情況,就是都不是嵌套的類型。這也是平時基本上就已經(jīng)夠用了的情況。假設(shè)我類型實現(xiàn)了接口,那么可以有這樣的情況:
public class A
和public interface B
;public class A
和internal interface B
;internal class A
和public interface B
;internal class A
和internal interface B
。
實際上這四種情況全部都可以。其中有一個比較奇怪的繼承關(guān)系,是類型繼承關(guān)系下不可能出現(xiàn)的,但這在接口里是可以被允許的。這類型的接口實現(xiàn)可以在私下自己實現(xiàn),然后不暴露給外界知道我有這一層實現(xiàn)接口的關(guān)系,這就是隱藏接口實現(xiàn)的一個慣用手法:將接口設(shè)置為 internal
,然后讓一個 public
類型實現(xiàn)該接口,這樣外界就不知道我這個繼承關(guān)系了。
而如果兩個都是接口的話……
public interface A
和public interface B
;public interface A
和internal interface B
;internal interface A
和public interface B
;internal interface A
和internal interface B
。
這種情況下呢?這個時候它和普通的繼承關(guān)系的約束是一致的,也就是說 public interface J : I
關(guān)系下,J
是 public
而 I
接口是 internal
的接口的話,這種關(guān)系是不可以的。
……算了我直接上結(jié)論吧,反正基本上沒有人用得上這種情況……用得上的也只需要查個表就行。

然后是接口和接口的繼承關(guān)系。

Part 4 遺留問題解答
下面我們針對前面介紹的內(nèi)容做一個總結(jié)和說明??赡苣銜邢旅娴膯栴}的困惑,我們都來解答一下。
4-1 為什么不能對接口成員的實現(xiàn)使用 new
修飾符?
要說不能,其實是沒有必要。這是接口的一大性質(zhì):因為接口支持隱式接口實現(xiàn),所以你即使不指明實現(xiàn)的接口名稱是哪個,編譯器自己也知道。因此,override
關(guān)鍵字也就不用寫出來了;我們站在另外一個角度來說的話,既然 override
關(guān)鍵字都不寫了,那么就說明“接口的成員被隱式實現(xiàn)”是一種正常的、默認的行為,因此加 new
修飾符就變得很奇怪:你是在類里給出的實現(xiàn),說明類里沒有同名、同簽名的別的成員。那么,你加上 new
是為了干什么呢?new
是用在同同,就連簽名(如果是方法、索引器或者運算符重載的話,就有簽名一說)都一樣的時候。隱式接口實現(xiàn)暗示了你的類里沒有這樣的情況,所以 new
關(guān)鍵字就顯得沒有意義。
4-2 顯式接口實現(xiàn)僅僅是為了避免不構(gòu)成重載而產(chǎn)生語法錯誤?
顯式接口實現(xiàn)并不是專門用來避免語法錯誤的。它還有一個特性,叫做“隱藏接口成員”。
拿我們前面舉例說明的這個 IA
的 SumUp
方法來說吧。我們使用了顯式接口實現(xiàn)來表達 SumUp
,這并不僅僅是“為了消除語法錯誤”而這么寫,它還有一個原因是隱藏掉 IA
里的 SumUp
這個成員。
所謂隱藏,和前面介紹過的 new
修飾符還有一點不一樣的感覺。前面說過了,接口可以實現(xiàn)很多個,顯式接口實現(xiàn)要指定接口名稱和實現(xiàn)的接口成員。既然被同時指定了,那么與其認為“這個成員顯式實現(xiàn)在類里”,還不如把它理解成是“這個成員在實例化的時候仍然看不見”。
它的訪問級別最小,小到什么程度呢?小到比 private
還小,因為你甚至就在同一個類型里使用這個成員,都是失敗的。舉個例子。假設(shè),我們在 TestClass
里創(chuàng)建一個 ?Output
方法,它里要輸出一系列數(shù)據(jù)的和:
int
作為返回值類型的 SumUp
方法也在這個 TestClass
類里面。但我們就在這個類里使用 SumUp

可以看到,它會告訴你“你沒辦法把 decimal
這個結(jié)果轉(zhuǎn)換為int
(你是不是少了一個強制轉(zhuǎn)換?)”。你可能會納悶,我明明有一個 int
返回值的方法 SumUp
,怎么告訴我要從 decimal
強制轉(zhuǎn)換過去?因為這個 int
返回值的 SumUp
方法被隱藏掉了,因此編譯器會自動定位到 decimal
的這個方法上去。然后又因為 decimal
到 int
的賦值是需要強制轉(zhuǎn)換的,因此就告知你“decimal
無法轉(zhuǎn)換為 int
,請問你是不是少了個強制轉(zhuǎn)換”。
這就是隱藏接口成員的一個特效:隱藏接口成員。這也是為什么我最開始說的這句話:“我們要把不常用的那個方法用顯式接口實現(xiàn)表示出來”的真正原因:因為顯式接口實現(xiàn)會隱藏掉這個成員,導(dǎo)致你無法看到它(它的訪問級別比 private
還要?。?。
不過,這個方法是真的再也看不到了嗎?實際上并不是,這一點我們在下一節(jié)“接口的多態(tài)”里會給大家介紹,把成員轉(zhuǎn)換成接口類型的實例來用的方式;這樣就可以看到這個隱藏成員了。
Part 5 總結(jié)
好久沒見到“總結(jié)”這個部分了。下面我們來對接口內(nèi)容進行一個整理和總結(jié)。
接口可包含屬性、索引器、事件(還沒講)和方法四大成員類型。接口的實現(xiàn)分顯式接口實現(xiàn)和隱式接口實現(xiàn)兩種。一般我們都用的是隱式接口實現(xiàn),因為方便;但是極少數(shù)情況下,接口可能會存在重名但不構(gòu)成重載的現(xiàn)象,這個時候我們需要使用顯式接口實現(xiàn)來避免語法問題。顯式接口實現(xiàn)會隱藏掉成員,所以請盡量使用隱式接口實現(xiàn)的模式,除非是迫不得已出現(xiàn)上面這樣的情況。
