C#中接口的显式实现与隐式实现及其相关应用案例
C#中接口的显式实现与隐式实现
最近在学习演化一款游戏项目框架时候,框架作者巧妙使用接口中方法的显式实现来变相对接口中方法进行“密封”,增加实现接口的类访问方法的“成本”。
接口的显式实现和隐式实现:
先定义一个接口,接口中有这两个方法。
public interface ICanSingSong
{
void SingJayChow();
void SingOther();
}
接下来我们让InterfaceDesignExample 继承该接口。使用常用的隐式实现方法来实现SingJayChow方法,在Start函数中直接可以调用,而使用显式实现的接口方法SingOther,则需要将类的实力转换为接口类型才可以调用。
这样相当于告诉类,ICanSingSong.SingOther()样式就是表明SIngOther是属于ICanSingSong接口中的“私有方法”。调用时候先要将类类型转换为接口。
public class InterfaceDesignExample : MonoBehaviour,ICanSingSong
{
void Start()
{
//接口的隐式实现 可以直接调用
SingJayChow();
//显示调用则需要 准换成接口 调用
//this.SingOther();
(this as ICanSingSong).SingOther();
}
/// <summary>
/// 接口的隐式实现
/// </summary>
public void SingJayChow()
{
Debug.Log("你说家是唯一的城堡,随着稻香一路奔跑~");
}
/// <summary>
/// 接口的显式实现
/// </summary>
void ICanSingSong.SingOther()
{
Debug.Log("lalalalalalallalaal!");
}
}

我们这样做的目的之一就是为了增加类对接口中的一些方法的调用成本,和使用“private”修饰有类似效果,降低对方法乱用的可能。
接口-抽象类-子类 使用显式实现接口方法
同样,我们先声明一个接口:
//接口 Application
public interface IApplication
{
void Start();
void Update();
void Destroy();
void Test();
}
抽象类继承该接口,并对生命周期函数进行显式实现,而供子类继承实现的方法为OnXXX
//抽象类
public abstract class Application : IApplication
{
//不希望子类去访问实现接口的方法
//使用显式调用
void IApplication.Start()
{
OnStart();
}
void IApplication.Update()
{
OnUpdate();
}
void IApplication.Destroy()
{
OnDestroy();
}
public void Test()
{
Debug.Log("我是测试方法,隐式实现接口的方法,子类可以轻松访问我");
}
//希望子类的实现的方法
public abstract void OnStart();
public abstract void OnUpdate();
public abstract void OnDestroy();
}
继承抽象类的子类对生命周期函数进行实现:
//子类
public class SubApplication : Application
{
public override void OnStart()
{
Test();
//Start(); 情况会发生递归调用 造成堆栈溢出 而此方法使用现实实现 所以在子类中无法访问 避免情况发生
Debug.Log("OnStart");
}
public override void OnUpdate()
{
Debug.Log("OnUpdate");
}
public override void OnDestroy()
{
Debug.Log("OnDestroy");
}
}
最后我们调用函数:
//测试调用
//通过接口调用 显示实现的方法
IApplication application = new SubApplication();
application.Start();
application.Update();
application.Destroy();
//通过类 无法调用显示实现的方法 只能访问使用OnXXXX方法
Application application2 = new SubApplication();
application2.OnStart();
application2.OnUpdate();
application2.OnDestroy();

