在C# 8中如何使用默认接口方法详解

前言

C# 8 中新增了一个非常有趣的特性,叫做 默认接口方法 (又称虚拟扩展方法),这篇文章将会讨论 C# 8 中的默认接口方法以及如何使用。

在 C# 8 之前,接口不能包含方法定义,只能在接口中定义方法签名,还有一个就是接口的成员默认是 public 和 abstract , 在 C# 8 之前,接口不能包含字段,也不能包含private, protected, 或者 internal 的方法成员。如果你在接口中引入了一个新成员,默认情况下你必须更新实现该接口的所有子类。

在 C# 8 中可以在接口定义方法的默认实现,而且还可以定义接口成员为 private,protect,甚至是 static,还有一点挺奇葩的,一个接口的 protect 成员是不能被实现类所访问的,相反,它只能在子接口中被访问,接口的 virtual 成员可以由派生接口 override,但不能被派生类 override,还有一点请注意,接口目前还不能定义 实例成员。

为什么要使用默认接口方法

所谓的 默认接口方法 指的是接口中定义了一个默认实现的方法, 如果实现该接口的类没有实现默认接口方法的话,那么这个 默认接口方法 只能从接口上进行访问,这是一个很有用的特性,因为它可以帮助开发人员在不破坏现有功能的情况下向接口的未来版本添加新方法。

考虑下面的 ILogger 定义。

 public interface ILogger
 {
 public void Log(string message);
 }

下面的两个类扩展了ILogger接口并实现了Log()方法。

 public class FileLogger : ILogger
 {
 public void Log(string message)
 {
  //Some code
 }
 }

 public class DbLogger : ILogger
 {
 public void Log(string message)
 {
  //Some code
 }
 }

现在假设你想在ILogger接口中新增一个方法,该方法接受两个参数:一个 文本 一个 日志级别,下面的代码片段展示了日志级别的枚举类。

 public enum LogLevel
 {
 Info, Debug, Warning, Error
 }

修改后的 ILogger 接口如下:

 public interface ILogger
 {
 public void Log(string message);
 public void Log(string message, LogLevel logLevel);
 }

好了,现在问题来了,因为 ILogger 中新增了一个 Log 方法,你必须要在所有实现该接口的所有子类中实现 Log(string message, LogLevel logLevel) 方法,这就很尴尬了,如果不这样做的话,编译器肯定是不会放行的,在现实情况下,这个接口实现类可能在多个 dll 中,甚至在多个团队中,可想而知,这个工作量是非常大并且非常痛苦的。

默认接口方法案例

这就是 默认接口方法 的应用场景,你可以在接口中定义一个默认方法是实现,如下代码所示:

 public interface ILogger
 {
 public void Log(string message);

 public void Log(string message, LogLevel logLevel)
 {
  Console.WriteLine("Log method of ILogger called.");
  Console.WriteLine("Log Level: "+ logLevel.ToString());
  Console.WriteLine(message);
 }
 }

这个时候,实现 ILogger 接口的子类可以不实现新的 Log(string message, LogLevel logLevel) 方法,因此下面的代码也是跑的通的,编译器不会抛出任何错误。

 public class FileLogger : ILogger
 {
 public void Log(string message)
 {
  //Some code
 }
 }

 public class DbLogger : ILogger
 {
 public void Log(string message)
 {
  //Some code
 }
 }

默认接口方法不能被继承

现在创建一个 FileLogger 类实例,然后直接调用新的带参数的 Log() 方法,如下代码所示:

FileLogger fileLogger = new FileLogger();
fileLogger.Log("This is a test message.", LogLevel.Debug);

从上面图可看出 默认接口方法 不能被子类继承,换句话说,子类根本就不知道接口中还有带参数的 Log() 方法。

默认接口方法和菱形问题

现在有一个非常重要的问题,默认接口方法如何避免 菱形问题?换句话说就是 接口的 多继承 问题,考虑下面的代码清单。

 public interface A
 {
  public void Display();
 }

 public interface B : A
 {
  public void Display()
  {
   Console.WriteLine("Interface B.");
  }
 }

 public interface C : A
 {
  public void Display()
  {
   Console.WriteLine("Interface C.");
  }
 }

 public class MyClass : B, C
 {

 }

