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 ...
随机推荐
- LeetCode:999. 车的可用捕获量
999. 车的可用捕获量 在一个 8 x 8 的棋盘上,有一个白色车(rook).也可能有空方块,白色的象(bishop)和黑色的卒(pawn).它们分别以字符 "R"," ...
- 密码学中的RSA算法与椭圆曲线算法
PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. 在数字安全领域,加密算法扮演着至关重要的角色.它们确保了信息的机 ...
- React 逃离闭包陷阱
众所周知,JavaScript 中的闭包(Closures)一定是这种语言最可怕的特性之一,即使是无所不知的 ChatGPT 也是这样说的.另外它可能也是最隐蔽的语言特性之一,我们在编写 React ...
- NOIP模拟四
NOIP模拟四 number 题目描述 现有 \(2^n\) 个点,点编号为 \(0\sim2^n-1\). 定义这些点的一张异或图为: 先选定一个集合 \(S\). 对于原图上编号为 \(x\) 和 ...
- JVM简明笔记1:JVM 概述
什么是JVM JVM 即 Java Virtual Machine,中文名为 Java虚拟机. 一般情况下 C/C++ 程序,编译成二进制文件后,就可以直接执行了: Java 需要使用 javac 编 ...
- 阿里云RemoteShuffleService 新功能:AQE 和流控
简介:阿里云EMR 自2020年推出 Remote Shuffle Service(RSS)以来,帮助了诸多客户解决 Spark 作业的性能.稳定性问题,并使得存算分离架构得以实施.为了更方便大家使 ...
- 更灵活的边缘云原生运维:OpenYurt 单元化部署新增 Patch 特性
简介: 在正文开始之前,我们先回顾一下单元化部署的概念和设计理念.在边缘计算场景下,计算节点具有很明显的地域分布属性,相同的应用可能需要部署在不同地域下的计算节点上. 作者 | 张杰(冰羽)来源 | ...
- 特权同学笔记-《边练边学》-在QP里调用modelsim的步骤
在QP里调用Modelsim需要先设置仿真参数和工具路径. 在QP调用modelsim的步骤 1. 在QP里建立工程,代码,分析综合:2. 添加testbench代码,processing-start ...
- 解密Prompt系列28. LLM Agent之金融领域摸索:FinMem & FinAgent
本章介绍金融领域大模型智能体,并梳理金融LLM的相关资源.金融领域的大模型智能体当前集中在个股交易决策这个相对简单的场景,不需要考虑多资产组合的复杂场景.交易决策被简化成市场上各个信息,包括技术面,消 ...
- 算法~PBKDF2-SHA让密码更安全
摘要:在当今的数字世界中,密码安全是至关重要的.为了保护用户密码免受未经授权的访问和破解,Password-Based Key Derivation Function 2 (PBKDF2)算法成为了一 ...