接口
什么是接口
接口是指定一組函數(shù)成員而不實(shí)現(xiàn)它們的引用類型。所以只能類和結(jié)構(gòu)來實(shí)現(xiàn)接口。
這種描述比較抽象,直接來看個(gè)示例。
下例中,Main方法創(chuàng)建并初始化了一個(gè)CA類的對(duì)象,并將該對(duì)象傳遞給PrintInfo方法。
class CA
{
public string Name; public int Age;
}
class CB
{
public string First; public string Last; public double PersonsAge;
}
class Program
{
static void PrintInfo(CA item)
{
Console.WriteLine("Name: {0},Age {1}",item.Name,item.Age);
}
static void Main()
{
CA a=new CA(){Name="John Doe",Age=35};
PrintInfo(a);
}
}
只要傳入的是CA類型的對(duì)象,PrintInfo就能正常工作。但如果傳入的是CB,就不行了。
現(xiàn)在的代碼不能滿足上面的需求,原因有很多。
- PrintInfo的形參指明了實(shí)參必須為CA類型的對(duì)象
- CB的結(jié)構(gòu)與CA不同,字段的名稱和類型與CA不一樣
接口解決了這一問題。
- 聲明一個(gè)IInfo接口,包含兩個(gè)方法–GetName和GetAge,每個(gè)方法都返回string
- 類CA和CB各自實(shí)現(xiàn)了IInfo接口,并實(shí)現(xiàn)了兩個(gè)方法
- Main創(chuàng)建了CA和CB的實(shí)例,并傳入PrintInfo
- 由于類實(shí)例實(shí)現(xiàn)了接口,PrintInfo可以調(diào)用那兩個(gè)方法,每個(gè)類實(shí)例執(zhí)行各自的方法,就好像是執(zhí)行自己類聲明中的方法
interface IInfo //聲明接口
{
string GetName();
string GetAge();
}
class CA:IInfo //聲明了實(shí)現(xiàn)接口的CA類
{
public string Name; public int Age;
public string GetName(){return Name;}
public string GetAge(){return Age.ToString();}
}
class CB:IInfo //聲明了實(shí)現(xiàn)接口的CB類
{
public string First;
public string Last;
public double PersonsAge;
public string GetName(){return First+""+Last;}
public string GetAge(){return PersonsAge.ToString();}
}
class Program
{
static void PrintInfo(IInfo item)
{
Console.WriteLine("Name: {0},Age {1}",item.GetName(),item.GetAge());
}
static void Main()
{
var a=new CA(){Name="John Doe",Age=35};
var b=new CB(){First="Jane",Last="Doe",PersonsAge=33};
PrintInfo(a);
PrintInfo(b);
}
}

