一:背景
1. 讲故事
大家在通过面向对象洗礼的时候,都理解过接口,而且晓得它是一种自上而下的设计思路,举个例子,咱们电脑上都有 USB 2.0 接口,蓝牙耳机实现了它能够进行充电,移动硬盘实现了它能够在电脑端显示硬盘内容,蓝牙鼠标实现了它能够进行鼠标操控,能够看出USB插口做进去后,谁来实现谁也搞不清楚,实现者能做出什么货色,谁也不晓得,这就是接口的魅力,落实在 C# 上就是接口中那一个一个的 stub 办法,留给将来的有缘人去实现,如下代码:
public interface IUsb { void Execute(); }
2. 你可能会有的纳闷
有些敌人可能会说,码农胡说八道,接口不光能够定义实例办法,还能够定义 属性,索引器,事件 等等。。。 如下代码:
public interface IUsb { event Action<string> action; string Name { get; set; } string this[string key] { get; set; } void Execute(); }
哈哈,果然是一个好问题,没错,属性,索引器和事件都能够定义在接口中,但请不要忘了,你列举的这些都是编译器层面的语法糖而已,话中有话就是你看过 编译后的 IL 代码吗? 如下图所示:
能够看到,那些所谓的语法糖在IL层面通通是办法,这就很好的解释了为啥接口中只能定义方法的起因。
3. 当初的接口真的变了
然而这种均衡在 C# 8.0 中被突破,现如今的接口除了惯例的实例办法,还能够定义任何标记为 static 的字段,属性,办法,构造函数 甚至还能够是 实例办法的默认实现,这就很奇葩了。。。不得不大吼一声,????????, 参考代码如下:
public interface IUsb { //常量 public const string constVal = ""; //动态字段 public static int age = 20; //动态构造函数 static IUsb() { } //默认办法实现 void Disco() { Console.WriteLine("Disco..."); } void Execute(); }
这下把我搞蒙了,目前除了一些实例字段还不能定义外,其余的都没有问题了,我置信不久的未来 interface 也会把这个遗憾解决掉,/(ㄒoㄒ)/ , 这叫我如何向起初的长辈解释呀~ 搞的我当初有很多纳闷!
二:笔者的纳闷
1. 接口的默认办法意义何在?
一个事物的呈现,必然有它的利用场景,有些敌人可能谈判到这样的场景,当很多类实现了 IUSB 接口之后,如下代码:
public interface IUsb { void Execute(); } public class Mp4 : IUsb { public void Execute() { } } public class Mouse: IUsb { public void Execute(){ } }
因为某些起因我筹备在 IUSB 中新增 Disco 办法,这个时候 MP4 和 Mouse 类必定会报错,大家都晓得这是因为没有实现 Disco 的办法,如下图所示:
这个时候该怎么办呢? C# 8.0 的接口默认办法就起到作用了,能够间接在原有接口中定义默认办法,对泛滥的接口实现者们是无感知的,能够编译胜利,如下图所示:
一起都很顺利,接下来我就急不可待的调用 Disco 办法,代码如下:
我去,从图中看竟然说 Mp4 类没有 Disco 办法,这就很莫名其妙了,气人,这叫啥默认办法,为了验证 MP4 类到底有没有 Disco 办法,一个到位的验证形式就是用 windbg 看看 MP4 的办法表。
0:000> !do 0x0000021e63c2ab10Name: DataStruct.Mp4MethodTable: 00007ff7cd972248EEClass: 00007ff7cd96c5e8Size: 24(0x18) bytesFile: E:\net5\ConsoleApp2\ConsoleApp1\bin\Debug\netcoreapp3.1\ConsoleApp1.dllFields:None0:000> !dumpmt -md 00007ff7cd972248EEClass: 00007FF7CD96C5E8Module: 00007FF7CD94F7D0Name: DataStruct.Mp4mdToken: 0000000002000004File: E:\net5\ConsoleApp2\ConsoleApp1\bin\Debug\netcoreapp3.1\ConsoleApp1.dllBaseSize: 0x18ComponentSize: 0x0Slots in VTable: 6Number of IFaces in IFaceMap: 1--------------------------------------MethodDesc Table Entry MethodDesc JIT Name00007FF7CD8A0090 00007FF7CD870A78 NONE System.Object.Finalize()00007FF7CD8A0098 00007FF7CD870A88 NONE System.Object.ToString()00007FF7CD8A00A0 00007FF7CD870A98 NONE System.Object.Equals(System.Object)00007FF7CD8A00B8 00007FF7CD870AD8 NONE System.Object.GetHashCode()00007FF7CD8B0670 00007FF7CD972228 NONE DataStruct.Mp4.Execute()00007FF7CD8B1030 00007FF7CD972238 JIT DataStruct.Mp4..ctor()
从下面最初6行代码可看出,MP4类的办法表中基本就没有 Disco 办法,阐明 MP4 的世界里基本就没有这玩意。。。那怎么样能力调用的上呢?你须要将 mp4 转成 IUSB 接口,而后再调用 Disco
办法就能够了,如下图所示:
可是即便能运行,又有什么用呢?反正子类是感知不到这个接口的默认办法,也颠覆了对接口的认知!我是没有看出有什么益处,程度无限没方法哈。。。
2. 这个场景自有它的解决方案 [扩大办法]
方才有些敌人提到的场景说后续减少接口办法的时候不影响已实现子类批改代码,其实不须要这个个性 C# 也能实现,毕竟这么宏大的类库代码,必定会有这样的场景哈,我就拿 List 汇合说事,如下代码是 List 的类定义:
public class List<T> :IList<T>, ICollection<T>{}public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable{ int IndexOf(T item); void Insert(int index, T item); void RemoveAt(int index);}public interface ICollection<T> : IEnumerable<T>, IEnumerable{ void Add(T item); void Clear(); bool Contains(T item); void CopyTo(T[] array, int arrayIndex); bool Remove(T item);}
能够看到 List 实现了 IList 和 ICollection 共 7 个办法,但大家在用 List 编码的时候发现其实远不止这 7 个办法,其余办法的接入(Select,Where)就是通过 C# 特有的 扩大办法 机制实现的,对不对,我感觉扩大办法就能够很好的解决 默认接口办法
的问题,所以 USB 接口能够用 扩大办法 来实现,如下代码所示:
static void Main(string[] args) { var mp4 = new Mp4(); mp4.Disco(); Console.ReadLine(); } public static class UsbExtension { public static void Disco(this IUsb usb) { Console.WriteLine("Disco..."); } }
三: 总结
总的来说,这是一个颠覆我三观的个性,毁坏了我对接口的认知,不想再说什么了,大家有什么妙解,欢送留言~~~
更多高质量干货:参见我的 GitHub: dotnetfly