当编译上面代码时,会抛出一个编译错误,说 MyClass 没有实现 A.Display() 方法,解决这个问题很简单,在 MyClass 中实现一下接口方法就可以了,如下代码所示:

 public interface A
 {
  public void Display();
 }
 public interface B : A
 {
  public void Display()
  {
   Console.WriteLine("Interface B.");
  }
 }
 public interface C : A
 {
  public void Display()
  {
   Console.WriteLine("Interface C.");
  }
 }
 public class MyClass : B, C
 {
  public void Display()
  {
   Console.WriteLine("MyClass.");
  }
 }

接下来就可以生成 MyClass 实例了,然后再调用 Display() 方法,如下代码所示:

  static void Main(string[] args)
  {
   A obj = new MyClass();
   obj.Display();
   Console.Read();
  }

现在问题来了,到底是哪一个 Display() 方法被调用了呢?为了避免歧义,C# 将会使用最近覆盖规则,即 Class.Display() 方法被最先调用。

抽象类 VS 接口

到这里,我想你肯定有疑问,抽象类 和 接口 是不是很相似了,甚至可以互换了?虽然抽象类和接口现在看起来在很多方面都很相似,但两者之间还是有微妙的区别的,具体如下:

  • 抽象类可以有实例成员,接口则不能。
  • 抽象类不能多继承,接口还是可以的。

默认接口方法 允许开发人员利用 trait 编程技术,该技术可以让那些附属于该方法的不相关类型得以继续使用,可能你有点懵,我举个例子:假设你构建好了一个dll,被很多的开发人员所使用,现在你要发布该 dll 的新版本,比如说往接口中添加了新方法,这个时候你可以定义默认实现,这样就可以对已使用的开发者进行无感升级。

译文链接:https://www.infoworld.com/article/3455239/how-to-use-default-interface-methods-in-csharp-8.html

总结