使用IComparable接口的示例
- 第一行代碼創(chuàng)建了包含5個(gè)無序整數(shù)的數(shù)組
- 第二行代碼使用了Array類的靜態(tài)Sort方法來排序元素
- 用foreach循環(huán)輸出它們,顯式以升序排序的數(shù)字
Array.Sort(myInt);
foreach(var i in myInt)
{
Console.WriteLine("{0}",i);
}
Sort方法在int數(shù)組上工作良好,但如果我們嘗試在自己的類上使用會(huì)發(fā)生什么呢?
class MyClass
{
public int TheValue;
}
...
MyClass[] mc=new MyClass[5];
...
Array.Sort(mc);
運(yùn)行上面的代碼,將會(huì)得到一個(gè)異常。Sort并不能對(duì)MyClass對(duì)象數(shù)組排序,因?yàn)樗恢廊绾伪容^自定義的對(duì)象。Array類的Sort方法其實(shí)依賴于一個(gè)叫做IComparable的接口,它聲明在BCL中,包含唯一的方法CompareTo。
public interface IComparable
{
int CompareTo(object obj);
}
盡管接口聲明中沒有為CompareTo方法提供實(shí)現(xiàn),但I(xiàn)Comparable接口的.NET文檔中描述了該方法應(yīng)該做的事情,可以在創(chuàng)建實(shí)現(xiàn)該接口的類或結(jié)構(gòu)時(shí)參考。
文檔中寫到,在調(diào)用CompareTo方法時(shí),它應(yīng)該返回以下幾個(gè)值:
- 負(fù)數(shù)值 當(dāng)前對(duì)象小于參數(shù)對(duì)象
- 整數(shù)值 當(dāng)前對(duì)象大于參數(shù)對(duì)象
- 零 兩個(gè)對(duì)象相等
我們可以通過讓類實(shí)現(xiàn)IComparable來使Sort方法可以用于MyClass對(duì)象。要實(shí)現(xiàn)一個(gè)接口,類或結(jié)構(gòu)必須做兩件事情:
- 必須在基類列表后面列出接口名稱
- 必須實(shí)現(xiàn)接口的每個(gè)成員
例:MyClass中實(shí)現(xiàn)了IComparable接口
class MyClass:IComparable
{
public int TheValue;
public int CompareTo(object obj)
{
var mc=(MyClass)obj;
if(this.TheValue<mc.TheValue)return -1;
if(this.TheValue>mc.TheValue)return 1; return 0;
}
}
例:完整示例代碼
class MyClass:IComparable
{
public int TheValue;
public int CompareTo(object obj)
{
var mc=(MyClass)obj;
if(this.TheValue<mc.TheValue)return -1;
if(this.TheValue>mc.TheValue)return 1; return 0;
}
}
class Program
{
static void PrintInfo(string s,MyClass[] mc)
{
Console.WriteLine(s);
foreach(var m in mc)
{
Console.WriteLine("{0}",m.TheValue);
}
Console.WriteLine("");
}
static void Main()
{
var myInt=new[] {20,4,16,9,2};
MyClass[] mcArr=new MyClass[5];
for(int i=0;i<5;i++)
{
mcArr[i]=new MyClass();
mcArr[i].TheValue=myInt[i];
}
PrintOut("Initial Order: ",mcArr);
Array.Sort(mcArr);
PrintOut("Sorted Order: ",mcArr);
}
}

聲明接口
上一節(jié)使用的是BCL中已有的接口?,F(xiàn)在我們來看看如何聲明接口。
關(guān)于聲明接口,需要知道的重要事項(xiàng)如下:
- 接口聲明不能包含以下成員
- 數(shù)據(jù)成員
- 靜態(tài)成員
- 接口聲明只能包含如下類型的非靜態(tài)成員函數(shù)的聲明
- 方法
- 屬性
- 事件
- 索引器
- 這些函數(shù)成員的聲明不能包含任何實(shí)現(xiàn)代碼,只能用分號(hào)
- 按照慣例,接口名稱以大寫字母I(Interface)開始
- 與類和結(jié)構(gòu)一樣,接口聲明也可以分布
例:兩個(gè)方法成員接口的聲明
關(guān)鍵字 接口名稱
↓ ↓
interface IMyInterface1
{
int DoStuff(int nVar1,long lVar2); //分號(hào)替代了主體
double DoOtherStuff(string s,long x);
}
接口的訪問性和接口成員的訪問性之間有一些重要區(qū)別
- 接口聲明可以有任何的訪問修飾符public、protected、internal或private
- 然而,接口的成員是隱式public的,不允許有任何訪問修飾符,包括public
接口允許訪問修飾符
↓
public interface IMyInterface2
{
private int Method1(int nVar1); //錯(cuò)誤
↑
接口成員不允許訪問修飾符
}
實(shí)現(xiàn)接口
只有類和結(jié)構(gòu)才能實(shí)現(xiàn)接口。
- 在基類列表中包括接口名稱
- 實(shí)現(xiàn)每個(gè)接口成員
例:MyClass實(shí)現(xiàn)IMyInterface1接口
class MyClass:IMyInterface1
{
int DoStuff(int nVar1,long lVar2)
{...} //實(shí)現(xiàn)代碼
double DoOtherStuff(string s,long x)
{...} //實(shí)現(xiàn)代碼
}
關(guān)于實(shí)現(xiàn)接口,需要了解以下重要事項(xiàng):
- 如果類實(shí)現(xiàn)了接口,它必須實(shí)現(xiàn)接口的所有成員
- 如果類從基類繼承并實(shí)現(xiàn)了接口,基類列表中的基類名稱必須放在所有接口之前。
基類必須放在最前面 接口名
↓ ↓
class Derived:MyBaseClass,IIfc1,IEnumerable,IComparable
簡(jiǎn)單接口示例
interface IIfc1
{
void PrintOut(string s);
}
class MyClass:IIfc1
{
public void PrintOut(string s)
{
Console.WriteLine("Calling through: {0}",s);
}
}
class Program
{
static void Main()
{
var mc=new MyClass();
mc.PrintOut("object");
}
}