这样我们可以体会到接口作为高抽象层的存在,可以调用子类具体实现的生命周期函数,而子类却无法访问显示实现的接口中“私有”的生命周期函数。
接口--子接口--静态类拓展 实现对接口函数的访问修饰
咳咳~故事开始!
作为一个资深Jay迷,我同样认识一个自称曲库的小精灵【SongLibrary】,它最拿手的三首歌曲是晴天、彩虹和说好不哭.
//曲库
public class SongLibrary
{
public void SingSunny()
{
Debug.Log("晴天:刮风这天,我试着握你的手~");
}
//彩虹
public void SingRainbow()
{
Debug.Log("彩虹:你要离开,我知道很简单~");
}
public void SingNoCry()
{
Debug.Log("说好不哭:说好不哭让我走~");
}
}
这个小曲库精灵居住在抽象出的留声机中,我可以通过留声机来和它交流播放对应的歌曲。
public interface ISingAllSong
{
SongLibrary songLibrary { get; }
}
留声机上有三个按钮,对应播放歌曲,
播放晴天的按钮功能:
抽象出子接口来继承曲库接口,通过静态类拓展来调用曲库中对应方法播放歌曲。
public interface ISingSunny : ISingAllSong
{
}
//静态类拓展
public static class SingSunnyExtensions
{
public static void SingSunny(this ISingSunny self)
{
self.songLibrary.SingSunny();
}
}
同样的方式,两个子接口。两个继承子接口的静态类负责调用曲库中的方法:
//彩虹
public interface ISingRainbow : ISingAllSong
{
}
//静态类拓展
public static class SingRainbowExtensions
{
public static void SingRainbow(this ISingRainbow self)
{
self.songLibrary.SingRainbow();
}
}
//说好不哭
public interface ISingNoCry : ISingAllSong
{
}
//静态类拓展
public static class SingNoCryExtensions
{
public static void SingNoCry(this ISingNoCry self)
{
self.songLibrary.SingNoCry();
}
}
这样我们使用三个静态类来调用留声机【interface】中居住的精灵【class】的方法,实现三个按钮功能。
当我想听歌时候,我只需要按照我的需求,搭配继承对应的子接口即可播放对应的歌曲,不用怕我按下去的是晴天歌曲播放按钮而短路到播放其它的歌曲曲目。
这样保证,拿到对应的子按钮,只能播放对应的歌曲,保证曲目播放的有序性。
public class InterfaceRuleExample : MonoBehaviour
{
public class OnlySingSunny : ISingSunny
{
SongLibrary ISingAllSong.songLibrary { get; } = new SongLibrary();
}
public class OnlySingRainbowNoCry : ISingRainbow,ISingNoCry
{
SongLibrary ISingAllSong.songLibrary { get; } = new SongLibrary();
}
void Start()
{
var onlySingSunny = new OnlySingSunny();
onlySingSunny.SingSunny();
//不能访问
//onlySingSunny.SingRainbow()
//onlySingSunny.SingNoCry();
var SingRainbowNoCry = new OnlySingRainbowNoCry();
SingRainbowNoCry.SingRainbow();
SingRainbowNoCry.SingNoCry();
//无法访问
//SingRainbowNoCry.SingSUnny();
}
}