到此这篇关于在C# 8中如何使用默认接口方法的文章就介绍到这了,更多相关C#8使用默认接口方法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#8.0 中开启默认接口实现方法

    当你升级到 C# 8.0 和 .NET Core 3.0 之后,你就可以开始使用默认接口实现的功能了. 从现在开始,你可以在接口里面添加一些默认实现的成员,避免在接口中添加成员导致大量对此接口的实现崩溃. 最低要求 要写出并且正常使用接口的默认实现,你需要: C# 8.0 .NET Core 3.0 Visual Studio 2019 Preview (16.1 以上版本) 下载安装 Visual Studio 2019 Preview 版本 前往下载安装 Visual Studio Prev

  • 在C# 8中如何使用默认接口方法详解

    前言 C# 8 中新增了一个非常有趣的特性,叫做 默认接口方法 (又称虚拟扩展方法),这篇文章将会讨论 C# 8 中的默认接口方法以及如何使用. 在 C# 8 之前,接口不能包含方法定义,只能在接口中定义方法签名,还有一个就是接口的成员默认是 public 和 abstract , 在 C# 8 之前,接口不能包含字段,也不能包含private, protected, 或者 internal 的方法成员.如果你在接口中引入了一个新成员,默认情况下你必须更新实现该接口的所有子类. 在 C# 8 中

  • Spring中自定义数据类型转换的方法详解

    目录 类型转换服务 实现Converter接口 实现ConverterFactory接口 实现GenericConverter接口 环境:Spring5.3.12.RELEASE. Spring 3引入了一个core.onvert包,提供一个通用类型转换系统.系统定义了一个SPI来实现类型转换逻辑,以及一个API来在运行时执行类型转换.在Spring容器中,可以使用这个系统作为PropertyEditor实现的替代,将外部化的bean属性值字符串转换为所需的属性类型.还可以在应用程序中需要类型转

  • struts2中使用注解配置Action方法详解

    使用注解来配置Action可以实现零配置,零配置将从基于纯XML的配置转化为基于注解的配置.使用注解,可以在大多数情况下避免使用struts.xml文件来进行配置. struts2框架提供了四个与Action相关的注解类型,分别为ParentPackage.Namespace.Result和Action. ParentPackage:ParentPackage注解用于指定Action所在的包要继承的父包.该注解只有一个value参数.用于指定要继承的父包. 示例: 使用ParentPackage

  • 在vue项目中引入highcharts图表的方法(详解)

    npm进行highchars的导入,导入完成后就可以进行highchars的可视化组件开发了 npm install highcharts --save 1.components目录下新建一个chart.vue组件 <template> <div class="x-bar"> <div :id="id" :option="option"></div> </div> </templa

  • Spring Boot项目中定制拦截器的方法详解

    这篇文章主要介绍了Spring Boot项目中定制拦截器的方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Servlet 过滤器属于Servlet API,和Spring关系不大.除了使用过滤器包装web请求,Spring MVC还提供HandlerInterceptor(拦截器)工具.根据文档,HandlerInterceptor的功能跟过滤器类似,但拦截器提供更精细的控制能力:在request被响应之前.request被响应之后.视

  • SpringMVC中常用注解与使用方法详解

    MVC简介 MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写, 是一种用于设计创建 Web 应用程序表现层的模式. Model(模型): 通常指的就是我们的数据模型.作用一般情况下用于封装数据. View(视图): 通常指的就是我们的 jsp 或者 html.作用一般就是展示数据的. 通常视图是依据模型数据创建的. Controller(控制器): 是应用程序中处理用户交互的部分.作用一般就是处理程序逻辑的.

  • Python实现从文件中加载数据的方法详解

    前几篇都是手动录入或随机函数产生的数据.实际有许多类型的文件,以及许多方法,用它们从文件中提取数据来图形化. 比如之前python基础(12)介绍打开文件的方式,可直接读取文件中的数据,扩大了我们的数据来源.下面,将展示几种方法. 我们将使用内置的 csv 模块加载CSV文件 CSV文件是一种特殊的文本文件,文件中的数据以逗号作为分隔符,很适合进行数据的解析.先用excle建立如下表格和数据,另存为csv格式文件,放到代码目录下. 包含在Python标准库中自带CSV 模块,我们只需要impor

  • Spring中bean集合注入的方法详解

    目录 Map注入 List注入 Set注入 数组注入 应用 哈喽大家好啊,我是Hydra. Spring作为项目中不可缺少的底层框架,提供的最基础的功能就是bean的管理了.bean的注入相信大家都比较熟悉了,但是有几种不太常用到的集合注入方式,可能有的同学会不太了解,今天我们就通过实例看看它的使用. 首先,声明一个接口: public interface UserDao { String getName(); } 然后定义两个类来分别实现这个接口,并通过@Component注解把bean放入s

  • Java中Optional类及orElse方法详解

    目录 引言 Java 中的 Optional 类 ofNullable() 方法 orElse() 方法 案例 orElseGet() 方法 案例 orElse() 与 orElseGet() 之间的区别 引言 为了让我更快的熟悉代码,前段时间组长交代了一个小任务,大致就是让我整理一下某个模块中涉及的 sql,也是方便我有目的的看代码,也是以后方便他们查问题(因为这个模块,涉及的判断很多,所以之前如果 sql 出错了,查问题比较繁琐). 昨天算是基本完成了,然后今天组长就让给我看一个该模块的缺陷

  • 去除element-ui中Dialog对话框遮罩层方法详解

    目录 前言 modal 属性 示例代码: 前言 本文主要介绍了如何去除 element-ui 中 Dialog 对话框遮罩层的方法,并给出了示例代码以及页面效果作为参考. modal 属性 在使用element-ui的Dialog时,默认会有遮罩层,想要去除遮罩层,官方文档给出了 modal 属性: modal 是一个布尔值,表示是否需要遮罩层: 然而,将 modal 赋值为 false 时并不生效: 这里官方文档也给出了提示: 当 modal 的值为 false 时,请一定要确保 append

随机推荐