C#中接口默認方法是什么?相信很多新手小白對C#中接口默認方法的了解處于懵懂狀態(tài),通過這篇文章的總結,希望你能有所收獲。如下資料是關于接口默認方法的內(nèi)容。
創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站設計、做網(wǎng)站與策劃設計,松陽網(wǎng)站建設哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設10多年,網(wǎng)設計領域的專業(yè)建站公司;建站業(yè)務涵蓋:松陽等地區(qū)。松陽做網(wǎng)站價格咨詢:18980820575interface IStringList {
void Add(string o); // 添加元素
void Remove(int i); // 刪除元素
string Get(int i); // 獲取元素
int Length { get; } // 獲取列表長度
}
不管怎么說,這個列表已經(jīng)擁有了基本的增刪除改查功能,比如遍歷,可以這樣寫
IStringList list = createList();
for (var i = 0; i < list.Length; i++) {
string o = list.Get(i);
// Do something with o
}
這個IStringList
作為一個基礎接口在類庫中發(fā)布之后,大量的程序員使用了這個接口,實現(xiàn)了一堆各種各種各樣的列表,像StringArrayList
、LinkedStringList
、StringQueue
、StringStack
、SortedStringList
……有抽象類,有擴展接口,也有各種實現(xiàn)類??傊?jīng)過較長一段時間的積累,IStringList
的子孫遍布全球。
然后IStringList
的發(fā)明者,決定為列表定義更多的方法,以適合在技術飛速發(fā)展下開發(fā)者們對IStringList
使用便捷性的要求,于是
interface IStringList {
int IndexOf(string o); // 查找元素的索引,未找到返回 -1
void Insert(string o, int i); // 在指定位置插入元素
// ------------------------------
void Add(string o); // 添加元素
void Remove(int i); // 刪除元素
string Get(int i); // 獲取元素
int Length { get; } // 獲取列表長度
}
當然,接口變化之外所有實現(xiàn)類都必須實現(xiàn)它,不然編譯器會報錯,基礎庫的抽象類AbstractStringList
中實現(xiàn)了上述新增加的接口。整個基礎庫完美編譯,發(fā)布了 2.0 版本。
然而,現(xiàn)實非常殘酷!
基礎庫的用戶們(開發(fā)者)發(fā)出了極大的報怨聲,因為他們太多代碼編譯不過了!
是的,并不是所有用戶都會直接繼承AbstractStringList
,很多用戶直接實現(xiàn)了IStringList
。還有不少用戶甚至擴展了IStringList
,但他們沒有定義int IndexOf(string o)
而是定義的int Find(string o)
。由于基礎庫接口IStringList
的變化,用戶們需要花大量地時間去代碼來實現(xiàn)IStringList
中定義的新方法。
這個例子是提到了IStringList
,只添加了兩個方法。這對用戶造成的麻煩雖然已經(jīng)不小,但工作量還算可以接受。但是想想 JDK 和 .NET Framework/Core 龐大的基礎庫,恐怕用戶只能用“崩潰”來形容!
肯定不能讓用戶崩潰,得想辦法解決這個問題。于是,Java 和 C# 的兩個方案出現(xiàn)了
不得不說 C# 的擴展方法很聰明,但它畢竟不是真正對接口進行擴展,所以在 C# 8 中也加入了默認方法來解決接口擴展造成的問題。
接口擴展方法提出來之后,雖然解決了默認實現(xiàn)的問題,卻又帶出了新的問題。
忽略上面IStringList
接口中補充的Insert(Object, int)
方法,我們把關注點放在IndexOf(Object)
上。Java 和 C# 的語法異曲同工:
interface StringList {
void add(Object s);
void remove(int i);
Object get(int i);
int getLength();
default int indexOf(Object s) {
for (int i = 0; i < getLength(); i++) {
if (get(i) == s) { return i; }
}
return -1;
}
}
interface IStringList
{
public void Add(string s);
void Remove(int i);
string Get(int i);
int Length { get; }
int IndexOf(string s)
{
for (var i = 0; i < Length; i++)
{
if (Get(i) == s) { return i; }
}
return -1;
}
}
這里把 C# 和 Java 的接口都寫出來,主要是因為二者講法和命名規(guī)范略有不同。接下來進行的研究 C# 和 Java 行為相似的地方,就主要以 C# 為例了。
怎么區(qū)分是 C# 示例還是 Java 示例?看代碼規(guī)范,最明顯的是 C# 方法用 Pascal 命名規(guī)則,Java 方法用 camel 命名規(guī)則。當然,還有 Lambda 的箭頭也不一樣。
接下來的實現(xiàn),僅以 C# 為例:
class MyList : IStringList
{
List<string> list = new List<string>(); // 偷懶用現(xiàn)成的
public int Length => list.Count;
public void Add(string o) => list.Add(o);
public string Get(int i) => list[i];
public void Remove(int i) => list.RemoveAt(i);
}
MyList
沒有實現(xiàn)IndexOf
,但是使用起來不會有任何問題
class Program
{
static void Main(string[] args)
{
IStringList myList = new MyList();
myList.Add("First");
myList.Add("Second");
myList.Add("Third");
Console.WriteLine(myList.IndexOf("Third")); // 輸出 2
Console.WriteLine(myList.IndexOf("first")); // 輸出 -1,注意 first 大小寫
}
}
現(xiàn)在,在MyList
中添加IndexOf
,實現(xiàn)對字符串忽略大小寫的查找:
// 這里用 partial class 表示是部分實現(xiàn),
// 對不住 javaer,Java 沒有部分類語法
partial class MyList
{
public int IndexOf(string s)
{
return list.FindIndex(el =>
{
return el == s
|| (el != null && el.Equals(s, StringComparison.OrdinalIgnoreCase));
});
}
}
然后Main
函數(shù)中輸出的內(nèi)容變了
Console.WriteLine(myList.IndexOf("Third")); // 還是返回 2
Console.WriteLine(myList.IndexOf("first")); // 返回 0,不是 -1
顯然這里調(diào)用了MyList.IndexOf()
。
上面主要是以 C# 作為示例,其實 Java 也是一樣的。上面的示例中是通過接口類型來調(diào)用的IndexOf
方法。第一次調(diào)用的是IStringList.IndexOf
默認實現(xiàn),因為這時候MyList
并沒有實現(xiàn)IndexOf
;第二次調(diào)用的是MyList.IndexOf
實現(xiàn)。筆者使用 Java 寫了類似的代碼,行為完全一致。
因此,對于默認方法,會優(yōu)先調(diào)用類中的實現(xiàn),如果類中沒有實現(xiàn)具有默認方法的接口,才會去調(diào)用接口中的默認方法。
但是?。?!前面的示例是使用的接口類型引用實現(xiàn),如果換成實例類類型來引用實例呢?
如果MyList
中實現(xiàn)了IndexOf
,那結果沒什么區(qū)別。但是如果MyList
中沒有實現(xiàn)IndexOf
的時候,Java 和 C# 在處理上有就區(qū)別了。
先看看 C# 的Main
函數(shù),編譯不過(Compiler Error CS1929),因為MyList
中沒有定義IndexOf
。
而 Java 呢?通過了,一如既往的運行出了結果!
從 C# 的角度來看,MyList
既然知道有IndexOf
接口,那就應該實現(xiàn)它,而不能假裝不知道。但是如果通過IStringList
來調(diào)用IndexOf
,那么就可以認為MyList
并不知道有IndexOf
接口,因此允許調(diào)用默認接口。接口還是接口,不知道有新接口方法,沒實現(xiàn),不怪你;但是你明知道還不實現(xiàn),那就是你的不對了。
但從 Java 的角度來看,MyList
的消費者并不一定是MyList
的生產(chǎn)者。從消費者的角度來看,MyList
實現(xiàn)了StringList
接口,而接口定義有indexOf
方法,所以消費者調(diào)用myList.indexOf
是合理的。
Java 的行為相對寬松,只要有實現(xiàn)你就用,不要管是什么實現(xiàn)。
而 C# 的行為更為嚴格,消費者在使用的時候可以通過編譯器很容易了解到自己使用的是類實現(xiàn),還是接口中的默認實現(xiàn)(雖然知道了也沒多少用)。實際上,如果沒在在類里面實現(xiàn),接口文檔中就不會寫出來相關的接口,編輯器的智能提示也不會彈出來。實在要寫,可以顯示轉換為接口來調(diào)用:
Console.WriteLine(((IStringList)myList).IndexOf("Third"));
而且根據(jù)上面的試驗結果,將來MyList
實現(xiàn)了IndexOf
之后,這樣的調(diào)用會直接切換到調(diào)用MyList
中的實現(xiàn),不會產(chǎn)生語義上的問題。
無論 Java 還是 C# 都不允許類多繼承,但是接口可以。而接口中的默認實現(xiàn)帶來了類似于類多繼承所產(chǎn)生的問題,怎么辦?
舉個例,人可以走,鳥也可以走,那么“云中君”該怎么走?
類中不實現(xiàn)默認接口的情況:
interface IPerson
{
void Walk() => Console.WriteLine("IPerson.Walk()");
}
interface IBird
{
void Walk() => Console.WriteLine("IBird.Walk()");
}
class BirdPerson : IPerson, IBird { }
調(diào)用結果:
BirdPerson birdPerson = new BirdPerson();
// birdPerson.Walk(); // CS1061,沒有實現(xiàn) Walk
((IPerson)birdPerson).Walk(); // 輸出 IPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 IBird.Walk()
不能直接使用birdPerson.Walk()
,道理前面已經(jīng)講過。不過通過不同的接口類型來調(diào)用,行為是不一致的,完全由接口的默認方法來決定。這也可以理解,既然類沒有自己的實現(xiàn),那么用什么接口來引用,說明開發(fā)者希望使用那個接口所規(guī)定的默認行為。
說得直白一點,你把云中君看作人,他就用人的走法;你把云中君看作鳥,它就用鳥的走法。
然而,如果類中有實現(xiàn),情況就不一樣了:
class BirdPerson : IPerson, IBird
{
// 注意這里的 public 可不能少
public void Walk() => Console.WriteLine("BirdPerson.Walk()");
}
BirdPerson birdPerson = new BirdPerson();
birdPerson.Test(); // 輸出 BirdPerson.Walk()
((IPerson)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
輸出完全一致,接口中定義的默認行為,在類中有實現(xiàn)的時候,就當不存在!
云中君有個性:不管你怎么看,我就這么走。
這里唯一需要注意的是BirdPerson
中實現(xiàn)的Walk()
必須聲明為public
,否則 C# 會把它當作類的內(nèi)部行為,而不是實現(xiàn)的接口行為。這一點和 C# 對實現(xiàn)接口方法的要求是一致的:實現(xiàn)接口成員必須聲明為public
。
轉到 Java 這邊,情況就不同了,編譯根本不讓過
interface Person {
default void walk() {
out.println("IPerson.walk()");
}
}
interface Bird {
default void walk() {
out.println("Bird.walk()");
}
}
// Duplicate default methods named walk with the parameters () and ()
// are inherited from the types Bird and Person
class BirdPerson implements Person, Bird { }
這個意思就是,Person
和Bird
都為簽名相同的walk
方法定義了默認現(xiàn),所以編譯器不知道BirdPerson
到底該怎么辦了。那么如果只有一個walk
有默認實現(xiàn)呢?
interface Person {
default void walk() {
out.println("IPerson.walk()");
}
}
interface Bird {
void walk();
}
// The default method walk() inherited from Person conflicts
// with another method inherited from Bird
class BirdPerson implements Person, Bird { }
這意思是,兩個接口行為不一致,編譯器還是不知道該怎么處理BirdPerson
。
總之,不管怎么樣,就是要BirdPerson
必須實現(xiàn)自己的walk()
。既然BirdPerson
自己實現(xiàn)了walk()
,那調(diào)用行為也就沒有什么懸念了:
BirdPerson birdPerson = new BirdPerson();
birdPerson.walk(); // 輸出 BirdPerson.walk()
((Person) birdPerson).walk(); // 輸出 BirdPerson.walk()
((Bird) birdPerson).walk(); // 輸出 BirdPerson.walk()
如果一個類實現(xiàn)的多個接口中定義了相同簽名的方法,沒有默認實現(xiàn)的情況下,當然不會有問題。
如果類中實現(xiàn)了這個簽名的方法,那無論如何,調(diào)用的都是這個方法,也不會有問題。
但在接口有默認實現(xiàn),而類中沒有實現(xiàn)的情況下,C# 將實際行為交給引用類型去處理;Java 則直接報錯,交給開發(fā)者去處理。筆者比較贊同 C# 的做法,畢竟默認方法的初衷就是為了不強制開發(fā)者去處理增加接口方法帶來的麻煩。
對于更復雜的情況,多數(shù)時候還是可以猜到會怎么去調(diào)用的,畢竟有個基本原則在那里。
比如,WalkBase
定義了Walk()
方法,但沒實現(xiàn)任何接口,BirdPerson
從WalkBase
繼承,實現(xiàn)了IPerson
接口,但沒實現(xiàn)Walk()
方法,那么該執(zhí)行哪個Walk
呢?
會執(zhí)行WalkBase.Walk()
——不管什么情況下,類方法優(yōu)先!
class WalkBase
{
public void Walk() => Console.WriteLine("WalkBase.Walk()");
}
class BirdPerson : WalkBase, IPerson { }
static void Main(string[] args)
{
BirdPerson birdPerson = new BirdPerson();
birdPerson.Walk(); // 輸出 WalkBase.Walk()
((IPerson)birdPerson).Walk(); // 輸出 WalkBase.Walk()
}
如果父類子類都有實現(xiàn),但子類不是“重載”,而是“覆蓋”實現(xiàn),那要根據(jù)引用類型來找最近的類,比如
class WalkBase : IBird // <== 注意這里實現(xiàn)了 IBird
{
public void Walk() => Console.WriteLine("WalkBase.Walk()");
}
class BirdPerson : WalkBase, IPerson // <== 這里_沒有_實現(xiàn) IBird
{
// 注意:這里是 new,而不是 override
public new void Walk() => Console.WriteLine("BirdPerson.Walk()");
}
static void Main(string[] args)
{
BirdPerson birdPerson = new BirdPerson();
birdPerson.Walk(); // 輸出 BirdPerson.Walk()
((WalkBase)birdPerson).Walk(); // 輸出 WalkBase.Walk()
((IPerson)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 WalkBase.Walk()
}
如果WalkBase
中以virtual
定義Walk()
,而BirdPerson
中以override
定義Walk()
,那毫無懸念輸出全都是BirdPerson.Walk()
。
class WalkBase : IBird
{
public virtual void Walk() => Console.WriteLine("WalkBase.Walk()");
}
class BirdPerson : WalkBase, IPerson
{
public override void Walk() => Console.WriteLine("BirdPerson.Walk()");
}
static void Main(string[] args)
{
BirdPerson birdPerson = new BirdPerson();
birdPerson.Walk(); // 輸出 BirdPerson.Walk()
((WalkBase)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
((IPerson)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 BirdPerson.Walk()
}
上面示例中的候最后一句輸出,是通過IBird.Walk()
找到WalkBase.Walk()
,而WalkBase.Walk()
又通過虛方法鏈找到BirdPerson.Walk()
,所以輸出仍然是BirdPerson.Walk()
。學過 C++ 的同學這時候可能就會很有感覺了!
至于 Java,所有方法都是虛方法。雖然可以通過final
讓它非虛,但是在子類中不能定義相同簽名的方法,所以 Java 的情況會更簡單一些。
還是拿WalkBase
和BirdPerson
分別實現(xiàn)了IBird
和IPerson
的例子,
class WalkBase : IBird { }
class BirdPerson : WalkBase, IPerson { }
((IPerson)birdPerson).Walk(); // 輸出 IPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 IBird.Walk()
哦,當然 Java 中不存在,因為編譯器會要求必須實現(xiàn)BirdPerson.Walk()
。
以上就是C#中接口默認方法的詳細內(nèi)容了,看完之后是否有所收獲呢?如果想了解更多相關內(nèi)容,歡迎關注創(chuàng)新互聯(lián)網(wǎng)站制作公司行業(yè)資訊!
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡助力業(yè)務部署。公司持有工信部辦法的idc、isp許可證, 機房獨有T級流量清洗系統(tǒng)配攻擊溯源,準確進行流量調(diào)度,確保服務器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務器買多久送多久。
分享名稱:C#中的接口默認方法是什么?-創(chuàng)新互聯(lián)
文章分享:http://chinadenli.net/article34/dhjipe.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供面包屑導航、做網(wǎng)站、網(wǎng)站設計公司、Google、域名注冊、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容