c#中接口的使用方法图解_C#图解教程 第十五章 接口
接口
什么是接口
接口是指定一组函数成员而不实现它们的引用类型。所以只能类和结构来实现接口。
这种描述比较抽象,直接来看个示例。
下例中,Main方法创建并初始化了一个CA类的对象,并将该对象传递给PrintInfo方法。
classCA
{public stringName;public intAge;
}classCB
{public stringFirst;public stringLast;public doublePersonsAge;
}classProgram
{static voidPrintInfo(CA item)
{
Console.WriteLine("Name: {0},Age {1}",item.Name,item.Age);
}static voidMain()
{
CA a=new CA(){Name="John Doe",Age=35};
PrintInfo(a);
}
}
只要传入的是CA类型的对象,PrintInfo就能正常工作。但如果传入的是CB,就不行了。
现在的代码不能满足上面的需求,原因有很多。
PrintInfo的形参指明了实参必须为CA类型的对象
CB的结构与CA不同,字段的名称和类型与CA不一样
接口解决了这一问题。
声明一个IInfo接口,包含两个方法–GetName和GetAge,每个方法都返回string
类CA和CB各自实现了IInfo接口,并实现了两个方法
Main创建了CA和CB的实例,并传入PrintInfo
由于类实例实现了接口,PrintInfo可以调用那两个方法,每个类实例执行各自的方法,就好像是执行自己类声明中的方法
interface IInfo //声明接口
{stringGetName();stringGetAge();
}class CA:IInfo //声明了实现接口的CA类
{public stringName;public intAge;public string GetName(){returnName;}public string GetAge(){returnAge.ToString();}
}class CB:IInfo //声明了实现接口的CB类
{public stringFirst;public stringLast;public doublePersonsAge;public string GetName(){return First+""+Last;}public string GetAge(){returnPersonsAge.ToString();}
}classProgram
{static voidPrintInfo(IInfo item)
{
Console.WriteLine("Name: {0},Age {1}",item.GetName(),item.GetAge());
}static voidMain()
{var a=new CA(){Name="John Doe",Age=35};var b=new CB(){First="Jane",Last="Doe",PersonsAge=33};
PrintInfo(a);
PrintInfo(b);
}
}
使用IComparable接口的示例
第一行代码创建了包含5个无序整数的数组
第二行代码使用了Array类的静态Sort方法来排序元素
用foreach循环输出它们,显式以升序排序的数字
var myInt=new[]{20,4,16,9,2};
Array.Sort(myInt);foreach(var i inmyInt)
{
Console.WriteLine("{0}",i);
}
Sort方法在int数组上工作良好,但如果我们尝试在自己的类上使用会发生什么呢?
classMyClass
{public intTheValue;
}
...
MyClass[] mc=new MyClass[5];
...
Array.Sort(mc);
运行上面的代码,将会得到一个异常。Sort并不能对MyClass对象数组排序,因为它不知道如何比较自定义的对象。Array类的Sort方法其实依赖于一个叫做IComparable的接口,它声明在BCL中,包含唯一的方法CompareTo。
public interfaceIComparable
{int CompareTo(objectobj);
}
尽管接口声明中没有为CompareTo方法提供实现,但IComparable接口的.NET文档中描述了该方法应该做的事情,可以在创建实现该接口的类或结构时参考。
文档中写到,在调用CompareTo方法时,它应该返回以下几个值:
负数值 当前对象小于参数对象
整数值 当前对象大于参数对象
零 两个对象相等
我们可以通过让类实现IComparable来使Sort方法可以用于MyClass对象。要实现一个接口,类或结构必须做两件事情:
必须在基类列表后面列出接口名称
必须实现接口的每个成员
例:MyClass中实现了IComparable接口
classMyClass:IComparable
{public intTheValue;public int CompareTo(objectobj)
{var mc=(MyClass)obj;if(this.TheValuemc.TheValue)return 1;return 0;
}
}
例:完整示例代码
classMyClass:IComparable
{public intTheValue;public int CompareTo(objectobj)
{var mc=(MyClass)obj;if(this.TheValuemc.TheValue)return 1;return 0;
}
}classProgram
{static void PrintInfo(strings,MyClass[] mc)
{
Console.WriteLine(s);foreach(var m inmc)
{
Console.WriteLine("{0}",m.TheValue);
}
Console.WriteLine("");
}static voidMain()
{var myInt=new[] {20,4,16,9,2};
MyClass[] mcArr=new MyClass[5];for(int i=0;i<5;i++)
{
mcArr[i]=newMyClass();
mcArr[i].TheValue=myInt[i];
}
PrintOut("Initial Order:",mcArr);
Array.Sort(mcArr);
PrintOut("Sorted Order:",mcArr);
}
}
声明接口
上一节使用的是BCL中已有的接口。现在我们来看看如何声明接口。
关于声明接口,需要知道的重要事项如下:
接口声明不能包含以下成员
数据成员
静态成员
接口声明只能包含如下类型的非静态成员函数的声明
方法
属性
事件
索引器
这些函数成员的声明不能包含任何实现代码,只能用分号
按照惯例,接口名称以大写字母I(Interface)开始
与类和结构一样,接口声明也可以分布
例:两个方法成员接口的声明
关键字 接口名称
↓ ↓interfaceIMyInterface1
{int DoStuff(int nVar1,long lVar2); //分号替代了主体
double DoOtherStuff(string s,longx);
}
接口的访问性和接口成员的访问性之间有一些重要区别
接口声明可以有任何的访问修饰符public、protected、internal或private
然而,接口的成员是隐式public的,不允许有任何访问修饰符,包括public
接口允许访问修饰符
↓public interfaceIMyInterface2
{private int Method1(int nVar1); //错误
↑
接口成员不允许访问修饰符
}
实现接口
只有类和结构才能实现接口。
在基类列表中包括接口名称
实现每个接口成员
例:MyClass实现IMyInterface1接口
classMyClass:IMyInterface1
{int DoStuff(int nVar1,longlVar2)
{...}//实现代码
double DoOtherStuff(string s,longx)
{...}//实现代码
}
关于实现接口,需要了解以下重要事项:
如果类实现了接口,它必须实现接口的所有成员
如果类从基类继承并实现了接口,基类列表中的基类名称必须放在所有接口之前。
基类必须放在最前面 接口名
↓ ↓class Derived:MyBaseClass,IIfc1,IEnumerable,IComparable
简单接口示例
interfaceIIfc1
{void PrintOut(strings);
}classMyClass:IIfc1
{public void PrintOut(strings)
{
Console.WriteLine("Calling through: {0}",s);
}
}classProgram
{static voidMain()
{var mc=newMyClass();
mc.PrintOut("object");
}
}
接口是引用类型
接口不仅是类或结构要实现的成员列表。它是一个引用类型。
我们不能直接通过类对象的成员访问接口。然而,我们可以通过把类对象引用强制转换为接口类型来获取指向接口的引用。一旦有了接口引用,我们就可以使用点号来调用接口方法。
例:从类对象引用获取接口引用
IIfc1 ifc=(IIfc1)mc; //转换为接口,获取接口引用
ifc.PrintOut("interface"); //使用接口的引用调用方法
例:类和接口的引用
interfaceIIfc1
{void PrintOut(strings);
}classMyClass:IIfc1
{public void PrintOut(strings)
{
Console.WriteLine("Calling through: {0}",s);
}
}classProgram
{static voidMain()
{var mc=newMyClass();
mc.PrintOut("object"); //调用类对象的实现方法
IIfc1 ifc=(IIfc1)mc;
ifc.PrintOut("interface"); //调用引用方法
}
}
接口和as运算符
上一节,我们已经知道可以使用强制转换运算符来获取对象接口引用,另一个更好的方式是使用as运算符。
如果我们尝试将类对象引用强制转换为类未实现的接口的引用,强制转换操作会抛出异常。我们可以通过as运算符来避免该问题。
如果类实现了接口,表达式返回指向接口的引用
如果类没有实现接口,表达式返回null
ILiveBirth b=a asILiveBirth;if(b!=null)
{
Console.WriteLine("Baby is called: {0}",b.BabyCalled());
}
实现多个接口
类或结构可以实现多个接口
所有实现的接口必须列在基类列表中并以逗号分隔(如果有基类名称,则在其后)
interface IDataRetrieve{intGetData();}interface IDataStore{void SetData(intx);}classMyData:IDataRetrieve,IDataStore
{intMem1;public int GetData(){returnMem1;}public void SetData(int x){Mem1=x;}
}classProgram
{static voidMain()
{var data=newMyData();
data.SetData(5);
Console.WriteLine("Value = {0}",data.GetData());
}
}
实现具有重复成员的接口
由于接口可以多实现,有可能多个接口有相同的签名和返回类型。编译器如何处理这种情况呢?
例:IIfc1和IIfc2具有相同签名
interfaceIIfc1
{void PrintOut(strings);
}interfaceIIfc2
{void PrintOut(stringt);
}
答案是:如果一个类实现了多接口,并且其中有些接口有相同签名和返回类型,那么类可以实现单个成员来满足所有包含重复成员的接口。
例:MyClass 类实现了IIfc1和IIfc2.PrintOut满足了两个接口的需求。
classMyClass:IIfc1,IIfc2
{public void PrintOut(string s)//两个接口单一实现
{
Console.WriteLine("Calling through: {0}",s);
}
}classProgram
{static voidMain()
{var mc=newMyClass();
mc.PrintOut("object");
}
}
多个接口的引用
如果类实现了多接口,我们可以获取每个接口的独立引用。
例:下面类实现了两个具有当PrintOut方法的接口,Main中以3种方式调用了PrintOut。
通过类对象
通过指向IIfc1接口的引用
通过指向IIfc2接口的引用
interfaceIIfc1
{void PrintOut(strings);
}interfaceIIfc2
{void PrintOut(stringt);
}classMyClass:IIfc1,IIfc2
{public void PrintOut(strings)
{
Console.WriteLine("Calling through: {0}",s);
}
}classProgram
{static voidMain()
{var mc=newMyClass();
IIfc1 ifc1=(IIfc1)mc;
IIfc2 ifc2=(IIfc2)mc;
mc.PrintOut("object");
ifc1.PrintOut("interface 1");
ifc2.PrintOut("interface 2");
}
}
派生成员作为实现
实现接口的类可以从它的基类继承实现的代码。
例:演示 类从基类代码继承了实现
IIfc1是一个具有PrintOut方法成员的接口
MyBaseClass包含一个PrintOut方法,它和IIfc1匹配
Derived类有空的声明主体,但它派生自MyBaseClass,并在基类列表中包含了IIfc1
即使Derived的声明主体为空,基类中的代码还是能满足实现接口方法的需求
interfaceIIfc1
{void PrintOut(strings);
}classMyBaseClass
{public void PrintOut(strings)
{
Console.WriteLine("Calling through: {0}",s);
}
}classDerived:MyBaseClass,IIfc1
{
}classProgram
{static voidMain()
{var d=newDerived();
d.PrintOut("object");
}
}
显式接口成员实现
上面,我们已经看到了单个类可以实现多个接口需要的所有成员。
但是,如果我们希望为每个接口分离实现该怎么做呢?这种情况下,我们可以创建显式接口成员实现。
与所有接口实现相似,位于实现了接口的类或结构中
它使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔符号构成
classMyClass:IIfc1,IIfc2
{void IIfc1.PrintOut(strings)
{...}void IIfc2.PrintOut(strings)
{...}
}
例:MyClass为两个解耦的成员声明了显式接口成员实现。
interfaceIIfc1
{void PrintOut(strings);
}interfaceIIfc2
{void PrintOut(stringt);
}classMyClass:IIfc1,IIfc2
{void IIfc1.PrintOut(strings)
{
Console.WriteLine("IIfc1: {0}",s);
}void IIfc2.PrintOut(strings)
{
Console.WriteLine("IIfc2: {0}",s);
}
}classProgram
{static voidMain()
{var mc=newMyClass();
IIfc1 ifc1=(IIfc1)mc;
ifc1.PrintOut("interface 1");
IIfc2 ifc2=(IIfc2)mc;
ifc2.PrintOut("interface 2");
}
}
如果有显式接口成员实现,类级别的实现是允许的,但不是必需的。显式实现满足了类或结构必须实现的方法。因此,我们可以有如下3种实现场景。
类级别实现
显式接口成员实现
类级别和显式接口成员实现
访问显式接口成员实现
显式接口成员实现只可以通过指向接口的引用来访问。即其他的类成员都不可以直接访问它们。
例:如下MyClass显式实现了IIfc1接口。注意,即使是MyClass的另一成员Method1,也不可以直接访问显式实现。
Method1的前两行编译错误,因为方法在尝试直接访问实现
只有Method1的最后一行代码才可以编译,因此它强制转换当前对象的引用(this)为接口类型的引用,并使用这个指向接口的引用来调用显式接口实现
classMyClass:IIfc1
{void IIfc1.PrintOut(strings)
{
Console.WriteLine("IIfc1");
}public voidMethod1()
{
PrintOut("..."); //编译错误
this.PrintOut("..."); //编译错误
((IIfc1)this).PrintOut("...");
↑
转换为接口引用
}
}
这个限制对继承产生了重要影响。由于其他类成员不能直接访问显式接口成员实现,衍生类的成员也不能直接访问它们。它们必须总是通过接口的引用来访问。
接口可以继承接口
之前我们已经知道接口实现可以从基类继承,而接口本身也可以从一个或多个接口继承。
要指定某个接口继承其他的接口,应在接口声明中把某接口以逗号分隔的列表形式放在接口名称的冒号之后
与类不同,它在基类列表中只能有一个类名,接口可以在基接口列表中有任意多个接口
列表中的接口本身可以继承其他接口
结果接口包含它声明的所有接口和所有基接口的成员
interfaceIDataIO:IDataRetrieve,IDataStore
{
...
}
例:IDataIO接口继承了两个接口
interfaceIDataRetrieve
{intGetData();
}interfaceIDataStore
{void SetData(intx);
}interfaceIDaTaIO:IDataRetrieve,IDataStore
{
}classMyData:IDataIO
{intnPrivateData;public intGetData()
{returnnPrivateData;
}public void SetData(intx)
{
nPrivateData=x;
}
}classProgram
{static voidMain()
{var data=newMyData();
data.SetData(5);
Console.WriteLine("{0}",data.GetData());
}
}
不同类实现一个接口的示例
interface ILiveBirth //声明接口
{stringBabyCalled();
}class Animal{} //基类Animal
class Cat:Animal,ILiveBirth //声明Cat类
{stringILiveBirth.BabyCalled()
{return "kitten";
}
}class Dog:Animal,ILiveBirth //声明DOg类
{stringILiveBirth.BabyCalled()
{return "puppy";
}
}class Bird:Animal //声明Bird类
{
}classProgram
{static voidMain()
{
Animal[] animalArray=new Animal[3];
animalArray[0]=newCat();
animalArray[1]=newBird();
animalArray[2]=newDog();foreach(Animal a inanimalArray)
{
ILiveBirth b= a as ILiveBirth;//如果实现ILiveBirth
if(b!=null)
{
Console.WriteLine("Baby is called: {0}",b.BabyCalled());
}
}
}
}
相关文章:

提高C#编程水平的50个要点
1.总是用属性 (Property) 来代替可访问的数据成员 2.在 readonly 和 const 之间,优先使用 readonly 3.在 as 和 强制类型转换之间,优先使用 as 操作符 4.使用条件属性 (Conditional Attributes) 来代替条件编译语句 #if 5.总是为自定义类重载 ToString 方…

1.6 INSERT语句
1.6 INSERT语句正在更新内容,请稍后
基于Matlab的多层BP神经网络在非线性函数拟合中的应用
本图文详细介绍了如何利用Matlab神经网络工具箱实现多层BP神经网络对非线性函数的拟合。

华为云大数据存储的冗余方式是三副本_大数据入门:HDFS数据副本存放策略
大数据处理当中,数据储存始终是一个重要的环节,从现阶段的市场现状来说,以Hadoop为首的大数据技术框架,仍然占据主流地位,而Hadoop的HDFS,在数据存储方面,仍然得到重用。今天的大数据入门分享&a…

linux调试C++错误: 程序中有游离的‘\240’‘\302’
今天在网上找到一个小程序,一编译出现一大堆的错误: 程序中有游离的 ......开始觉得可能我从网页上直接复制的代码中有别的字符。于是把中文的全角空格全部替换了。这次好多了,少了一些,不过还有很多,调试信息说是XX行…