接口是引用類型
接口不僅是類或結(jié)構(gòu)要實(shí)現(xiàn)的成員列表。它是一個(gè)引用類型。
我們不能直接通過類對(duì)象的成員訪問接口。然而,我們可以通過把類對(duì)象引用強(qiáng)制轉(zhuǎn)換為接口類型來獲取指向接口的引用。一旦有了接口引用,我們就可以使用點(diǎn)號(hào)來調(diào)用接口方法。
例:從類對(duì)象引用獲取接口引用
IIfc1 ifc=(IIfc1)mc; //轉(zhuǎn)換為接口,獲取接口引用
ifc.PrintOut("interface"); //使用接口的引用調(diào)用方法</pre>
例:類和接口的引用
interface IIfc1
{
void PrintOut(string s);
}
class MyClass:IIfc1
{
public void PrintOut(string s)
{
Console.WriteLine("Calling through: {0}",s);
}
}
class Program
{
static void Main()
{
var mc=new MyClass();
mc.PrintOut("object"); //調(diào)用類對(duì)象的實(shí)現(xiàn)方法
IIfc1 ifc=(IIfc1)mc;
ifc.PrintOut("interface"); //調(diào)用引用方法
}
}


接口和as運(yùn)算符
上一節(jié),我們已經(jīng)知道可以使用強(qiáng)制轉(zhuǎn)換運(yùn)算符來獲取對(duì)象接口引用,另一個(gè)更好的方式是使用as運(yùn)算符。
如果我們嘗試將類對(duì)象引用強(qiáng)制轉(zhuǎn)換為類未實(shí)現(xiàn)的接口的引用,強(qiáng)制轉(zhuǎn)換操作會(huì)拋出異常。我們可以通過as運(yùn)算符來避免該問題。
- 如果類實(shí)現(xiàn)了接口,表達(dá)式返回指向接口的引用
- 如果類沒有實(shí)現(xiàn)接口,表達(dá)式返回null
ILiveBirth b=a as ILiveBirth;
if(b!=null)
{
Console.WriteLine("Baby is called: {0}",b.BabyCalled());
}
實(shí)現(xiàn)多個(gè)接口
- 類或結(jié)構(gòu)可以實(shí)現(xiàn)多個(gè)接口
- 所有實(shí)現(xiàn)的接口必須列在基類列表中并以逗號(hào)分隔(如果有基類名稱,則在其后)
interface IDataRetrieve{int GetData();}
interface IDataStore{void SetData(int x);}
class MyData:IDataRetrieve,IDataStore
{
int Mem1;
public int GetData(){return Mem1;}
public void SetData(int x){Mem1=x;}
}
class Program
{
static void Main()
{
var data=new MyData();
data.SetData(5);
Console.WriteLine("Value = {0}",data.GetData());
}
}

實(shí)現(xiàn)具有重復(fù)成員的接口
由于接口可以多實(shí)現(xiàn),有可能多個(gè)接口有相同的簽名和返回類型。編譯器如何處理這種情況呢?
例:IIfc1和IIfc2具有相同簽名
interface IIfc1
{
void PrintOut(string s);
}
interface IIfc2
{
void PrintOut(string t);
}
答案是:如果一個(gè)類實(shí)現(xiàn)了多接口,并且其中有些接口有相同簽名和返回類型,那么類可以實(shí)現(xiàn)單個(gè)成員來滿足所有包含重復(fù)成員的接口。
例:MyClass 類實(shí)現(xiàn)了IIfc1和IIfc2.PrintOut滿足了兩個(gè)接口的需求。
class MyClass:IIfc1,IIfc2
{
public void PrintOut(string s)//兩個(gè)接口單一實(shí)現(xiàn)
{
Console.WriteLine("Calling through: {0}",s);
}
}
class Program
{
static void Main()
{
var mc=new MyClass();
mc.PrintOut("object");
}
}

