1. 程式人生 > >C#圖解教程 第十五章 介面

C#圖解教程 第十五章 介面

介面

什麼是介面


介面是指定一組函式成員而不實現它們的引用型別。所以只能類和結構來實現介面。 
這種描述比較抽象,直接來看個示例。 
下例中,Main方法建立並初始化了一個CA類的物件,並將該物件傳遞給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型別的物件,PrintInfo就能正常工作。但如果傳入的是CB,就不行了。 
現在的程式碼不能滿足上面的需求,原因有很多。

  • PrintInfo的形參指明瞭實參必須為CA型別的物件
  • CB的結構與CA不同,欄位的名稱和型別與CA不一樣

介面解決了這一問題。

  • 宣告一個IInfo介面,包含兩個方法–GetName和GetAge,每個方法都返回string
  • 類CA和CB各自實現了IInfo介面,並實現了兩個方法
  • Main建立了CA和CB的例項,並傳入PrintInfo
  • 由於類例項實現了介面,PrintInfo可以呼叫那兩個方法,每個類例項執行各自的方法,就好像是執行自己類宣告中的方法
interface IInfo       //宣告介面
{
    string GetName();
    string GetAge();
}
class CA:IInfo         //聲明瞭實現介面的CA類
{
    public string Name;
    public int Age;
    public string GetName(){return Name;}
    public string GetAge(){return Age.ToString();}
}
class CB:IInfo         //聲明瞭實現介面的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介面的示例
  • 第一行程式碼建立了包含5個無序整數的陣列
  • 第二行程式碼使用了Array類的靜態Sort方法來排序元素
  • 用foreach迴圈輸出它們,顯式以升序排序的數字
var myInt=new[]{20,4,16,9,2};
Array.Sort(myInt);
foreach(var i in myInt)
{
    Console.WriteLine("{0}",i);
}

Sort方法在int陣列上工作良好,但如果我們嘗試在自己的類上使用會發生什麼呢?

class MyClass
{
    public int TheValue;
}
...
MyClass[] mc=new MyClass[5];
...
Array.Sort(mc);

執行上面的程式碼,將會得到一個異常。Sort並不能對MyClass物件陣列排序,因為它不知道如何比較自定義的物件。Array類的Sort方法其實依賴於一個叫做IComparable的介面,它宣告在BCL中,包含唯一的方法CompareTo。

public interface IComparable
{
    int CompareTo(object obj);
}

儘管介面宣告中沒有為CompareTo方法提供實現,但IComparable介面的.NET文件中描述了該方法應該做的事情,可以在建立實現該介面的類或結構時參考。 
文件中寫到,在呼叫CompareTo方法時,它應該返回以下幾個值:

  • 負數值 當前物件小於引數物件
  • 整數值 當前物件大於引數物件
  • 零 兩個物件相等

我們可以通過讓類實現IComparable來使Sort方法可以用於MyClass物件。要實現一個介面,類或結構必須做兩件事情:

  • 必須在基類列表後面列出介面名稱
  • 必須實現介面的每個成員

例:MyClass中實現了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);
    }
}

宣告介面


上一節使用的是BCL中已有的介面。現在我們來看看如何宣告介面。 
關於宣告介面,需要知道的重要事項如下:

  • 介面宣告不能包含以下成員
    • 資料成員
    • 靜態成員
  • 介面宣告只能包含如下型別的非靜態成員函式的宣告
    • 方法
    • 屬性
    • 事件
    • 索引器
  • 這些函式成員的宣告不能包含任何實現程式碼,只能用分號
  • 按照慣例,介面名稱以大寫字母I(Interface)開始
  • 與類和結構一樣,介面宣告也可以分佈

例:兩個方法成員介面的宣告

  關鍵字     介面名稱
    ↓          ↓
interface IMyInterface1
{
    int DoStuff(int nVar1,long lVar2);  //分號替代了主體
    double DoOtherStuff(string s,long x);
}

介面的訪問性和介面成員的訪問性之間有一些重要區別

  • 介面宣告可以有任何的訪問修飾符public、protected、internal或private
  • 然而,介面的成員是隱式public的,不允許有任何訪問修飾符,包括public
介面允許訪問修飾符
   ↓
public interface IMyInterface2
{
    private int Method1(int nVar1); //錯誤
       ↑
介面成員不允許訪問修飾符
}

實現介面


只有類和結構才能實現介面。

  • 在基類列表中包括介面名稱
  • 實現每個介面成員

例:MyClass實現IMyInterface1介面

class MyClass:IMyInterface1
{
    int DoStuff(int nVar1,long lVar2)
    {...}    //實現程式碼
    double DoOtherStuff(string s,long x)
    {...}    //實現程式碼
}

關於實現介面,需要了解以下重要事項:

  • 如果類實現了介面,它必須實現介面的所有成員
  • 如果類從基類繼承並實現了介面,基類列表中的基類名稱必須放在所有介面之前。
           基類必須放在最前面       介面名
                  ↓                 ↓
class Derived:MyBaseClass,IIfc1,IEnumerable,IComparable
簡單介面示例
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");
    }
}

介面是引用型別


介面不僅是類或結構要實現的成員列表。它是一個引用型別。 
我們不能直接通過類物件的成員訪問介面。然而,我們可以通過把類物件引用強制轉換為介面型別來獲取指向介面的引用。一旦有了介面引用,我們就可以使用點號來呼叫介面方法。

例:從類物件引用獲取介面引用

IIfc1 ifc=(IIfc1)mc;         //轉換為介面,獲取介面引用
ifc.PrintOut("interface");   //使用介面的引用呼叫方法