IE8下不识别indexOf的问题
1、为Array原型添加indexOf方法(如果学过面向对象,相当于给Array类添加实例方法),方法体如下: //添加数组IndexOf方法 if (!Array.prototype.indexOf){Array.prototype.indexOf function(elt /*, from*/){var len this.length >>> 0;var from Number(arguments[1])…
《C#精彩实例教程》小组阅读05 -- C#变量与常量
本微信图文介绍了C#的变量与常量。

k均值聚类算法考试例题_一文读懂K-means聚类算法
1、引言什么是聚类?我们通常说,机器学习任务可以分为两类,一类是监督学习,一类是无监督学习。监督学习:训练集有明确标签,监督学习就是寻找问题(又称输入、特征、自变量)与标签&…
《C#精彩实例教程》小组阅读06 -- C#运算符与表达式
本微信图文介绍了C#的运算符与表达式。

kvm启动报错
[rootstorage ~]# virsh -c qemu:///system list error: failed to connect to the hypervisor error: Failed to connect socket to /var/run/libvirt/libvirt-sock: No such file or directory原因:libvirt未启动解决方法[rootstorage ~]# libvirtd -d [rootst…

邀请参加活动的邀请函_圣诞节活动策划邀请函在线制作
2020年就要过去了,许多人说这一年很难,难上加南。莎士比亚说凡是过去,皆为序章。无论好的还是坏的终究会成为过往,向前看吧。圣诞节快要到来,商场开始布置精致的橱窗,电商巨头也在忙着做促销,幼…