多個(gè)接口的引用
如果類實(shí)現(xiàn)了多接口,我們可以獲取每個(gè)接口的獨(dú)立引用。
例:下面類實(shí)現(xiàn)了兩個(gè)具有當(dāng)PrintOut方法的接口,Main中以3種方式調(diào)用了PrintOut。
- 通過類對(duì)象
- 通過指向IIfc1接口的引用
- 通過指向IIfc2接口的引用

interface IIfc1
{
void PrintOut(string s);
}
interface IIfc2
{
void PrintOut(string t);
}
class MyClass:IIfc1,IIfc2
{
public void PrintOut(string s)
{
Console.WriteLine("Calling through: {0}",s);
}
}
class Program
{
static void Main()
{
var mc=new MyClass();
IIfc1 ifc1=(IIfc1)mc;
IIfc2 ifc2=(IIfc2)mc;
mc.PrintOut("object");
ifc1.PrintOut("interface 1");
ifc2.PrintOut("interface 2");
}
}

派生成員作為實(shí)現(xiàn)
實(shí)現(xiàn)接口的類可以從它的基類繼承實(shí)現(xiàn)的代碼。
例:演示 類從基類代碼繼承了實(shí)現(xiàn)
- IIfc1是一個(gè)具有PrintOut方法成員的接口
- MyBaseClass包含一個(gè)PrintOut方法,它和IIfc1匹配
- Derived類有空的聲明主體,但它派生自MyBaseClass,并在基類列表中包含了IIfc1
- 即使Derived的聲明主體為空,基類中的代碼還是能滿足實(shí)現(xiàn)接口方法的需求
interface IIfc1
{
void PrintOut(string s);
}
class MyBaseClass
{
public void PrintOut(string s)
{
Console.WriteLine("Calling through: {0}",s);
}
}
class Derived:MyBaseClass,IIfc1
{
}
class Program
{
static void Main()
{
var d=new Derived();
d.PrintOut("object");
}
}

顯式接口成員實(shí)現(xiàn)
上面,我們已經(jīng)看到了單個(gè)類可以實(shí)現(xiàn)多個(gè)接口需要的所有成員。
但是,如果我們希望為每個(gè)接口分離實(shí)現(xiàn)該怎么做呢?這種情況下,我們可以創(chuàng)建顯式接口成員實(shí)現(xiàn)。
- 與所有接口實(shí)現(xiàn)相似,位于實(shí)現(xiàn)了接口的類或結(jié)構(gòu)中
- 它使用限定接口名稱來聲明,由接口名稱和成員名稱以及它們中間的點(diǎn)分隔符號(hào)構(gòu)成
class MyClass:IIfc1,IIfc2
{
void IIfc1.PrintOut(string s)
{...}
void IIfc2.PrintOut(string s)
{...}
}

例:MyClass為兩個(gè)解耦的成員聲明了顯式接口成員實(shí)現(xiàn)。
interface IIfc1
{
void PrintOut(string s);
}
interface IIfc2
{
void PrintOut(string t);
}
class MyClass:IIfc1,IIfc2
{
void IIfc1.PrintOut(string s)
{
Console.WriteLine("IIfc1: {0}",s);
}
void IIfc2.PrintOut(string s)
{
Console.WriteLine("IIfc2: {0}",s);
}
}
class Program
{
static void Main()
{
var mc=new MyClass();
IIfc1 ifc1=(IIfc1)mc;
ifc1.PrintOut("interface 1");
IIfc2 ifc2=(IIfc2)mc;
ifc2.PrintOut("interface 2");
}
}


