​      在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 新特性之二:接口默认实现的更多相关文章

  1. c# 6.0新特性(二)

    写在前面 上篇文章介绍了c#6.0的using static,Auto Property Initializers,Index Initializers新的特性,这篇文章将把剩下的几个学习一下. 原文 ...

  2. Java8新特性之四:接口默认方法和静态方法

    在JDK1.8以前,接口(interface)没有提供任何具体的实现,在<JAVA编程思想>中是这样描述的:"interface这个关键字产生了一个完全抽象的类,它根本就没有提供 ...

  3. android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。

    android6.0 参考一:简书Android 6.0 新特性详解 参考二:关于Android6.0以上系统的权限问题 参考三:值得你关注的Android6.0上的重要变化(一) 参考四:值得你关注 ...

  4. [转]Servlet 3.0 新特性详解

    原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listen ...

  5. Java8新特性之二:方法引用

    上一节介绍了Java8新特性中的Lambda表达式,本小节继续讲解Java8的新特性之二:方法引用.方法引用其实也离不开Lambda表达式. 1.方法引用的使用场景 我们用Lambda表达式来实现匿名 ...

  6. C#6.0,C#7.0新特性

    C#6.0新特性 Auto-Property enhancements(自动属性增强) Read-only auto-properties (真正的只读属性) Auto-Property Initia ...

  7. Day07 jdk5.0新特性&Junit&反射

    day07总结 今日内容 MyEclipse安装与使用 JUnit使用 泛型 1.5新特性 自动装箱拆箱 增强for 静态导入 可变参数方法 枚举 反射 MyEclipse安装与使用(yes) 安装M ...

  8. [翻译] C# 8.0 新特性 Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南) 【由浅至深】redis 实现发布订阅的几种方式 .NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐

    [翻译] C# 8.0 新特性 2018-11-13 17:04 by Rwing, 1179 阅读, 24 评论, 收藏, 编辑 原文: Building C# 8.0[译注:原文主标题如此,但内容 ...

  9. 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.  托管与本地 ...

随机推荐

  1. CTF实验吧——证明自己吧

    题目地址:http://www.shiyanbar.com/ctf/28 没有壳 ,vc++ 写的 拖进OD观察观察,发现代码很短哟,先来看这俩个call 怀疑他们其中有正确的flag和我们输入的东西 ...

  2. c#移位运算符("<<"及">>")

    C#是用<<(左移) 和 >>(右移) 运算符是用来执行移位运算. 左移 (<<) 将第一个操作数向左移动第二个操作数指定的位数,空出的位置补0.  左移相当于乘. ...

  3. MyBatis-Plus学习笔记(1):环境搭建以及基本的CRUD操作

    MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,使用MyBatis-Plus时,不会影响原来Mybatis方式的使用. SpringBoot+M ...

  4. python数据分析常用图大集合

    目录 一.折线图 二.直方图 三.垂直条形图 四.水平条形图 五.饼图 六.箱线图 七.热力图 八.散点图 九.蜘蛛图 十.二元变量分布 十一.面积图 十二.六边形图 以下默认所有的操作都先导入了Nu ...

  5. HanLP《自然语言处理入门》笔记--2.词典分词

    2. 词典分词 中文分词:指的是将一段文本拆分为一系列单词的过程,这些单词顺序拼接后等于原文本. 中文分词算法大致分为基于词典规则与基于机器学习这两大派. 2.1 什么是词 在基于词典的中文分词中,词 ...

  6. OpenResty学习指南(一)

    我的博客: https://www.luozhiyun.com/archives/217 想要学好 OpenResty,你必须理解下面 8 个重点: 同步非阻塞的编程模式: 不同阶段的作用: LuaJ ...

  7. 在python3 encode和decode 的使用

    说这个问题之前必须的介绍关于编码的在我们这的发展: 首先电脑能识别的最初的语言是二进制 ---010101这种 然后在是我们知道的ASSIC码 再过了就是 gb2312----------->g ...

  8. idea|properties文件乱码

    案例 在idea 打开有些配置文件,如config.properties,里面中文注释出现乱码 解决方案 点击FILE->Settings->Editor->File Encodin ...

  9. tmobst5an

    1.(单选题)SQL语言又称为() A)结构化定义语言 B)结构化控制语言 C)结构化查询语言 D)结构化操纵语言 解析:SQL语言又称为结构化查询语言 2.(单选题)只有满足联接条件的记录才包含在查 ...

  10. python笔记18(复习)

    今日内容 复习 内容详细 1.Python入门 1.1 环境的搭建 mac系统上搭建python环境. 环境变量的作用:方便在命令行(终端)执行可执行程序,将可执行程序所在的目录添加到环境变量,那么以 ...