C#圖解教程 第十五章 接口

接口

什么是接口


接口是指定一組函數(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);
    }
}
image
使用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);
    }
}
image

聲明接口


上一節(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");
    }
}
image

接口是引用類型


接口不僅是類或結(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)用引用方法
    }
}
image
image

接口和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());
    }
}
image

實(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");
    }
}
image

多個(gè)接口的引用


如果類實(shí)現(xiàn)了多接口,我們可以獲取每個(gè)接口的獨(dú)立引用。

例:下面類實(shí)現(xiàn)了兩個(gè)具有當(dāng)PrintOut方法的接口,Main中以3種方式調(diào)用了PrintOut。

  • 通過類對(duì)象
  • 通過指向IIfc1接口的引用
  • 通過指向IIfc2接口的引用
image
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");
    }
}
image

派生成員作為實(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");
    }
}
image

顯式接口成員實(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)
    {...}
}
image

例: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");
    }
}
image
image

如果有顯式接口成員實(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());
    }
}
image

不同類實(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());
            }
        }
    }
}
image

image

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.C和C++的區(qū)別?C++的特性?面向?qū)ο缶幊痰暮锰帲?答:c++在c的基礎(chǔ)上增添類,C是一個(gè)結(jié)構(gòu)化語言,它的重...
    杰倫哎呦哎呦閱讀 9,984評(píng)論 0 45
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 設(shè)計(jì)模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計(jì)原則時(shí)需要注意以下幾點(diǎn):a) 高內(nèi)聚、低耦合和單一職能的“沖突”實(shí)際上,這兩者...
    彥幀閱讀 3,873評(píng)論 0 14
  • 作者:毓?jié)? 今天是參加夏令營(yíng)的第二天,我們要去一個(gè)非常著名的景點(diǎn)——張掖丹霞國(guó)家地...
    齊天小圣6閱讀 266評(píng)論 0 0
  • 執(zhí)行Maven Install打包的時(shí)候,提示以下警告信息: 解決方法: 打開項(xiàng)目屬性》Resources,按下圖...
    Scorpio_cc閱讀 21,180評(píng)論 1 4

友情鏈接更多精彩內(nèi)容