​      在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. mongdb角色的授权

    开启cmd窗口切换到cd D:\programs\mongoDB\bin D:\programs\mongoDB\bin>mongo MongoDB shell version v3.4.6 c ...

  2. [状压DP思路妙题]图

    源自 luhong 大爷的 FJ 省冬令营模拟赛题 Statement 给定一个 \(n\) 个点 \(m\) 条边的图,没有重边与自环 每条边的两端点编号之差不超过 \(12\) 求选出一个非空点集 ...

  3. MyBatis3——输出参数ResultType、动语态sql

    输出参数ResultType 1.输出参数为简单类型(8个基本+String) 2.输出参数为对象类型 3.输出参数为实体对象类型的集合:虽然输出类型为集合,但是resultType依然写集合的元素类 ...

  4. 关于C++读入数字按位取出与进制转换问题

    这一片博客我就不写具体的一个题了,只是总结一种典型问题——读入数字按位取出. 就拿数字12345举例吧. 是首先,我们要取出个位.这样取出: 12345/1=12345 12345%10=5.    ...

  5. 【Oracle】内连接、外连接、(+)的使用

    表各有A, B两列 A B 001 10A 002 20A A B 001 10B 003 30B A B 001 10C 004 40C 连接分为两种:内连接与外连接. A.内连接 内连接,即最常见 ...

  6. TCP客户端服务器编程模型

    1.客户端调用序列 客户端编程序列如下: 调用socket函数创建套接字 调用connect连接服务器端 调用I/O函数(read/write)与服务器端通讯 调用close关闭套接字 2.服务器端调 ...

  7. 实验三:在eNSP上进行Hybrid链路类型端口实验

    1.配置图 2.配置命令 LSW1的命令配置如下: <Huawei>system-view 进入特权模式 [Huawei]vlan batch 2 3 99 创建vlan2.vlan3.v ...

  8. Day9-Python3基础-多线程、多进程

    1.进程.与线程区别 2.python GIL全局解释器锁 3.线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Que ...

  9. Shrio | java.io.IOException: Resource [classpath:shiro.ini] could not be found

    案例 今天项目启动时一直报异常,看了错误日志发现是shrio文件找不到引起的,异常: java.io.IOException: Resource [classpath:shiro.ini] could ...

  10. File流与IO流 看这一篇就够了

    主要内容 File类 递归 IO流 字节流 字符流 异常处理 Properties 缓冲流 转换流 序列化流 打印流 学习目标 [ ] 能够说出File对象的创建方式 [ ] 能够说出File类获取名 ...