標籤:

C#里的顯式介面實現是什麼原理?

今天偶然看到了這兩個文檔:

Explicit Interface Implementation Tutorial (C#)

Explicit Interface Implementation (C# Programming Guide)

很好奇這是什麼原理?是C#支持的還是CIL/CLS/CLR支持的?


針對 @李建忠 的回答說幾句。

包括老趙,好幾個同學都在說為了「避免命名衝突」, 這也是MSDN一些人給的所謂「官方理由」。

sign....繞這麼大彎子就為了一個 命名衝突.... 為什麼不直接改名字? 隱藏成員......好吧,真不知道為什麼要隱藏。 如果private,就乾乾脆脆private;如果public,就乾乾脆脆public。騎牆派設計,純粹為了給api的使用者增加心智負擔。

「改名字」不是解決命名衝突的辦法,原因很多,說幾個常見的情況。

1、介面不是你自己定義的,你只拿到dll,沒有源代碼。

2、介面是為不同組件/框架定義的,在各自組件中都是合理的命名,但命名有衝突。框架實現者沒法為了使用者而改動,實現者不可能知道使用者會和誰一起用。甚至因為要保證兼容性,根本沒法改。

3、有些介面成員在實現的時候,就是不準備給人用的。例如你要調用的一個方法接受IList參數,但不會修改它。你寫一個自己的類來實現IList時,就把不準備用的隱藏起來,甚至可以額外暴露一個同名方法供內部使用,此時它可以不是介面方法:

private class MyBuffer& : IList& {
public void Add&(T item) { ... }

public void Clear() { ... }

void IList&.Add(T item) {
throw new NotSupportedException();
}

void IList&.Clear(T item) {
throw new NotSupportedException();
}
}

這樣我們可以修改我們自己的Buffer(因為我們拿到的是MyBuffer類型),但作為IList&傳出去的時候,外部是無法修改的。但是在使用的時候,都很清楚Add就是Add的意思,Clear就是Clear的意思。

4. 命名是根據成員的概念來的,不能因為命名衝突就妥協。尤其是優秀的實踐是讓方法「單純」,越「單純」的方法名字越簡單,越容易衝突。由於絕大部分語言是不能通過返回值來區分方法的,這個需求更加重要。打個比方,我們自己寫了個TreeList類,它自然是要實現IList&介面的:

private class TreeList& : IList& {
public T RemoveAt(int index) {
...
}

void RemoveAt(int index) {
RemoveAt(index);
}
}

TreeList根據索引來獲取元素是O(log(N))的行為,所以說假如我要在刪除前拿到這個元素,則通過IList&.RemoveAt方法是做不到的,則必須之前取一次,這樣就是要訪問兩次。於是我們可以把IList&.RemoveAt隱藏起來,而實現一個帶返回值的RemoveAt。這裡RemoveAt是個好名字,難道只是因為和介面方法重名就改一個奇怪的名字?

另外,最後提到的「隱藏成員」不是private和public能做到的。假如一個類不想讓dynamic或WPF綁定訪問到一些屬性/方法,但又要實現某個介面的屬性/方法,就可以把這個屬性/方法定義為顯式實現,這樣就不會被使用者誤用。當你是框架或通用代碼提供者的時候,你就知道避免誤用是多麼重要的事情了。

總之,多寫一些代碼,多想辦法提高一點代碼質量,多想辦法提高一點代碼性能,多想一下如何與別人配合,就會知道顯式實現介面是多麼重要的設計。

最後,我不記得Jeffrey Richter說過這個設計是為了讓某些成員不要有太多類型。首先,「可以讓某些成員不要有太多類型」和「為了讓某些成員不要有太多類型」是兩個概念。其次,就算是他這麼說了,也不用盲信,他說錯了也就是錯了。


問的是顯式介面的實現原理。

首先搞清楚,介面的原理是什麼——跟C++的虛根類差不多一樣的東西。區別是,介面的成員,語法設計上,只能通過介面來訪問,而通過其實現類不可以直接訪問。說白了,介面的正體就是顯式介面。

但是每次都要轉換成介面才能訪問很不方便,因此C#弄了個方便法門——隱式介面,給實現介面的類多做一個函數,指向跟介面同名同參的函數,同時簡化了語法。這就是隱式介面的實質。

當然這種簡化是有代價的,佔用了一個函數。當這種方便變成不便,讓你覺得為難困擾的時候,就改用顯示介面吧,具體老趙的答案里已經說出

===============================================

補充:這並非一種「腦殘設計」,我們想一下天堂上的類多繼承——爸爸們帶來了多少困擾?這不是改名字就可以解決的,你可以改變自己,但是不能改變你爸爸。介面(顯式)解決了這問題。


顯式介面實現當初不一定是設計來給C#用的啊,也許C#最後只是順便就支持了……

VB就沒有什麼隱式顯式的概念,它只支持顯式介面實現,也就是說每個介面方法都要顯示指定

相對於C#EIMI的不優雅,VB裡面更強大更清爽,而且Public的方法也能用來顯示實現,也不存在不能被派生之類的限制


沒啥原理,無非就是在metadata層面,當以類的面貌出現時,將metadata設為private;當以介面面貌出現時,將metadata設為public罷了。

不過,顯式介面實現絕對是一個腦殘的設計。 我記得Jeffrey Richter說這個設計是為了 讓某些成員太多的類型(比如Int32)顯得少一些。 這是我歷史上看到最搞笑的理由了.....

-----------------------------------------------------------------------------------------

包括老趙,好幾個同學都在說為了「避免命名衝突」, 這也是MSDN一些人給的所謂「官方理由」。

sign....繞這麼大彎子就為了一個 命名衝突.... 為什麼不直接改名字? 隱藏成員......好吧,真不知道為什麼要隱藏。 如果private,就乾乾脆脆private;如果public,就乾乾脆脆public。騎牆派設計,純粹為了給api的使用者增加心智負擔。

保留「腦殘設計」的評價。


這個顯示介面應用還是比較廣泛的 , 記得上個月吧 , 有一個新手問我的問題如下:

請問,我的A程序集中的User Class 中 ,Name , Age . Get 是 Public , Set 是Internal 訪問修飾符,但是我還想讓B程序集可以賦值 . 如何解決呢 ?

public class UserInformation
{
public string Name { get; set; }

public uint Age { get; internal set; }
}

----------------------------------------------------------- 貼心 , 用心 分割 --------------------------------------------------------

我給這位朋友提供了兩種方案:

第一種方案:java Getter ,Setter 模式.

public class UserInformation
{
public string Name { get; internal set; }

public uint Age { get; set; }

public void SetName(string name)
{
Name = name;
}
}

第二種方案:C# 顯示介面實現模式 , 我給這個模式起名叫 「讀寫裝飾」

public interface IInformation
{
string Name { get; }

uint Age { get; }
}

public interface IMutableInformation : IInformation
{
new string Name { set; }

new uint Age { set; }
}

public class UserInformation : IInformation , IMutableInformation
{
private string _Name = string.Empty;
private uint _Age = default(uint);

public string Name { get { return _Name; } }

public uint Age { get { return _Age; } }

string IMutableInformation.Name
{
set { _Name = value; }
}

uint IMutableInformation.Age
{
set { _Age = value; }
}
}

// 開始測試啦.....
using System;
using static Console;

//using A

class Program
{
static void Main(string[] args)
{

A.UserInformation information = new A.UserInformation();
A.IMutableInformation mutableInformation = information;
mutableInformation.Name = "張三丰";
mutableInformation.Age = 102;

WriteLine(information.Name);
WriteLine(information.Age);

// information.Name = "張三丰"; // 這樣,此處編譯的時候會出錯,你需要用【IMutableInformation】介面 給【A.UserInformation】賦值.

ReadLine();
}
}

總而言之 , 好處還是有的 , 不行其他人 , 說的那麼一無是處 . 我喜歡C# "靈活", 就是因為 「自由」 .....

忘記補上官方的解釋了:

如果類實現兩個介面,並且這兩個介面包含具有相同簽名的成員,那麼在類中實現該成員將導致兩個介面都使用該成員作為它們的實現。 在下面的示例中,所有對 Paint 調用方法相同。


推薦閱讀:

如何開始學習CoreCLR源代碼?
Stack-based 的虛擬機有什麼常用的優化策略?
RyuJIT為什麼比JIT64編譯速度快?
程序集什麼玩意?我知道其表現形式為dll和exe,但是exe不是直接執行的文件嗎?而dll只是類庫,供exe調用代碼?

TAG:C# | CLR |