c#中接口的使用方法图解_C#图解教程 第十五章 接口

news/2024/7/5 4:05:09

接口

什么是接口

接口是指定一组函数成员而不实现它们的引用类型。所以只能类和结构来实现接口。

这种描述比较抽象,直接来看个示例。

下例中,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());

}

}

}

}


http://lihuaxi.xjx100.cn/news/242813.html

相关文章

Restore Volume 操作 - 每天5分钟玩转 OpenStack(60)

前面我们 backup 了 voluem&#xff0c;今天我们将讨论如何 restore volume。 restore 的过程其实很简单&#xff0c;两步走&#xff1a; 在存储节点上创建一个空白 volume。 将 backup 的数据 copy 到空白 voluem 上。 下面我们来看 restore 操作的详细流程&#xff1a; …

学习Java知识应该注意哪些基础原则

想要做java程序猿&#xff0c;学习起来没有那么快的&#xff0c;尤其是零基础学员&#xff0c;java技术在学习的过程中是比较枯燥的&#xff0c;下面小编就为大家详细的介绍一下学习Java知识应该注意哪些基础原则&#xff0c;方便大家在学习的时候能够更加有效率! 学习Java知识…

BZOJ 1194: [HNOI2006]潘多拉的盒子 [DP DFA]

传送门 题意&#xff1a; s个DFA&#xff0c;选出尽量多的自动机a0, a1, a2, . . . , at&#xff0c;使得a1包含a0、a2包 含a1&#xff0c;以此类推。s ≤ 50。 DFA的字符集为{0,1}&#xff0c;有的节点是输出源&#xff0c;节点数n ≤ 50。 判断出包含关系后就是裸的最长路&a…

RestTemplate的GET多参数请求转发

请求方 RequestMapping(value "/movieFindByUser",method RequestMethod.GET)public Object findByUser(RequestParam(name "name", required false) String name, RequestParam(name "username", required false) String username, Reque…

华为云大数据存储的冗余方式是三副本_大数据入门:HDFS数据副本存放策略

大数据处理当中&#xff0c;数据储存始终是一个重要的环节&#xff0c;从现阶段的市场现状来说&#xff0c;以Hadoop为首的大数据技术框架&#xff0c;仍然占据主流地位&#xff0c;而Hadoop的HDFS&#xff0c;在数据存储方面&#xff0c;仍然得到重用。今天的大数据入门分享&a…

琐碎的知识库

禁止当前 Activity截图 <pre> Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); setContentView(R.layout.activity_main); } </pre> 获取当前…

k均值聚类算法考试例题_一文读懂K-means聚类算法

1、引言什么是聚类&#xff1f;我们通常说&#xff0c;机器学习任务可以分为两类&#xff0c;一类是监督学习&#xff0c;一类是无监督学习。监督学习&#xff1a;训练集有明确标签&#xff0c;监督学习就是寻找问题&#xff08;又称输入、特征、自变量&#xff09;与标签&…

JAVA工资高吗

JAVA工资高吗?很多人都是非常关注这个问题的&#xff0c;近几年&#xff0c;java技术在互联网行业有了自己的一席之地&#xff0c;越来越多的人都投身到java技术行业&#xff0c;下面我们来看看详细的介绍。 JAVA工资高吗? 近年来,在美国、加拿大、澳大利亚、新加坡等发达国家…