C# 8中的默认接口方法
\
关键要点
\\
- 默认接口方法已经被包含在C# 8的新功能建议中,开发人员可以像使用trait那样使用默认方法。\\t
- trait是面向对象的编程技术,用于提升不相关类之间方法的重用性。\\t
- C#语言开发人员基于Java的默认方法概念开发此功能。\\t
- C#通过在运行时调用最具体的覆盖方法来解决默认接口方法可能会发生的钻石继承问题。\\t
- 在使用默认接口方法时,C#编译器将尽量让开发者免于发生许多常见的实现错误。\
\\
默认接口方法(也称为虚拟扩展方法)是C#8的一项新功能建议,开发人员可以像使用trait那样使用默认方法。trait是面向对象的编程技术,用于提升不相关类之间方法的重用性。
\\
在这篇文章中,我将介绍这个新功能,包括新的C#语法,以及这个功能如何让你的代码更加干净和紧凑。
\\
默认方法带来的主要好处是,现在可以在不破坏实现类的情况下给接口添加默认方法。换句话说,这个特性让开发者可以选择是否要覆盖默认方法。
\\
下面描述的日志记录示例是该功能的一个非常好的使用场景。ILogger接口有一个抽象的WriteLogCore方法。其他方法都是默认方法,如WriteError和WriteInformation,它们通过不同的参数调用WriteLogCore。ILogger实现类只需要实现WriteLogCore方法即可。
\\
可以想象一下,你的继承类为此可以省去多少代码。不过,这个功能虽好,但也存在风险,因为它是一种多重继承。它也存在钻石继承问题,下面将作具体描述。另外,接口方法必须是没有状态的“纯行为”,这意味着接口仍然像过去一样不能直接引用其他字段。
\\
接口语法已经经过扩展,可接受下面列出的新关键字。例如,你可以在接口中编写一个私有方法,代码仍然可以通过编译并正常工作。
\\
- 方法体或索引器、属性、事件访问器\\t
- private、protected、internal、public、virtual、abstract、override、sealed、static、extern\\t
- 静态字段\\t
- 静态方法、属性、索引器和事件\\t
- 具有默认访问权限的显式访问修饰符是public的\\t
- Override修饰符\
不允许出现:
\\
- 实例状态、实例字段、实例自动属性\
默认接口方法示例
\\
下面这个简单的例子演示了如何使用这一特性。
\\
\// ------------------------Default Interface Methods---------------------------\\ interface IDefaultInterfaceMethod\ {\ public void DefaultMethod()\ {\ Console.WriteLine(\"I am a default method in the interface!\");\ }\ }\\ class AnyClass : IDefaultInterfaceMethod\ {\ }\\ class Program\ {\ static void Main()\ {\ IDefaultInterfaceMethod anyClass = new AnyClass();\ anyClass.DefaultMethod();\ }\ }\
\\
控制台输出:
\\
\\u0026gt; I am a default method in the interface!
\\
可以看到,接口提供了默认方法,实现类并不知道接口提供了默认方法,也不包含该接口方法的实现。
\\
将IDefaultInterfaceMethod更改为AnyClass,如下所示:
\\
\AnyClass anyClass = new AnyClass();\ anyClass.DefaultMethod();\
\\
上面的代码会产生编译时错误:AnyClass不包含DefaultMethod。
\\
这证明了实现类对默认方法一无所知。
\\
\\
图1:在类上调用默认方法时的错误消息
\\
要访问默认接口方法,必须将其转型成接口:
\\
\AnyClass anyClass = new AnyClass();\ ((IDefaultInterfaceMethod)anyClass).DefaultMethod(); \
\\
控制台输出:
\\
\\u0026gt; I am a default method in the interface!
\\
值得一提的是,相同的功能在Java中已经存在了很长时间,.NET团队已经将Java默认方法文档作为.NET Framework开发人员的参考,例如:
\\
\
“我们应该更深入地了解Java在这方面所做的工作,他们肯定已经积累了很多这方面的见解。” —— C#语言设计笔记 2017年4月11日
\
\\
接口中的修饰符
\\
正如我之前提到的,接口语法现在可以接受以下关键字:protected、internal、public和virtual。默认情况下,默认接口方法是virtual的,除非使用了sealed或private修饰符。类似的,没有方法体的接口成员默认是abstract的。
\\
例如:
\\
\ // ------------------------ Virtual and Abstract---------------------------\\interface IDefaultInterfaceMethod\ {\ // By default, this method will be virtual, and the virtual keyword can be here used!\ virtual void DefaultMethod()\ {\ Console.WriteLine(\"I am a default method in the interface!\");\ }\\ // By default, this method will be abstract, and the abstract keyword can be here used\ abstract void Sum();\ }\\ interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod\ {\ void IDefaultInterfaceMethod.DefaultMethod()\ {\ Console.WriteLine(\"I am an overridden default method!\");\ }\ }\\ class AnyClass : IDefaultInterfaceMethod, IOverrideDefaultInterfaceMethod\ {\ public void Sum()\ {\ }\ }\\ class Program\ {\ static void Main()\ {\ IDefaultInterfaceMethod anyClass = new AnyClass();\ anyClass.DefaultMethod();\\ IOverrideDefaultInterfaceMethod anyClassOverridden = new AnyClass();\ anyClassOverridden.DefaultMethod();\ }\ }
\\
控制台输出:
\\
\\u0026gt; I am a default method in the interface!\\u0026gt; I am an overridden default method!\
\\
关键字virtual和abstract可以从接口中删除,不过删不删除其实对编译后的代码并没有任何影响。
\\
注意:在覆盖的方法中不允许出现访问修饰符。
\\
覆盖示例:
\\
\interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod\ {\ public void IDefaultInterfaceMethod.DefaultMethod()\ {\ Console.WriteLine(\"I am an overridden default method\");\ }\ }\
\\
上面的代码会产生编译时错误:修饰符“public”在此处无效。
\\
\\
图2:修改器在重写的方法中是不允许的
\\
钻石继承问题
\\
这个问题指的是因为允许多重继承而产生的模糊性。对于允许多重继承的语言(如C++)来说,这是一个很大的问题。然而,在C#中,类不允许多重继承,接口也只在有限的范围内进行多重继承,而且不包含状态。
\\
\\
图3:钻石依赖关系
\\
考虑以下情况:
\\
\// ------------------------Diamond inheritance and classes---------------------------\\ interface A\ {\ void m();\ }\\ interface B : A\ {\ void A.m() { System.Console.WriteLine(\"interface B\"); }\ }\\ interface C : A\ {\ void A.m() { System.Console.WriteLine(\"interface C\"); }\ }\\ class D : B, C\ {\ static void Main()\ {\ C c = new D();\ c.m();\ }\ }\
\\
上面的代码会产生编译时错误,如图4所示:
\\
\\
图4:钻石问题的错误消息
\\
.NET开发团队决定通过在运行时调用最具体的覆盖方法来解决钻石问题。
\\
\\
\
“实现了接口成员的类应该总是胜过接口提供的默认实现,即使它是从基类继承的。只有当类没有提供具体的实现时,才考虑使用默认实现“
\
\\
如果你想了解更多关于此问题的信息,可以参看提案:默认接口方法和C#语言设计笔记2017年4月19日。
\\
回到我们的例子。问题是编译器无法推断出最具体的覆盖方法是哪个。不过,你可以像下面这样在类D中添加方法“m”,现在编译器就可以使用这个类实现来解决钻石问题。
\\
\ class D : B, C\ {\ // Now the compiler will use the most specific override, which is defined in the class ‘D’\ void A.m()\ {\ System.Console.WriteLine(\"I am in class D\"); \ }\\ static void Main()\ {\ A a = new D();\ a.m();\ }\ }
\\
控制台输出:
\\
\\u0026gt; I am in class D
\\
this关键字
\\
下面的例子演示了如何在接口中使用“this”关键字。
\\
\public interface IDefaultInterfaceWithThis\ {\ internal int this[int x]\ {\ get\ {\ System.Console.WriteLine(x);\ return x;\ }\ set\ {\ System.Console.WriteLine(\"SetX\");\ }\ }\\ void CallDefaultThis(int x)\ {\ this[0] = x;\ }\ }\\ class DefaultMethodWithThis : IDefaultInterfaceWithThis\ {\ }\
\\
客户端代码:
\\
\ IDefaultInterfaceWithThis defaultMethodWithThis = new DefaultMethodWithThis();\ Console.WriteLine(defaultMethodWithThis[0]); \ defaultMethodWithThis.CallDefaultThis(0); \
\\
控制台输出:
\\
\0\SetX\
\\
ILogger示例
\\
ILogger接口是解释默认方法技术的最常用示例。在我的代码示例中,包含了一个名为“WriteCore”的抽象方法,其他方法都有一个默认的实现。ConsoleLogger和TraceLogger实现了ILogger接口。下面的这些代码非常紧凑和干净。在过去,一个类除非是抽象类,否则必须实现接口所有的方法,这可能导致很多重复代码。而使用新的方法,ConsoleLogger将能够继承另一个类层次结构,换句话说,默认方法将为你提供最灵活的设计。
\\
\ 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!\
\\
Player示例
\\
这是一款包含不同类型玩家的游戏。力量型玩家具有更大的攻击力,而限制型玩家具有更小的攻击力。
\\
\ public interface IPlayer\ {\ int Attack(int amount);\ }\\ public interface IPowerPlayer: IPlayer\ {\ int IPlayer.Attack(int amount)\ {\ return amount + 50;\ }\ }\\ public interface ILimitedPlayer: IPlayer\ {\ int IPlayer.Attack(int amount)\ {\ return amount + 10;\ }\ }\\ public class WeakPlayer : ILimitedPlayer\ {\ }\\ public class StrongPlayer : IPowerPlayer\ {\ }\
\\
客户端代码:
\\
\ IPlayer powerPlayer = new StrongPlayer(); \ Console.WriteLine(powerPlayer.Attack(5)); // Output 55\\ IPlayer limitedPlayer = new WakePlayer();\ Console.WriteLine(limitedPlayer.Attack(5)); // Output 15\
\\
正如你在上面的代码示例中看到的那样,IPowerPlayer接口和ILimitedPlayer接口包含了默认实现。限制型玩家攻击力更小。如果我们定义一个新的类,例如SuperDuperPlayer(继承自StrongPlayer),那么新类会自动从接口中获得默认的强攻击力行为,如下所示。
\\
\ public class SuperDuperPlayer: StrongPlayer\ {\ }\\ IPlayer superDuperPlayer = new SuperDuperPlayer();\ Console.WriteLine(superDuperPlayer.Attack(5)); // Output 55\
\\
Generic Filter示例
\\
ApplyFilter是一个默认接口方法,它包含了一个应用在泛型类型上的Predicate。在我的例子中,使用了一个虚拟的过滤器来模拟行为。
\\
\ interface IGenericFilter\u0026lt;T\u0026gt;\ {\ IEnumerable\u0026lt;T\u0026gt; ApplyFilter(IEnumerable\u0026lt;T\u0026gt; collection, Func\u0026lt;T, bool\u0026gt; predicate)\ {\ foreach (var item in collection)\ {\ if (predicate(item))\ {\ yield return item;\ }\ }\ }\ }\\ interface IDummyFilter\u0026lt;T\u0026gt; : IGenericFilter\u0026lt;T\u0026gt;\ {\ IEnumerable\u0026lt;T\u0026gt; IGenericFilter\u0026lt;T\u0026gt;.ApplyFilter(IEnumerable\u0026lt;T\u0026gt; collection, Func\u0026lt;T, bool\u0026gt; predicate)\ {\ return default;\ }\ }\\ public class GenericFilterExample: IGenericFilter\u0026lt;int\u0026gt;, IDummyFilter\u0026lt;int\u0026gt;\ {\ }\
\\
客户端代码:
\\
\ IGenericFilter\u0026lt;int\u0026gt; genericFilter = new GenericFilterExample();\ var result = genericFilter.ApplyFilter(new Collection\u0026lt;int\u0026gt;() { 1, 2, 3 }, x =\u0026gt; x \u0026gt; 1);\
\\
控制台输出:
\\
\2, 3\
\\
客户端代码:
\\
\ IDummyFilter\u0026lt;int\u0026gt; dummyFilter = new GenericFilterExample();\ var emptyResult = dummyFilter.ApplyFilter(new Collection\u0026lt;int\u0026gt;() { 1, 2, 3 }, x =\u0026gt; x \u0026gt; 1);
\\
控制台输出:
\\
\0
\\
你可以将此通用过滤器概念应用在其他设计上。
\\
限制
\\
在接口中使用修饰符关键字时,首先需要了解一些限制和注意事项。在很多情况下,编译器会为我们检测常见错误(例如下面列出的错误)。
\\
例如下面的代码:
\\
\ interface IAbstractInterface\ {\ abstract void M1() { }\ abstract private void M2() { }\ abstract static void M3() { }\ static extern void M4() { }\ }\\ class TestMe : IAbstractInterface\ {\ void IAbstractInterface.M1() { }\ void IAbstractInterface.M2() { }\ void IAbstractInterface.M3() { }\ void IAbstractInterface.M4() { }\ }\
\\
上面的代码将产生下面列出的编译时错误:
\\
\error CS0500: 'IAbstractInterface.M1()' cannot declare a body because it is marked abstract\error CS0621: 'IAbstractInterface.M2()': virtual or abstract members cannot be private\error CS0112: A static member 'IAbstractInterface.M3()' cannot be marked as override, virtual, or abstract\error CS0179: 'IAbstractInterface.M4()' cannot be extern and declare a body\error CS0122: 'IAbstractInterface.M2()' is inaccessible due to its protection level\
\\
错误CS0500表示默认方法“IAbstractInterface.M3()”不能是抽象的,因为它有方法体。错误CS0621表示该方法不能是既是private又是abstract的。
\\
在Visual Studio中:
\\
\\
图5:Visual Studio中的编译错误
\\
更多信息和源代码:
\\
- Default Interface Methods C# 8 in depth\\t
- C# 7.0 New Features\\t
- Cheat Sheet C# 7\\t
- .NET Futures: Multiple Inheritance\
关于作者
\\
Bassam Alugili 是STRATEC AG的高级软件专家和数据库专家。STRATEC是全球领先的全自动分析器系统软件合作商,专注于实验室数据管理和智能消耗品的软件系统。
\\
查看英文原文:Default Interface Methods in C# 8
\\
C# 8中的默认接口方法相关推荐
- 如何在 C# 8 中使用默认接口方法
C# 8 中新增了一个非常有趣的特性,叫做 默认接口方法 (又称虚拟扩展方法),这篇文章将会讨论 C# 8 中的默认接口方法以及如何使用. 在 C# 8 之前,接口不能包含方法定义,只能在接口中定义方 ...
- C#和F#默认接口方法更新
"默认接口方法(Default Interface Methods)"特性提案将允许C#.F#及其他.NET语言实现有限形式的多继承.受Java的默认方法启发,库作者将可以向已发布 ...
- C# 8: 默认接口方法
翻译自 John Demetriou 2018年8月4日 的文章 <C# 8: Default Interface Methods>[1] C# 8 之前 今天我们来聊一聊默认接口方法.听 ...
- C# 默认接口方法更新完成,很多细节问题尚待解决
随着对默认接口方法的支持越来越接近完成,一些潜在的问题被提了出来.虽然已经完成了很多工作,但这是一个复杂的特性,许多细节问题还没有解决.但首先,这里有一些已解决的问题. 接口允许使用 static 和 ...
- C# 8.0 中开启默认接口实现
当你升级到 C# 8.0 和 .NET Core 3.0 之后,你就可以开始使用默认接口实现的功能了. 从现在开始,你可以在接口里面添加一些默认实现的成员,避免在接口中添加成员导致大量对此接口的实现崩 ...
- C# 8.0 的默认接口方法
例子 直接看例子 有这样一个接口: 然后有三个它的实现类: 然后在main方法里面调用: 截至目前,程序都可以成功的编译和运行. IPerson接口变更 突然,我想对所有的人类添加一个新的特性,例如, ...
- java中ssh测试接口方法_SSH入门---框架搭建(eclipse环境下)
前情提要:本文是把Spring.Struts2.Hibernate三大框架整合到一起,搭建整合框架的教程,如需查看各个框架的单独搭建,请看我个人的相关文章. 一.新建动态web项目,导包: 我的项目名 ...
- Intellij IDEA中快速实现接口方法的快捷键
Ctrl+I 参考了网上好多的快捷键都不能用,只有这个是亲测可用的,记录一下.
- 接口中默认方法和静态方法_接口中的默认方法和静态方法
接口中默认方法和静态方法 在我们最初的Java 8支持公告中,我们特别提到了流的缺乏,但完全错过了默认/静态本机接口不起作用的事实. 现在,由于有一个警惕的社区成员指出了这一问题,因此此问题已得到解决 ...
最新文章
- iOS通过CAShapeLayer和UIBezierPath画环形进度条
- leetcode算法题--飞地的数量
- 3.Python算法之贪心算法思想
- 配置普通用户可以运行saltstack的模块
- springmvc如何使用视图解析器_SpringMVC工作原理
- 1040 有几个PAT (25 分
- 玩转linux文件描述符和重定向,玩转Linux文件描述符和重定向
- 实用常识 | 巧妙使用IA图片助手多地址提取批量下载(老白嫖怪了)
- SPOJ-New Distinct Substrings,注意会爆int
- vue中cookie的使用——将cookie放在请求头header中
- 16、Flutter Widget - PageView;
- 青少年c语言培训,青少年信息学奥赛培优教程·入门篇(2020年01月)
- 7、Lctech Pi(F1C200S)开启RNDIS,通过USB与电脑联网(CherryPi,Mangopi,F1C100S)
- android 屏幕orientation,关于屏幕旋转而orientation值不改变的问题
- 计算机cpu电源的diy,DIY台式电脑正确选择电源的新方法
- markdown编辑器推荐(附官网)
- 这帖子,程序员的痛心疾首,我明臣哭了.
- linux看网卡百兆千兆,查看网卡是百兆还是千兆
- spss数据处理--数据检查
- 用 PHP 来刷leetCode 之 四数之和
热门文章
- 漫画:什么是 JVM 的垃圾回收?
- 很开心收到了Andreas Loew发给我的注册key
- WIN10下Java环境变量配置
- JavaScript 设计模式的七大原则(未完成)
- ajax实现给JavaScript中全局变量赋值(转)
- ehcache + spring 整合以及配置说明 ,附带整合问题 (已解决)
- linux(centos 7版) 配置静态ip
- DevExpress Components16.2.6 Source Code 编译
- POJ2955Brackets[区间DP]
- Troubleshooting Open Cursor Issues