​      在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. synchronized底层实现

    1.锁升级的过程 当多个线程同时竞争一个对象监视器时:当前对象结构中的mark word中是否是当前线程id,如果是则当前线程获得偏向锁. 如果不是,则通过CAS将当前线程id置换到mark word ...

  2. [洛谷P4585] [FJOI2015] 火星商店问题

    Description 火星上的一条商业街里按照商店的编号 \(1\),\(2\) ,-,\(n\) ,依次排列着 \(n\) 个商店.商店里出售的琳琅满目的商品中,每种商品都用一个非负整数 \(va ...

  3. 看透Spring MVC:源代码分析与实践 (Web开发技术丛书)

    第一篇 网站基础知识 第1章 网站架构及其演变过程2 1.1 软件的三大类型2 1.2 基础的结构并不简单3 1.3 架构演变的起点5 1.4 海量数据的解决方案5 1.4.1 缓存和页面静态化5 1 ...

  4. 爬虫之pyspider 安装

    解决方法: 利用wheel安装 S1: pip install wheelS2: 进入www.lfd.uci.edu/~gohlke/pythonlibs/,Ctrl + F查找pycurl S3:这 ...

  5. mysql安装教程linux

    https://www.cnblogs.com/YangshengQuan/p/8431520.html 设置sql远程访问

  6. Python使用requests发送post请求的三种方式

    1.我们使用postman进行接口测试的时候,发现POST请求方式的编码有3种,具体的编码方式如下: A:application/x-www-form-urlencoded ==最常见的post提交数 ...

  7. java正则使用全记录!

    一 开坑! 场景: 将动态uri中{} 替换成 至少出现一次的任意字符进行匹配, 比如 loclahost:8080/{name}/{9527}  -> localhost:8080/.{1,} ...

  8. Java异常 | Error:java: Compilation failed: internal java compiler error

    背景 今天网上下载了一个项目,编辑运行报如下异常: Error:java: Compilation failed: internal java compiler error 经过往经验,读项目的编译环 ...

  9. python笔记16

    1.今日内容 模块基础知识 time/datetime json/picle shutil logging 其他 2.内容回顾和补充 2.1模块(类库) 内置 第三方 自定义 面试题: 列举常用内置模 ...

  10. C++ STL IO流 与 Unicode (UTF-16 UTF-8) 的协同工作

    09年研究技术的大神真的好多,本文测试有很多错误,更正后发布下(可能与编辑器相关). file.imbue(locale(file.getloc(), new codecvt_utf8<wcha ...