C#中接口默認(rèn)方法是什么?相信很多新手小白對(duì)C#中接口默認(rèn)方法的了解處于懵懂狀態(tài),通過(guò)這篇文章的總結(jié),希望你能有所收獲。如下資料是關(guān)于接口默認(rèn)方法的內(nèi)容。
我們提供的服務(wù)有:網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、襄陽(yáng)ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢(xún)和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的襄陽(yáng)網(wǎng)站制作公司
interface IStringList {
void Add(string o); // 添加元素
void Remove(int i); // 刪除元素
string Get(int i); // 獲取元素
int Length { get; } // 獲取列表長(zhǎng)度
}
不管怎么說(shuō),這個(gè)列表已經(jīng)擁有了基本的增刪除改查功能,比如遍歷,可以這樣寫(xiě)
IStringList list = createList();
for (var i = 0; i < list.Length; i++) {
string o = list.Get(i);
// Do something with o
}
這個(gè) IStringList
作為一個(gè)基礎(chǔ)接口在類(lèi)庫(kù)中發(fā)布之后,大量的程序員使用了這個(gè)接口,實(shí)現(xiàn)了一堆各種各種各樣的列表,像 StringArrayList
、LinkedStringList
、StringQueue
、StringStack
、SortedStringList
……有抽象類(lèi),有擴(kuò)展接口,也有各種實(shí)現(xiàn)類(lèi)??傊?jīng)過(guò)較長(zhǎng)一段時(shí)間的積累,IStringList
的子孫遍布全球。
然后 IStringList
的發(fā)明者,決定為列表定義更多的方法,以適合在技術(shù)飛速發(fā)展下開(kāi)發(fā)者們對(duì) 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; } // 獲取列表長(zhǎng)度
}
當(dāng)然,接口變化之外所有實(shí)現(xiàn)類(lèi)都必須實(shí)現(xiàn)它,不然編譯器會(huì)報(bào)錯(cuò),基礎(chǔ)庫(kù)的抽象類(lèi) AbstractStringList
中實(shí)現(xiàn)了上述新增加的接口。整個(gè)基礎(chǔ)庫(kù)完美編譯,發(fā)布了 2.0 版本。
然而,現(xiàn)實(shí)非常殘酷!
基礎(chǔ)庫(kù)的用戶(hù)們(開(kāi)發(fā)者)發(fā)出了極大的報(bào)怨聲,因?yàn)樗麄兲啻a編譯不過(guò)了!
是的,并不是所有用戶(hù)都會(huì)直接繼承 AbstractStringList
,很多用戶(hù)直接實(shí)現(xiàn)了 IStringList
。還有不少用戶(hù)甚至擴(kuò)展了 IStringList
,但他們沒(méi)有定義 int IndexOf(string o)
而是定義的 int Find(string o)
。由于基礎(chǔ)庫(kù)接口 IStringList
的變化,用戶(hù)們需要花大量地時(shí)間去代碼來(lái)實(shí)現(xiàn) IStringList
中定義的新方法。
這個(gè)例子是提到了 IStringList
,只添加了兩個(gè)方法。這對(duì)用戶(hù)造成的麻煩雖然已經(jīng)不小,但工作量還算可以接受。但是想想 JDK 和 .NET Framework/Core 龐大的基礎(chǔ)庫(kù),恐怕用戶(hù)只能用“崩潰”來(lái)形容!
肯定不能讓用戶(hù)崩潰,得想辦法解決這個(gè)問(wèn)題。于是,Java 和 C# 的兩個(gè)方案出現(xiàn)了
不得不說(shuō) C# 的擴(kuò)展方法很聰明,但它畢竟不是真正對(duì)接口進(jìn)行擴(kuò)展,所以在 C# 8 中也加入了默認(rèn)方法來(lái)解決接口擴(kuò)展造成的問(wèn)題。
接口擴(kuò)展方法提出來(lái)之后,雖然解決了默認(rèn)實(shí)現(xiàn)的問(wèn)題,卻又帶出了新的問(wèn)題。
忽略上面 IStringList
接口中補(bǔ)充的 Insert(Object, int)
方法,我們把關(guān)注點(diǎn)放在 IndexOf(Object)
上。Java 和 C# 的語(yǔ)法異曲同工:
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 的接口都寫(xiě)出來(lái),主要是因?yàn)槎咧v法和命名規(guī)范略有不同。接下來(lái)進(jìn)行的研究 C# 和 Java 行為相似的地方,就主要以 C# 為例了。
怎么區(qū)分是 C# 示例還是 Java 示例?看代碼規(guī)范,最明顯的是 C# 方法用 Pascal 命名規(guī)則,Java 方法用 camel 命名規(guī)則。當(dāng)然,還有 Lambda 的箭頭也不一樣。
接下來(lái)的實(shí)現(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
沒(méi)有實(shí)現(xiàn) IndexOf
,但是使用起來(lái)不會(huì)有任何問(wèn)題
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ě)
}
}
現(xiàn)在,在 MyList
中添加 IndexOf
,實(shí)現(xiàn)對(duì)字符串忽略大小寫(xiě)的查找:
// 這里用 partial class 表示是部分實(shí)現(xiàn),
// 對(duì)不住 javaer,Java 沒(méi)有部分類(lèi)語(yǔ)法
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# 作為示例,其實(shí) Java 也是一樣的。上面的示例中是通過(guò)接口類(lèi)型來(lái)調(diào)用的 IndexOf
方法。第一次調(diào)用的是 IStringList.IndexOf
默認(rèn)實(shí)現(xiàn),因?yàn)檫@時(shí)候 MyList
并沒(méi)有實(shí)現(xiàn) IndexOf
;第二次調(diào)用的是 MyList.IndexOf
實(shí)現(xiàn)。筆者使用 Java 寫(xiě)了類(lèi)似的代碼,行為完全一致。
因此,對(duì)于默認(rèn)方法,會(huì)優(yōu)先調(diào)用類(lèi)中的實(shí)現(xiàn),如果類(lèi)中沒(méi)有實(shí)現(xiàn)具有默認(rèn)方法的接口,才會(huì)去調(diào)用接口中的默認(rèn)方法。
但是?。?!前面的示例是使用的接口類(lèi)型引用實(shí)現(xiàn),如果換成實(shí)例類(lèi)類(lèi)型來(lái)引用實(shí)例呢?
如果 MyList
中實(shí)現(xiàn)了 IndexOf
,那結(jié)果沒(méi)什么區(qū)別。但是如果 MyList
中沒(méi)有實(shí)現(xiàn) IndexOf
的時(shí)候,Java 和 C# 在處理上有就區(qū)別了。
先看看 C# 的 Main
函數(shù),編譯不過(guò)(Compiler Error CS1929),因?yàn)?MyList
中沒(méi)有定義 IndexOf
。
而 Java 呢?通過(guò)了,一如既往的運(yùn)行出了結(jié)果!
從 C# 的角度來(lái)看,MyList
既然知道有 IndexOf
接口,那就應(yīng)該實(shí)現(xiàn)它,而不能假裝不知道。但是如果通過(guò) IStringList
來(lái)調(diào)用 IndexOf
,那么就可以認(rèn)為 MyList
并不知道有 IndexOf
接口,因此允許調(diào)用默認(rèn)接口。接口還是接口,不知道有新接口方法,沒(méi)實(shí)現(xiàn),不怪你;但是你明知道還不實(shí)現(xiàn),那就是你的不對(duì)了。
但從 Java 的角度來(lái)看,MyList
的消費(fèi)者并不一定是 MyList
的生產(chǎn)者。從消費(fèi)者的角度來(lái)看,MyList
實(shí)現(xiàn)了 StringList
接口,而接口定義有 indexOf
方法,所以消費(fèi)者調(diào)用 myList.indexOf
是合理的。
Java 的行為相對(duì)寬松,只要有實(shí)現(xiàn)你就用,不要管是什么實(shí)現(xiàn)。
而 C# 的行為更為嚴(yán)格,消費(fèi)者在使用的時(shí)候可以通過(guò)編譯器很容易了解到自己使用的是類(lèi)實(shí)現(xiàn),還是接口中的默認(rèn)實(shí)現(xiàn)(雖然知道了也沒(méi)多少用)。實(shí)際上,如果沒(méi)在在類(lèi)里面實(shí)現(xiàn),接口文檔中就不會(huì)寫(xiě)出來(lái)相關(guān)的接口,編輯器的智能提示也不會(huì)彈出來(lái)。實(shí)在要寫(xiě),可以顯示轉(zhuǎn)換為接口來(lái)調(diào)用:
Console.WriteLine(((IStringList)myList).IndexOf("Third"));
而且根據(jù)上面的試驗(yàn)結(jié)果,將來(lái) MyList
實(shí)現(xiàn)了 IndexOf
之后,這樣的調(diào)用會(huì)直接切換到調(diào)用 MyList
中的實(shí)現(xiàn),不會(huì)產(chǎn)生語(yǔ)義上的問(wèn)題。
無(wú)論 Java 還是 C# 都不允許類(lèi)多繼承,但是接口可以。而接口中的默認(rèn)實(shí)現(xiàn)帶來(lái)了類(lèi)似于類(lèi)多繼承所產(chǎn)生的問(wèn)題,怎么辦?
舉個(gè)例,人可以走,鳥(niǎo)也可以走,那么“云中君”該怎么走?
類(lèi)中不實(shí)現(xiàn)默認(rèn)接口的情況:
interface IPerson
{
void Walk() => Console.WriteLine("IPerson.Walk()");
}
interface IBird
{
void Walk() => Console.WriteLine("IBird.Walk()");
}
class BirdPerson : IPerson, IBird { }
調(diào)用結(jié)果:
BirdPerson birdPerson = new BirdPerson();
// birdPerson.Walk(); // CS1061,沒(méi)有實(shí)現(xiàn) Walk
((IPerson)birdPerson).Walk(); // 輸出 IPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 IBird.Walk()
不能直接使用 birdPerson.Walk()
,道理前面已經(jīng)講過(guò)。不過(guò)通過(guò)不同的接口類(lèi)型來(lái)調(diào)用,行為是不一致的,完全由接口的默認(rèn)方法來(lái)決定。這也可以理解,既然類(lèi)沒(méi)有自己的實(shí)現(xiàn),那么用什么接口來(lái)引用,說(shuō)明開(kāi)發(fā)者希望使用那個(gè)接口所規(guī)定的默認(rèn)行為。
說(shuō)得直白一點(diǎn),你把云中君看作人,他就用人的走法;你把云中君看作鳥(niǎo),它就用鳥(niǎo)的走法。
然而,如果類(lèi)中有實(shí)現(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()
輸出完全一致,接口中定義的默認(rèn)行為,在類(lèi)中有實(shí)現(xiàn)的時(shí)候,就當(dāng)不存在!
云中君有個(gè)性:不管你怎么看,我就這么走。
這里唯一需要注意的是 BirdPerson
中實(shí)現(xiàn)的 Walk()
必須聲明為 public
,否則 C# 會(huì)把它當(dāng)作類(lèi)的內(nèi)部行為,而不是實(shí)現(xiàn)的接口行為。這一點(diǎn)和 C# 對(duì)實(shí)現(xiàn)接口方法的要求是一致的:實(shí)現(xiàn)接口成員必須聲明為 public
。
轉(zhuǎn)到 Java 這邊,情況就不同了,編譯根本不讓過(guò)
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 { }
這個(gè)意思就是,Person
和 Bird
都為簽名相同的 walk
方法定義了默認(rèn)現(xiàn),所以編譯器不知道 BirdPerson
到底該怎么辦了。那么如果只有一個(gè) walk
有默認(rèn)實(shí)現(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 { }
這意思是,兩個(gè)接口行為不一致,編譯器還是不知道該怎么處理 BirdPerson
。
總之,不管怎么樣,就是要 BirdPerson
必須實(shí)現(xiàn)自己的 walk()
。既然 BirdPerson
自己實(shí)現(xiàn)了 walk()
,那調(diào)用行為也就沒(méi)有什么懸念了:
BirdPerson birdPerson = new BirdPerson();
birdPerson.walk(); // 輸出 BirdPerson.walk()
((Person) birdPerson).walk(); // 輸出 BirdPerson.walk()
((Bird) birdPerson).walk(); // 輸出 BirdPerson.walk()
如果一個(gè)類(lèi)實(shí)現(xiàn)的多個(gè)接口中定義了相同簽名的方法,沒(méi)有默認(rèn)實(shí)現(xiàn)的情況下,當(dāng)然不會(huì)有問(wèn)題。
如果類(lèi)中實(shí)現(xiàn)了這個(gè)簽名的方法,那無(wú)論如何,調(diào)用的都是這個(gè)方法,也不會(huì)有問(wèn)題。
但在接口有默認(rèn)實(shí)現(xiàn),而類(lèi)中沒(méi)有實(shí)現(xiàn)的情況下,C# 將實(shí)際行為交給引用類(lèi)型去處理;Java 則直接報(bào)錯(cuò),交給開(kāi)發(fā)者去處理。筆者比較贊同 C# 的做法,畢竟默認(rèn)方法的初衷就是為了不強(qiáng)制開(kāi)發(fā)者去處理增加接口方法帶來(lái)的麻煩。
對(duì)于更復(fù)雜的情況,多數(shù)時(shí)候還是可以猜到會(huì)怎么去調(diào)用的,畢竟有個(gè)基本原則在那里。
比如,WalkBase
定義了 Walk()
方法,但沒(méi)實(shí)現(xiàn)任何接口,BirdPerson
從 WalkBase
繼承,實(shí)現(xiàn)了 IPerson
接口,但沒(méi)實(shí)現(xiàn) Walk()
方法,那么該執(zhí)行哪個(gè) Walk
呢?
會(huì)執(zhí)行 WalkBase.Walk()
——不管什么情況下,類(lèi)方法優(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()
}
如果父類(lèi)子類(lèi)都有實(shí)現(xiàn),但子類(lèi)不是“重載”,而是“覆蓋”實(shí)現(xiàn),那要根據(jù)引用類(lèi)型來(lái)找最近的類(lèi),比如
class WalkBase : IBird // <== 注意這里實(shí)現(xiàn)了 IBird
{
public void Walk() => Console.WriteLine("WalkBase.Walk()");
}
class BirdPerson : WalkBase, IPerson // <== 這里_沒(méi)有_實(shí)現(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()
,那毫無(wú)懸念輸出全都是 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()
}
上面示例中的候最后一句輸出,是通過(guò) IBird.Walk()
找到 WalkBase.Walk()
,而 WalkBase.Walk()
又通過(guò)虛方法鏈找到 BirdPerson.Walk()
,所以輸出仍然是 BirdPerson.Walk()
。學(xué)過(guò) C++ 的同學(xué)這時(shí)候可能就會(huì)很有感覺(jué)了!
至于 Java,所有方法都是虛方法。雖然可以通過(guò) final
讓它非虛,但是在子類(lèi)中不能定義相同簽名的方法,所以 Java 的情況會(huì)更簡(jiǎn)單一些。
還是拿 WalkBase
和 BirdPerson
分別實(shí)現(xiàn)了 IBird
和 IPerson
的例子,
class WalkBase : IBird { }
class BirdPerson : WalkBase, IPerson { }
((IPerson)birdPerson).Walk(); // 輸出 IPerson.Walk()
((IBird)birdPerson).Walk(); // 輸出 IBird.Walk()
哦,當(dāng)然 Java 中不存在,因?yàn)榫幾g器會(huì)要求必須實(shí)現(xiàn) BirdPerson.Walk()
。
以上就是C#中接口默認(rèn)方法的詳細(xì)內(nèi)容了,看完之后是否有所收獲呢?如果想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊!
分享標(biāo)題:C#中的接口默認(rèn)方法是什么?
路徑分享:http://muchs.cn/article42/jpighc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、定制網(wǎng)站、網(wǎng)站內(nèi)鏈、微信公眾號(hào)、品牌網(wǎng)站制作、靜態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)