C# 8.0 新特性之二:接口默认实现
在C#8.0中,针对接口引入了一项新特性,就是可以指定默认实现,方便对已有实现进行扩展,也对面向Android和Swift的Api进行互操作提供了可能性。下面我们来看看该特性的的概念、规则与示例代码。
一、什么是默认实现
顾名思义,默认实现就是接口中的成员可以进行实现,并作为该成员的一个默认实现,在以后,在实现该接口的时候,如果实现了该接口的成员,则会被覆盖默认实现,否则、它的实现依然会使用接口中定义的默认实现。
二、主要应用场景:
在不破坏影响已有实现的情况下,可以添加新成员。这解决了在第三方已经大量使用了的接口上进行扩展带来问题的痛点。
三、规则与限制:
1. 支持的成员:方法、属性、索引器、 及各种静态成员。不支持实例字段、实例事件、自动属性、实例构造和析构函数
2. 支持修饰符:private, protected, internal, public, virtual, abstract, sealed, static, extern, and partial.
3. 默认访问级别为public,可以显式指定,也可以不指定。
4. 除过sealed和private修饰的方法体之外,其他带有方法体的成员默认都是virtural成员
5. 接口中的默认实现只属于该接口和继承它的子接口,但不能被它的实现继承,所以只能通过接口变量调用。除非接口的实现中进行了再次实现。
6. 多层次继承的接口,调用最接近实现的接口的默认实现。也就是“层次最接近、最新实现最近、同级的new比overrided更接近”。
7. 在类中实现并覆盖接口中的成员,无需用new和override关键字,与接口的实现机制是保持一致,无需任何修改或操作。
四、实现举例:
1. 先定义一个接口IFlyable,代码如下:
public interface IFlyable
{
//支持const常量
public const int MAX_SPEED = ;
const int MIN_SPEED = ; //默认public,可以省略
public const string SPEED_UOM = "m/s";
private static readonly Dictionary<string, string> nameDic; //支持静态构造函数,不支持实例构造函数
static IFlyable()
{
nameDic = new Dictionary<string, string>() { {nameof(MAX_SPEED),MAX_SPEED.ToString()}, {nameof(MIN_SPEED),MIN_SPEED.ToString()} };
} //支持索引器,但是其中的变量也只能是静态变量。
string this[string key]
{
get
{
string tmp; if (nameDic.ContainsKey(key))
{
tmp = nameDic[key];
}
else
{
tmp = string.Empty;
} return tmp;
}
} int Speed { get;} //默认为public和virtual,所以此处的virtual和public可有可无
public void Initialize()
{
var defaultSpeed = AverageSpeed();
Initialize(defaultSpeed); WriteLine($"{nameof(IFlyable) + "." + nameof(Initialize)} at default {defaultSpeed} {SPEED_UOM}");
} // 私有带有方法体的成员是允许存在的
private int AverageSpeed()
{
return (MAX_SPEED + MIN_SPEED) / ;
} void Initialize(int speed); //默认为public和virtual,所以此处的virtual和public可有可无
void Fly()
{
WriteLine($"{nameof(IFlyable) + "." + nameof(Fly)}");
}
}
2. 再定义一个IAnimal接口:
public interface IAnimal
{
//默认为public和virtual,可以显式指出该成员时virtual
void SayHello()
{
WriteLine($"{nameof(IAnimal) + "." + nameof(SayHello)}");
} void Walk()
{
WriteLine($"{nameof(IAnimal) + "." + nameof(Walk)}");
}
}
3. 定义一个IFlyableAnimal接口,继承自前两个接口
public interface IFlyableAnimal:IAnimal,IFlyable
{
public new const int MAX_SPEED = ; //重写IAnimal的SayHello,
void IAnimal.SayHello()
{
WriteLine($"override {nameof(IFlyableAnimal) + "." + nameof(SayHello)} ");
} //因为IFlyableAnimal接口继承了IAnimal的接口,加new关键字来隐藏父类的继承的SayHello,可以不加,但会有警告。
public new void SayHello()
{
WriteLine($"new {nameof(IFlyableAnimal) + "." + nameof(SayHello)}");
} //因为IFlyableAnimal接口继承了IFlyable的接口,接口继承的默认实现是无法用override关键字的
public void Walk()
{
WriteLine($"new {nameof(IFlyableAnimal) + "." + nameof(Walk)}");
}
}
4. 定义一个类Sparrow,来实现接口IFlyableAnimal
public class Sparrow : IFlyableAnimal
{
//实现IFlyable中的Speed接口
public int Speed { get; private set; } //实现IFlyable中的Initialize(int speed)接口
public void Initialize(int speed)
{
this.Speed = speed;
WriteLine($"{nameof(Sparrow) + "." + nameof(Initialize)} at {Speed} {IFlyable.SPEED_UOM}");
} //实现并覆盖接口中的SayHello,无需用new和override关键字,与接口的实现机制是保持一致,无需任何修改或操作。
public virtual void SayHello()
{
// 注意的使用IFlyableAnimal.SPEED_UOM,类只能实现接口,不能继承接口的默认实现,但是接口可以继承父接口的默认实现
WriteLine($"{nameof(Sparrow) + "." + nameof(SayHello)} at {Speed} {IFlyableAnimal.SPEED_UOM}");
} }
5. 对前面的定义进行调用
static void Main(string[] args)
{
Sparrow bird = new Sparrow();
bird.Initialize(); //Sparrow中实现并覆盖了Initialize,所以可以直接用类调用
bird.SayHello();//Sparrow中实现并覆盖了,所以可以直接用类调用
//bird.Fly(); Fly不可访问,因为Bird没有实现也不会继承接口中的实现,所以不拥有Fly方法。 //IFlyableAnimal 继承了IAnimal和IFlyable的默认实现,通过该变量调用SayHello和Fly方法
IFlyableAnimal flyableAnimal = bird;
flyableAnimal.SayHello();
flyableAnimal.Fly(); //IFlyableAnimal继承自IAnimal和IFlyable,而Sparrow类又继承自了IFlyableAnimal,所以可以用IAnimal和IFlyable变量调用
IAnimal animal = bird;
animal.SayHello(); IFlyable flyable = bird;
flyable.Initialize();
flyable.Fly(); Monster monster = new Monster();
IAlien alien = monster;
//alien.Fly();//编译器无法分清是'IBird.Fly()' 还是 'IInsect.Fly()'
} //输出:
//Sparrow.Initialize at 98 m/s
//Sparrow.SayHello at 98 m/s
//Sparrow.SayHello at 98 m/s
//IFlyable.Fly
//Sparrow.SayHello at 98 m/s
//Sparrow.Initialize at 100 m/s
//IFlyable.Initialize at default 100 m/s
//IFlyable.Fly
五、多层次继承产生的问题
因为接口时可以多重继承的,这样就会出现类似C++里产生菱形继承问题。如下图所示,IBird和IInsect都继承了IFlyable,而IAlien又同时继承IBird和IInsert两个接口,并做了新的实现,这时,他们就构成了一个菱形或者钻石形状。这时候,实现了IAlien的Monster类,就会无法分清改用哪个Fly
代码如下:
public interface IBird : IFlyable
{
void Fly()
{
WriteLine($"{nameof(IBird) + "." + nameof(Fly)}");
}
} public interface IInsect : IFlyable
{
void Fly()
{
WriteLine($"{nameof(IInsect) + "." + nameof(Fly)}");
}
} public interface IAlien : IBird, IInsect
{
} public class Monster : IAlien
{
public int Speed { get; private set; } = ; public void Initialize(int speed)
{
this.Speed = speed;
}
}
下面调用语句alien.Fly就会导致混淆,编译器无法分清此Fly到底是IBird.Fly() 还是IInsect.Fly():
static void Main(string[] args)
{
Monster monster = new Monster();
IAlien alien = monster;
//alien.Fly();//编译器无法分清是'IBird.Fly()' 还是 'IInsect.Fly()'
}
对于这种问题的解决方案,是要么通过更为具体的接口(IBird或IInsect)来调用相应成员,要么在Monster类中实现Fly,再通过类来调用。
六、总结
C#8.0的接口默认实现对于软件的功能的扩展提供了比较大的灵活性,同时,也引入了一些规则,使得掌握其的成本增加。这里,我尽力求对其一些规则等做出了总结,并展现了示例予以说明,肯定又不足指出,希望大家指正。
C# 8.0 新特性之二:接口默认实现的更多相关文章
- c# 6.0新特性(二)
写在前面 上篇文章介绍了c#6.0的using static,Auto Property Initializers,Index Initializers新的特性,这篇文章将把剩下的几个学习一下. 原文 ...
- Java8新特性之四:接口默认方法和静态方法
在JDK1.8以前,接口(interface)没有提供任何具体的实现,在<JAVA编程思想>中是这样描述的:"interface这个关键字产生了一个完全抽象的类,它根本就没有提供 ...
- android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。
android6.0 参考一:简书Android 6.0 新特性详解 参考二:关于Android6.0以上系统的权限问题 参考三:值得你关注的Android6.0上的重要变化(一) 参考四:值得你关注 ...
- [转]Servlet 3.0 新特性详解
原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listen ...
- Java8新特性之二:方法引用
上一节介绍了Java8新特性中的Lambda表达式,本小节继续讲解Java8的新特性之二:方法引用.方法引用其实也离不开Lambda表达式. 1.方法引用的使用场景 我们用Lambda表达式来实现匿名 ...
- C#6.0,C#7.0新特性
C#6.0新特性 Auto-Property enhancements(自动属性增强) Read-only auto-properties (真正的只读属性) Auto-Property Initia ...
- Day07 jdk5.0新特性&Junit&反射
day07总结 今日内容 MyEclipse安装与使用 JUnit使用 泛型 1.5新特性 自动装箱拆箱 增强for 静态导入 可变参数方法 枚举 反射 MyEclipse安装与使用(yes) 安装M ...
- [翻译] C# 8.0 新特性 Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南) 【由浅至深】redis 实现发布订阅的几种方式 .NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐
[翻译] C# 8.0 新特性 2018-11-13 17:04 by Rwing, 1179 阅读, 24 评论, 收藏, 编辑 原文: Building C# 8.0[译注:原文主标题如此,但内容 ...
- Atitit. C#.net clr 2.0 4.0新特性
Atitit. C#.net clr 2.0 4.0新特性 1. CLR内部结构1 2. CLR 版本发展史3 3. CLR 2.0 3 4. CLR 4 新特性 概览4 4.1.1. 托管与本地 ...
随机推荐
- [apue] 使用 Ctrl+S停止输出而不用挂起前台进程
之前一直知道使用 Ctrl+Z 挂起前台进程来阻止进程运行,之后可以再通过 shell 的作业控制 (jobs / fg N) 来将后台进程切换为前台,从而继续运行. 最近学到一种新的方法,对于不停有 ...
- 《ASP.NET Core 高性能系列》关于.NET Core的部署方式
概述,.NET Core应用程序可以创建三种类型的部署:FDD SCD FDE 框架依赖的部署(FDD).顾名思义,框架依赖的部署(FDD)依赖于目标系统上是否存在.NET Core版本.由于.NET ...
- 基于 Lind.DDD 的 权限管理系统
先起个头,在跟吧 表结构 12个表 ps: sxiaomais.blog.163.com/blog/static/31741203200811102630406/ 需要数据库 文件的 附件 权限 ...
- 解决delete 删除sql语句,标识还保留删除之前的问题
我有一些数据,想要删除,首先想到的是delete,但是它会保留之前的标识,后来想用truncate来进行删除,但是,它会全部删除,并且不能加条件,只能回过头使用delete,以下是解决delete删除 ...
- [CCPC2019 ONLINE]E huntian oy
题意 http://acm.hdu.edu.cn/showproblem.php?pid=6706 思考 打表出奇迹. 注意到这个式子有一大堆强条件限制,最后化为: $$\frac{1}{2}\sum ...
- Informatica在linux下安装搭建
安装介质清单准备 介质名称 版本信息 描述 Informatica Powercenter 9.5.1 for Linux 64 bit 必须 Java Jdk 1.6.0_45 for Linux ...
- 面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗
一.前言 不知道大家还记不记得前几篇的文章:<面试官:能解释一下javascript中的this吗> 那今天这篇文章虽然是介绍javascript中bind.apply和call函数 ...
- ubuntu以root身份登录
- printf的用法进阶
今天来好好总结一下C语言中关于printf的常用用法 基础部分 printf("%d\n", numInt); printf("%f\n", numFloat) ...
- illegal use of this type as an expression
学习MCI时看别人样例手敲代码出现的一个很经典的错误. 在C语言中定义的变量没有放在函数的开头. #include <string.h> #include <windows.h> ...