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. 托管与本地 ...
随机推荐
- 异数OS 织梦师-纤手(二)-- LPC RPC篇
异数OS 织梦师-纤手(二)– LPC RPC篇 本文来自异数OS社区 github: https://github.com/yds086/HereticOS 异数OS社区QQ群: 652455784 ...
- Flsak学习笔记(1)
Day 01 最近项目里要用python写后端,同学推荐了flask框架就来学一学.写这个博客的目的主要是记录一下自己学习的内容,有基础知识忘了不用一个个去百度,还有就是跟大家分享一下,有不是很容易理 ...
- 工作笔记-- 源码安装nginx
源码安装nginx 1.安装nginx的依赖包 [root@localhost ~]# yum -y install gcc gcc-c++ openssl openssl-devel pcre pc ...
- C++ lambda 分析
lambda 表达式分析 构造闭包:能够捕获作用域中变量的匿名函数的对象,Lambda 表达式是纯右值表达式,其类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),所以在声 ...
- xlwings excel(四)
前言 当年看<别怕,Excel VBA其实很简单>相见恨晚,看了第一版电子版之后,买了纸质版,然后将其送人.而后,发现出了第二版,买之收藏.之后,发现Python这一编程语言,简直是逆天, ...
- ContractPattern 面向面向契约模式
- 红黑树(依照4阶B树C++实现)
我在编写红黑树的时候类比这2-3-4树的原理来书写 语言标准:C++11 在Ubuntu 18.04上通过编译和测试 从刚开始只听说过这个概念,到学习,再到编出代码,然后在进行测试,最后完成代码一共花 ...
- CentOS 6.6 下源码编译安装MySQL 5.7.5
版权声明:转自:http://www.linuxidc.com/Linux/2015-08/121667.htm 说明:CentOS 6.6 下源码编译安装MySQL 5.7.5 1. 安装相关工具# ...
- 自定义BeanDefinitionRegistryPostProcessor注册bean
自定义BeanDefinitionRegistryPostProcessor 概述 BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProce ...
- laravel 队列服务使用总结
laravel 队列服务使用总结 使用步骤 配置队列驱动 //env文件,有的版本是QUEUE_DRIVER QUEUE_CONNECTION=database 迁移队列需要的数据表,在数据库中生成j ...