比较全的字符串验证类,有人顶的话以后继续发
啥也不说看代码哈~ Codeusing System;using System.Collections.Generic;using System.Text;using System.Text.RegularExpressions;namespace Utility { public class ISExt { private static ISExt instance null; public static ISExt GetInstance…

设计模式(2)工厂方法模式(Factory Method)
设计模式(0)简单工厂模式 设计模式(1)单例模式(Singleton) 源码地址 0 工厂方法模式简介 0.0 工厂方法模式定义 工厂方法模式是在简单工厂模式基础上,为解决更复杂的对象创建问题而衍生进化出来…
基于Matlab的遗传算法优化BP神经网络在非线性函数拟合中的应用
本微信图文详细介绍了遗传算法优化BP神经网络初始权值阈值的过程,并通过实例说明该优化能够提升BP神经网络的预测精确程度。

android 加载h5页面部分机型滑动卡顿回弹_网易爆款H5 的交互方法参考
在早些年,H5其实更像是手机上的PPT,只支持点击、滑动这些基础手势操作。以内容展示为主,交互形式为辅。但到了今天,H5的玩法已经有了质的突破。不仅交互形式超多,形式与内容也能紧密结合,产生11大于2的效果…

使用SDL打造游戏世界之入门篇 - 1
来源:天极开发 作者:维维编译 出处:巧巧读书 2007-07-17 进入讨论组 简介 Simple DirectMedia Layer, 简称SDL,是一个自由的跨平台的多媒体开 发包,主要通过OpenGL和2D视频帧缓冲(framebuffer)提供对音频、键盘、鼠标…

RBAC新解 - 基于资源的权限管理
1、什么是角色 当说到程序的权限管理时,人们往往想到角色这一概念。角色是代表一系列可执行的操作或责任的实体,用于限定你在软件系统中能做什么、不能做什么。用户帐号往往与角色相关联,因此,一个用户在软件系统中能做什么取决于…
《C#精彩实例教程》小组阅读07 -- C#字符与字符串
本微信图文详细介绍了C#中的字符与字符串。

syslog打印不带等级_printk的日志级别和控制台级别
printk根据日志级别(loglevel)对消息进行分类。日志级别用宏定义,日志级别宏展开为一个字符串,在编译时由预处理器将它和消息文本拼接成一个字符串,因此printk 函数中日志级别宏和格式字符串间不能有逗号。下面是两个printk的例子,…
《C#精彩实例教程》小组阅读08 -- C#流程控制语句
本微信图文详细介绍了C#的流程控制语句。

ASPJPEG缩略图生成函数
好久没有发文章,贴一段代码出来晒晒!一段aspjpeg组件生成缩略图的代码,有4种生成方式,建议用最后一种,生成的缩略图最清晰而且不会拉伸、变形!做图片生成最好不过!// 缩略图生成函数 Code By S…

java面试题收集
2019独角兽企业重金招聘Python工程师标准>>> 1.什么是B/S架构?什么是C/S架构 B/S(Browser/Server),浏览器/服务器程序 C/S(Client/Server),客户端/服务端,桌面应用程序 2.你所知道网络协议有那些? HTTP&…

mobile还有人用吗 spring_话说,苹果手机语音备忘录功能还有人用吗?
hi,各位,苹果手机自带的语音备忘录功能还有人在用吗?前两天,有小伙伴在后台留言问:“苹果手机语音备忘录怎么恢复?”小编一时还有些恍惚“它是什么,手机上有吗?”,好在通…

MVC5 + EF6 完整入门教程三
期待已久的EF终于来了。 学完本篇文章,你将会掌握基于EF数据模型的完整开发流程。 本次将会完成EF数据模型的搭建和使用。 基于这个模型,将之前的示例添加数据库查询验证功能。 文章提纲 概述 & 要点 详细步骤 总结 概述 & 要点 下面是本文要点&…
Matlab编程与数据类型 -- 内联函数
本微信图文详细介绍了Matlab中的内联函数。

优化营商环境建议个人_优化营商环境的几点建议(三)
优化临沂的营商环境,重点是做好招商引资的后续环境优化!事关一个地区的财税收入,所以放眼全国,招商引资到哪里都是重点工作。早在2018年4月16日,临沂就召开了新旧动能转换暨开放型经济、招商引资工作动员大会ÿ…
基于Matlab的神经网络结合遗传算法在非线性函数极值寻优中的应用
本微信图文利用神经网络进行非线性函数数据的拟合并通过遗传算法对训练后的神经网络进行非线性函数极值寻优。

十分钟成为 Contributor 系列 | 为 TiDB 重构 built-in 函数
2019独角兽企业重金招聘Python工程师标准>>> 这是十分钟成为 TiDB Contributor 系列的第二篇文章,让大家可以无门槛参与大型开源项目,感谢社区为 TiDB 带来的贡献,也希望参与 TiDB Community 能为你的生活带来更多有意义的时刻。 …

研究生要这样度过!(转)
研究生要这样度过! 首先要知道研究生期间做什么?我认为研究生期间学生应该学三件事情: 1)建立合理的知识结构:尽量广地涉猎学科基本知识,尽量深地了解所研究领域的 方方面面、过去和现在 2)掌握…

后端开发面试自我介绍_字节跳动暑期实习后端开发面试经历
字节跳动后端实习是什么,字节跳动后端实习面试流程是怎样?今天小编就来帮助大家了解一下字节跳动后端实习面试到底有什么内容。(好了不皮了,开始正文)字节的面试流程总的来说还是挺享受的,和面试官两人的思…