例:類和介面的引用

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");    //呼叫類物件的實現方法
        IIfc1 ifc=(IIfc1)mc;
        ifc.PrintOut("interface"); //呼叫引用方法
    }
}

介面和as運算子


上一節,我們已經知道可以使用強制轉換運算子來獲取物件介面引用,另一個更好的方式是使用as運算子。 
如果我們嘗試將類物件引用強制轉換為類未實現的介面的引用,強制轉換操作會丟擲異常。我們可以通過as運算子來避免該問題。

  • 如果類實現了介面,表示式返回指向介面的引用
  • 如果類沒有實現介面,表示式返回null
ILiveBirth b=a as ILiveBirth;
if(b!=null)
{
    Console.WriteLine("Baby is called: {0}",b.BabyCalled());
}

實現多個介面


  • 類或結構可以實現多個介面
  • 所有實現的介面必須列在基類列表中並以逗號分隔(如果有基類名稱,則在其後)
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());
    }
}

實現具有重複成員的介面


由於介面可以多實現,有可能多個介面有相同的簽名和返回型別。編譯器如何處理這種情況呢? 
例:IIfc1和IIfc2具有相同簽名

interface IIfc1
{
    void PrintOut(string s);
}
interface IIfc2
{
    void PrintOut(string t);
}

答案是:如果一個類實現了多介面,並且其中有些介面有相同簽名和返回型別,那麼類可以實現單個成員來滿足所有包含重複成員的介面。 
例:MyClass 類實現了IIfc1和IIfc2.PrintOut滿足了兩個介面的需求。

class MyClass:IIfc1,IIfc2
{
    public void PrintOut(string s)//兩個介面單一實現
    {
        Console.WriteLine("Calling through: {0}",s);
    }
}
class Program
{
    static void Main()
    {
        var mc=new MyClass();
        mc.PrintOut("object");
    }
}

多個介面的引用


如果類實現了多介面,我們可以獲取每個介面的獨立引用。

例:下面類實現了兩個具有當PrintOut方法的介面,Main中以3種方式呼叫了PrintOut。

  • 通過類物件
  • 通過指向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");
    }
}

派生成員作為實現


實現介面的類可以從它的基類繼承實現的程式碼。 
例:演示 類從基類程式碼繼承了實現

  • IIfc1是一個具有PrintOut方法成員的介面
  • MyBaseClass包含一個PrintOut方法,它和IIfc1匹配
  • Derived類有空的宣告主體,但它派生自MyBaseClass,並在基類列表中包含了IIfc1
  • 即使Derived的宣告主體為空,基類中的程式碼還是能滿足實現介面方法的需求
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");
    }
}

顯式介面成員實現


上面,我們已經看到了單個類可以實現多個介面需要的所有成員。 
但是,如果我們希望為每個介面分離實現該怎麼做呢?這種情況下,我們可以建立顯式介面成員實現

  • 與所有介面實現相似,位於實現了介面的類或結構中
  • 它使用限定介面名稱來宣告,由介面名稱和成員名稱以及它們中間的點分隔符號構成
class MyClass:IIfc1,IIfc2
{
    void IIfc1.PrintOut(string s)
    {...}
    void IIfc2.PrintOut(string s)
    {...}
}

例:MyClass為兩個解耦的成員聲明瞭顯式介面成員實現。

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

 
 
如果有顯式介面成員實現,類級別的實現是允許的,但不是必需的。顯式實現滿足了類或結構必須實現的方法。因此,我們可以有如下3種實現場景。

  • 類級別實現
  • 顯式介面成員實現
  • 類級別和顯式介面成員實現
訪問顯式介面成員實現

顯式介面成員實現只可以通過指向介面的引用來訪問。即其他的類成員都不可以直接訪問它們。 
例:如下MyClass顯式實現了IIfc1介面。注意,即使是MyClass的另一成員Method1,也不可以直接訪問顯式實現。

  • Method1的前兩行編譯錯誤,因為方法在嘗試直接訪問實現
  • 只有Method1的最後一行程式碼才可以編譯,因此它強制轉換當前物件的引用(this)為介面型別的引用,並使用這個指向介面的引用來呼叫顯式介面實現
class MyClass:IIfc1
{
    void IIfc1.PrintOut(string s)
    {
        Console.WriteLine("IIfc1");
    }
    public void Method1()
    {
        PrintOut("...");         //編譯錯誤
        this.PrintOut("...");    //編譯錯誤
        ((IIfc1)this).PrintOut("...");
            ↑
       轉換為介面引用
    }
}

這個限制對繼承產生了重要影響。由於其他類成員不能直接訪問顯式介面成員實現,衍生類的成員也不能直接訪問它們。它們必須總是通過介面的引用來訪問。

介面可以繼承介面


之前我們已經知道介面實現可以從基類繼承,而介面本身也可以從一個或多個介面繼承。

  • 要指定某個介面繼承其他的介面,應在介面宣告中把某介面以逗號分隔的列表形式放在介面名稱的冒號之後
  • 與類不同,它在基類列表中只能有一個類名,介面可以在基介面列表中有任意多個介面
    • 列表中的介面本身可以繼承其他介面
    • 結果介面包含它宣告的所有介面和所有基介面的成員
interface IDataIO:IDataRetrieve,IDataStore
{
   ...
}

例:IDataIO介面繼承了兩個介面

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

不同類實現一個介面的示例


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;//如果實現ILiveBirth
            if(b!=null)
            {
            Console.WriteLine("Baby is called: {0}",b.BabyCalled());
            }
        }
    }
}

from: http://www.cnblogs.com/moonache/p/6346424.html