如果有顯式接口成員實(shí)現(xiàn),類級(jí)別的實(shí)現(xiàn)是允許的,但不是必需的。顯式實(shí)現(xiàn)滿足了類或結(jié)構(gòu)必須實(shí)現(xiàn)的方法。因此,我們可以有如下3種實(shí)現(xiàn)場(chǎng)景。
- 類級(jí)別實(shí)現(xiàn)
- 顯式接口成員實(shí)現(xiàn)
- 類級(jí)別和顯式接口成員實(shí)現(xiàn)
訪問顯式接口成員實(shí)現(xiàn)
顯式接口成員實(shí)現(xiàn)只可以通過指向接口的引用來訪問。即其他的類成員都不可以直接訪問它們。
例:如下MyClass顯式實(shí)現(xiàn)了IIfc1接口。注意,即使是MyClass的另一成員Method1,也不可以直接訪問顯式實(shí)現(xiàn)。
- Method1的前兩行編譯錯(cuò)誤,因?yàn)榉椒ㄔ趪L試直接訪問實(shí)現(xiàn)
- 只有Method1的最后一行代碼才可以編譯,因此它強(qiáng)制轉(zhuǎn)換當(dāng)前對(duì)象的引用(this)為接口類型的引用,并使用這個(gè)指向接口的引用來調(diào)用顯式接口實(shí)現(xiàn)
class MyClass:IIfc1
{
void IIfc1.PrintOut(string s)
{
Console.WriteLine("IIfc1");
}
public void Method1()
{
PrintOut("..."); //編譯錯(cuò)誤
this.PrintOut("..."); //編譯錯(cuò)誤
((IIfc1)this).PrintOut("...");
↑
轉(zhuǎn)換為接口引用
}
}
這個(gè)限制對(duì)繼承產(chǎn)生了重要影響。由于其他類成員不能直接訪問顯式接口成員實(shí)現(xiàn),衍生類的成員也不能直接訪問它們。它們必須總是通過接口的引用來訪問。
接口可以繼承接口
之前我們已經(jīng)知道接口實(shí)現(xiàn)可以從基類繼承,而接口本身也可以從一個(gè)或多個(gè)接口繼承。
- 要指定某個(gè)接口繼承其他的接口,應(yīng)在接口聲明中把某接口以逗號(hào)分隔的列表形式放在接口名稱的冒號(hào)之后
- 與類不同,它在基類列表中只能有一個(gè)類名,接口可以在基接口列表中有任意多個(gè)接口
- 列表中的接口本身可以繼承其他接口
- 結(jié)果接口包含它聲明的所有接口和所有基接口的成員
interface IDataIO:IDataRetrieve,IDataStore
{
...
}
例:IDataIO接口繼承了兩個(gè)接口
interface IDataRetrieve
{
int GetData();
}
interface IDataStore
{
void SetData(int x);
}
interface IDaTaIO:IDataRetrieve,IDataStore
{
}
class MyData:IDataIO
{
int nPrivateData;
public int GetData()
{
return nPrivateData;
}
public void SetData(int x)
{
nPrivateData=x;
}
}
class Program
{
static void Main()
{
var data=new MyData();
data.SetData(5);
Console.WriteLine("{0}",data.GetData());
}
}

不同類實(shí)現(xiàn)一個(gè)接口的示例
interface ILiveBirth //聲明接口
{
string BabyCalled();
}
class Animal{} //基類Animal
class Cat:Animal,ILiveBirth //聲明Cat類
{
string ILiveBirth.BabyCalled()
{
return "kitten";
}
}
class Dog:Animal,ILiveBirth //聲明DOg類
{
string ILiveBirth.BabyCalled()
{
return "puppy";
}
}
class Bird:Animal //聲明Bird類
{
}
class Program
{
static void Main()
{
Animal[] animalArray=new Animal[3];
animalArray[0]=new Cat();
animalArray[1]=new Bird();
animalArray[2]=new Dog();
foreach(Animal a in animalArray)
{
ILiveBirth b= a as ILiveBirth;//如果實(shí)現(xiàn)ILiveBirth
if(b!=null)
{
Console.WriteLine("Baby is called: {0}",b.BabyCalled());
}
}
}
}


作者:Moonache
出處:http://www.cnblogs.com/moonache/
原文鏈接:https://www.cnblogs.com/moonache/p/6346424.html