总结一下:
使用显示实现方式来对接口中方法进行实现,子类是无法直接调用的,需要将类转换为接口类型才可以调用。
同时如果接口中的方法不想让子类直接调用,可以让抽象类继承接口原生方法,在抽象类中进行方法声明供子类调用,避免子类对抽象层的直接交互。同时,使用静态类拓展来限制子接口对父接口中存在函数方法的访问,保证类对所需方法的规范使用。
也就是说,尽可能不让表层具象的类轻松的访问到抽象层的其它不需要的功能,即类需要什么就继承对应的子接口,实现对应功能即可,多余的功能不要访问。
当然也建议阅读一下官方社区对显式接口的实现的解释说明。
参考文章:
[显式接口实现 - C# | Microsoft Learn](
C#中接口的显式实现与隐式实现及其相关应用案例的更多相关文章
- selenium-webdriver中的显式等待与隐式等待
在selenium-webdriver中等待的方式简单可以概括为三种: 1 导入time包,调用time.sleep()的方法传入时间,这种方式也叫强制等待,固定死等一个时间 2 隐式等待,直接调用i ...
- C# Interface显式实现和隐式实现
c#中对接口的实现方式有两种:隐式实现和显式实现,之前一直没仔细看过,今天查了些资料,在这里整理一下. 隐式实现的例子 interface IChinese { string Speak(); } p ...
- Scala 中的隐式转换和隐式参数
隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...
- C# 数据类型转换 显式转型、隐式转型、强制转型
C# 的类型转换有 显式转型 和 隐式转型 两种方式. 显式转型:有可能引发异常.精确度丢失及其他问题的转换方式.需要使用手段进行转换操作. 隐式转型:不会改变原有数据精确度.引发异常,不会发生任何问 ...
- (java)selenium webdriver学习---三种等待时间方法:显式等待,隐式等待,强制等待
selenium webdriver学习---三种等待时间方法:显式等待,隐式等待,强制等待 本例包括窗口最大化,刷新,切换到指定窗口,后退,前进,获取当前窗口url等操作: import java. ...
- Java并发之显式锁和隐式锁的区别
Java并发之显式锁和隐式锁的区别 在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchr ...
- Scala 深入浅出实战经典 第61讲:Scala中隐式参数与隐式转换的联合使用实战详解及其在Spark中的应用源码解析
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...
- 多态设计 zen of python poem 显式而非隐式 延迟赋值
总结 1.python支持延迟赋值,但是给调用者带来了困惑: 2.显式而非隐式,应当显式地指定要初始化的变量 class Card: def __init__(self, rank, suit): s ...
- 大数据技术之_16_Scala学习_06_面向对象编程-高级+隐式转换和隐式值
第八章 面向对象编程-高级8.1 静态属性和静态方法8.1.1 静态属性-提出问题8.1.2 基本介绍8.1.3 伴生对象的快速入门8.1.4 伴生对象的小结8.1.5 最佳实践-使用伴生对象解决小孩 ...
- Scala 学习之路(十三)—— 隐式转换和隐式参数
一.隐式转换 1.1 使用隐式转换 隐式转换指的是以implicit关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能.示例如下: // 普通人 clas ...
随机推荐
- 粗心的小红qsnctfwp
将原 apk 安装包后缀名修改为 zip 将其中的 classes3.dex 文件解压 使用 Notepad++ 或其他工具打开 classes3.dex,将第 2 行的 38 修改为 35 或 36 ...
- android 找不到设备
前言 当我们安装android studio的时候,测试的时候,你可能找不到设备. 我遇到的有两种情况,一种是本身就需要安装插件,如一些低端机或者有些小米机. 还有一种情况需要去触发一下,有些华为手机 ...
- 吴恩达机器学习课后作业ex1
题目大体意思就是输入的是某地的人口,输出的是某地方的收益. 题目及数据集下载: https://wwa.lanzous.com/b054sprza 密码:ba3w 大体模型如下图:现在X前边加一列值为 ...
- 浅析Golang map的实现原理
Golang中的map底层使用的数据结构是hash table,基本原理就和基础的散列表一致,重点是Golang在设计中采用了分桶(Bucket),每个桶里面支持多个key-value元素的这种思路, ...
- vue+scss混合(mixins)使用(css代码的vuex(公共管理))
scss混合(mixins)使用 例一.使用混合mixins中的变量来定义一个n行文本溢出隐藏的公用样式. 1.创建mixins.scss文件 //文本n行溢出隐藏 @mixin ellipsisBa ...
- 【SQL】IN和EXISTS谁的效率更高
[SQL]IN和EXISTS谁的效率更高 总结: 索引设置好的情况下 子查询数据量大的,用exists 子查询数据量小的,用in 原文连接:https://zhuanlan.zhihu.com/p/4 ...
- 从no-code到low-code:企业级hpaPaaS的未来
简介: 本文将简单谈一谈基于 no-code > low-code > pro-code 渐进式思路的研发体系. 引子 宜搭负责人骁勇给我举过一个例子,我们小时候逢年过节穿的衣服,都是去裁 ...
- 双11特刊|十年磨一剑,云原生多模数据库Lindorm 2021双11总结
前言 2021 年,转眼 Lindorm 已经在阿里发展了十年的时间,从基于 HBase 深度改造的 Lindorm 1.0 版本,到全面重构,架构大幅升级的 Lindorm 2.0 版本:从单一的 ...
- 使用 SizeBench 分析 Exe 文件体积
本文将介绍微软开源免费的 SizeBench 工具,使用 SizeBench 工具可以用来分析 Exe 二进制文件的体积,分析 Exe 文件大小里面有哪些是可以优化的 下载安装方式: 请前往应用商店安 ...
- WPF 调试依赖属性变更方法
本文告诉大家如何调试 WPF 的某个依赖属性被变更的方法 在 WPF 里面,所有的依赖属性都有带通知的功能,通过带通知的功能,可以在通知里加上断点,通过调用堆栈了解是哪个模块调用的 对依赖属性添加通知 ...