【5min+】 巨大的争议?C# 8 中的接口
介紹
【五分鐘的dotnet】是一個(gè)利用您的碎片化時(shí)間來學(xué)習(xí)和豐富.net知識(shí)的博文系列。它所包含了.net體系中可能會(huì)涉及到的方方面面,比如C#的小細(xì)節(jié),AspnetCore,微服務(wù)中的.net知識(shí)等等。
5min+不是超過5分鐘的意思,"+"是知識(shí)的增加。so,它是讓您花費(fèi)5分鐘以下的時(shí)間來提升您的知識(shí)儲(chǔ)備量。
正文
伴隨著 .NET Core 3.0 一起發(fā)布的 C# 8 ,從發(fā)布至今已經(jīng)過了快大半年了。如果您細(xì)心的話,就能發(fā)現(xiàn)在C# 8新增的功能中有一條:“默認(rèn)接口方法”?。半年前當(dāng)我看到這一新特性的時(shí)候,我驚呆了,但是驚訝之余是更多的疑惑。因?yàn)閷?duì)于接口這個(gè)東西來說,從C#發(fā)布至今的十多年里幾乎一直保持它的樣子,然而在C# 8之后,它有了巨大的變化。隨著而來,也是各種爭(zhēng)論的聲音。
很早之前我就想寫這篇文章了,但是由于各種原因一直拖延到了現(xiàn)在。
先讓我們來回顧一下 C# 中原有的接口有什么特點(diǎn):
接口類似于只有抽象成員的抽象基類。實(shí)現(xiàn)接口的任何類或結(jié)構(gòu)都必須實(shí)現(xiàn)其所有成員。
接口無法直接進(jìn)行實(shí)例化。其成員由實(shí)現(xiàn)接口的任何類或結(jié)構(gòu)來實(shí)現(xiàn)。
接口可以包含事件、索引器、方法和屬性。
接口不含方法的實(shí)現(xiàn)。
一個(gè)類或結(jié)構(gòu)可以實(shí)現(xiàn)多個(gè)接口。一個(gè)類可以繼承一個(gè)基類,還可實(shí)現(xiàn)一個(gè)或多個(gè)接口。
也正是基于這些特點(diǎn),當(dāng)我們?cè)诮涌谥袨橐粋€(gè)方法加上"pulic"等關(guān)鍵字的時(shí)候,編譯器會(huì)提示我們這是一個(gè)錯(cuò)誤的寫法:
復(fù)制代碼
interface IRepository {//Compile-time error CS0106 The modifier 'public' is not valid for this item.public void Add(); }所以更不用談給方法寫一個(gè)實(shí)現(xiàn)了。這就讓它和 C# 中的另外一種事物行成了鮮明的對(duì)比,是的,抽象類。不知道大家有沒有在各種面試中遇到過這樣的提問:“接口能有任何的訪問修飾符嗎?”,“接口和抽象類的區(qū)別是什么?”
曾經(jīng)您可以和自然的脫口而出答案:“沒有修飾符。一個(gè)可以有默認(rèn)方法,一個(gè)只能申明方法…………”。但是從現(xiàn)在開始:這些答案是錯(cuò)的了。????
這是微軟MSDN中的設(shè)計(jì)規(guī)范截圖:
上面的圖是我半年前截的圖,今天本來想去找對(duì)應(yīng)的鏈接分享出來,但是發(fā)現(xiàn)找不到了。可能…………
新的接口
好了,說了那么多,我們來看看C# 8 為我們改變后的接口是什么樣子:
復(fù)制代碼
enum LogLevel {Information,Warning,Error }interface ILogger {void WriteCore(LogLevel level, string message);void WriteInformation(string message){WriteCore(LogLevel.Information, message);}void WriteWarning(string message){WriteCore(LogLevel.Warning, message);}void WriteError(string message){WriteCore(LogLevel.Error, message);} }class ConsoleLogger : ILogger {public void WriteCore(LogLevel level, string message){Console.WriteLine($"{level}: {message}");} }class TraceLogger : ILogger {public void WriteCore(LogLevel level, string message){switch (level){case LogLevel.Information:Trace.TraceInformation(message);break;case LogLevel.Warning:Trace.TraceWarning(message);break;case LogLevel.Error:Trace.TraceError(message);break;}} }ILogger consoleLogger = new ConsoleLogger(); consoleLogger.WriteWarning("Cool no code duplication!"); // Output: Warning: Cool no Code duplication!ILogger traceLogger = new TraceLogger(); consoleLogger.WriteInformation("Cool no code duplication!"); // Cool no Code duplication!這是我在網(wǎng)上摘取的一部分代碼。是的,您沒有看錯(cuò),接口可以實(shí)現(xiàn)方法了。并且還可以給它添加上訪問修飾符:
復(fù)制代碼
interface IDemoInterface {public static int staticIntValue = 123; //Rightpublic void PulicMethod(){ } //Right }就像您所見的一樣,它還可以在內(nèi)部聲明靜態(tài)的數(shù)據(jù)。
但是下面的寫法依舊會(huì)提示錯(cuò)誤哦:
復(fù)制代碼
interface IDemoInterface {abstract void M1() { } //Error. 因?yàn)橛衋bstractabstract private void M2() { } //Errorabstract static void M3() { } //Errorstatic extern void M4() { } //Error.因?yàn)橛衑xtern }爭(zhēng)議點(diǎn)
走到這里,也許您會(huì)說:“這不挺好的嗎?好像對(duì)我也沒有啥影響。” 確實(shí),假如您不更改接口的簽名,無論您是否在接口中增加默認(rèn)實(shí)現(xiàn)還是某些靜態(tài)數(shù)據(jù)都不會(huì)對(duì)已有的應(yīng)用程序造成任何錯(cuò)誤。
但是如果您經(jīng)常使用抽象類的話,您就會(huì)發(fā)現(xiàn),這樣的接口是不是和抽象類太像了?甚至有點(diǎn)完全掩蓋了抽象類的優(yōu)勢(shì)。
當(dāng)我半年前看到這一新特性時(shí),我就產(chǎn)生了這樣的疑惑。這個(gè)?“默認(rèn)方法實(shí)現(xiàn)”?的新特性,真的需要嗎?如果需要,那我如何選擇它和抽象類?
結(jié)果我發(fā)現(xiàn),大家都對(duì)這一特性產(chǎn)生了困惑:
于時(shí),我抱著懷疑的態(tài)度在網(wǎng)上到處搜索答案。最后在C# 官方團(tuán)隊(duì)的筆記中我看到了這樣一句話:
這句話的意思大致是:我們應(yīng)該更深入地研究Java在這里所做的事情,Java對(duì)接口的實(shí)現(xiàn)很好,我們應(yīng)該…………(有關(guān)該說明的github鏈接可以點(diǎn)擊這里)。
我當(dāng)時(shí)心就涼了半截。不過緩了緩,我鎮(zhèn)定的思考了一下:好的語(yǔ)言設(shè)計(jì)被借鑒和參考也是很有必要的。比如現(xiàn)在其它語(yǔ)言都在借鑒C#的await和async。(PS:C#和Typescript怎么越來越像????)。
那么我們真的需要在接口中提供默認(rèn)實(shí)現(xiàn)嗎?那什么情況下我需要這樣做?畢竟咱們使用了 C# 這么多年,就算接口沒有提供默認(rèn)實(shí)現(xiàn)也能設(shè)計(jì)出很好的系統(tǒng)來。所以為了解決上面的疑問,還是得回到接口和抽象類的本質(zhì)。
按照咱們以往使用接口和抽象類的情況來看:接口表示的是一種行為,"who can"(比如鳥會(huì)飛),而基類表示的是一種類別,"is a"(比如麻雀是鳥)。因此在OOP的世界中,如果咱們細(xì)心的來建模的話,我們會(huì)把表示行為的共性抽象為一個(gè)接口:比如鳥會(huì)飛,咱們可以抽象一個(gè)IFly的接口。對(duì)老版本的 C# 來說,不能提供方法的實(shí)現(xiàn),所以只會(huì)有一個(gè)Fly() 的方法簽名。而現(xiàn)在我們通過新的特性,我們可以給“飛”這個(gè)動(dòng)作提供一個(gè)默認(rèn)的實(shí)現(xiàn),比如 90%的鳥都是“煽動(dòng)翅膀起飛”,則我們可以將這個(gè)大部分?的操作作為默認(rèn)實(shí)現(xiàn),而對(duì)那些10%的 “小眾” 進(jìn)行重寫。也正是由于接口更關(guān)注的是“行為”,所以接口中不能存在“狀態(tài)”,因此您會(huì)發(fā)現(xiàn)雖然可以聲明字段了,但是只能聲明靜態(tài)字段。而實(shí)例化的狀態(tài)信息依舊只能通過抽象類來實(shí)現(xiàn)。
當(dāng)然,在現(xiàn)在接口和抽象類建模比較模糊的今天,從技術(shù)的實(shí)現(xiàn)上來說,其實(shí)接口的默認(rèn)實(shí)現(xiàn)并沒有帶來很多技術(shù)編碼上的好處。但是如果您堅(jiān)持好的規(guī)范抽象,比如接口開頭就是用“I”,將對(duì)象的行為進(jìn)行抽象提升為接口,也許某一刻您會(huì)感受到該特性帶來的改變。
總結(jié)
以上是生活随笔為你收集整理的【5min+】 巨大的争议?C# 8 中的接口的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊聊统一身份认证服务
- 下一篇: .NET Core开发实战(第5课:依赖