国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

[CLR via C#]13. 接口

2019-11-17 03:20:44
字體:
來源:轉載
供稿:網友

[CLR via C#]13. 接口

一、類和接口繼承

  在Microsoft.Net Framwork中,有一個名為System.Object的類,它定義了4個公共實例方法:ToString, Equals, GetHashCode和GetType。該類是其他所有類的根或者說最終基類。換言之,所有類都繼承了Object的4個實例方法。這還意味著能操作Object類的實例的代碼實際能操作任何類的實例。

  在CLR中,任何類都肯定從一個類也只能從一個類(而且只能從Objetc派生的類)派生的。這個類稱為基類。基類提供了一組方法簽名和這些方法的實現。你定義的新類可在將來由其它開發人員用作基類——所有方法簽名和方法實現都會由新的派生類繼承。

  CLR允許開發人員定義接口,它實際只是對一組方法簽名進行了統一的命名。這些方法沒有提供任何實現。類通過指定接口與名稱來實現這個接口,它必須顯式提供接口方法的實現,否則CLR會認為此類型定義無效。  類繼承有一個重要特點,凡是能使用基類型實例的地方,都能使用派生類型的實例。類似的,凡是能使用具有接口類型實例的地方,都能使用實現了這個接口的一個類型的實例。

二、定義接口

  接口對一組方法簽名進行了統一命名。接口還能定義事件,無參屬性和有參屬性(C#中的索引器),因為這些東西本質都是方法。接口不能定義任何構造器方法,和任何實例字段。

  雖然CLR允許接口定義靜態方法、靜態字段、常量和靜態構造器。但C#禁止接口定義任何一種這樣的靜態成員。  在C#中,使用interface關鍵字定義一個接口。要為接口指定一個名稱和一組實例方法簽名。比如FCL中的幾個接口定義:
public interface IDisposable {    void Dispose();} public interface IEnumerable {    IEnumerator GetEnumerator();}

  對CLR而言,定義接口就像定義類型,也就是說,CLR會為接口類型對象定義一個內部數據結構,同時可用反射機制來查詢接口類型的功能。定義接口類型時,可指定你希望的任何訪問性修飾符。

  根據約定,接口類型名稱要以大寫字母I開頭,目的是方便在源代碼中辨認接口類型。CLR支持泛型接口和接口中的泛型方法。

三、繼承接口

本節將講介紹如何定義一個實現了接口的類型,然后再介紹如何創建這個類型的一個實例,并用這個對象調用接口的方法。最后介紹C#接口實現時,幕后發生的事情。

下面是在MSCorLib.dll中定義的System.IComparable<T>接口:
public interface IComparable<in T> {    Int32 CompareTo(T other);}

  以下代碼展示類如何定義一個實現類該接口的類型,還展示了對象兩個Point對象進行比較的代碼:

   // Point 從 System.Object 派生并且實現了 IComparable<T>.    public sealed class Point : IComparable<Point>    {        PRivate Int32 m_x, m_y;         public Point(Int32 x, Int32 y)        {            m_x = x;            m_y = y;        }         // 該方法實現了 IComparable<T>.CompareTo()        public Int32 CompareTo(Point other)        {            return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)               - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));        }         public override String ToString()        {            return String.Format("({0}, {1})", m_x, m_y);        }    }     public static void Go()    {        Point[] points = new Point[] {         new Point(3, 3),         new Point(1, 2),      };        //調用了由Point實現的IComparable<T>.CompareTo()方法        if (points[0].CompareTo(points[1]) > 0)        {            Point tempPoint = points[0];            points[0] = points[1];            points[1] = tempPoint;        }        Console.WriteLine("Points from closest to (0, 0) to farthest:");        foreach (Point p in points)            Console.WriteLine(p);    }
  C#編譯器要求將用于實現一個接口的方法標記為public。CLR要求將接口方法標記為virtual。如果在源代碼中沒有顯式地將方法標記為virtual,編譯器會將它們標記為virtual和sealed;這會阻止派生類重寫接口方法。如果顯式地將方法標記為virtual,編譯器就會將該方法標記為virtual(并保持它的非密封狀態)。這樣一來,派生類就能重寫它了。  如果一個接口方法是sealed的,派生類就不能重寫它。不過,派生類可以重寫繼承同一個接口,并可為該接口的方法提供它中的實現。在一個對象上調用一個接口的方法時,將調用該方法在該對象的類型中的實現。
internal static class InterfaceReimplementation    {        public static void Go()        {            /************************* 第一個例子 *************************/            Base b = new Base();             // 結果顯示: "Base's Dispose"            b.Dispose();             // 結果顯示: "Base's Dispose"            ((IDisposable)b).Dispose();              /************************* 第二個例子 ************************/            Derived d = new Derived();             // 結果顯示: "Derived's Dispose"            d.Dispose();             // 結果顯示: "Derived's Dispose"            ((IDisposable)d).Dispose();              /************************* 第三個例子 *************************/            b = new Derived();             // 結果顯示: "Base's Dispose"            b.Dispose();             // 結果顯示: "Derived's Dispose"            ((IDisposable)b).Dispose();        }         // 這個類型派生自 Object 并且實現了 IDisposable        internal class Base : IDisposable        {            // 這個方法是隱式密封的,不能被重寫            public void Dispose()            {                Console.WriteLine("Base's Dispose");            }        }         // 這個類繼承了Base并且實現了IDisposable接口        internal class Derived : Base, IDisposable        {            // 這個方法不能重寫 Base's Dispose.            // 'new' 關鍵字表明重新實現了IDisposable的Dispose            new public void Dispose()            {                Console.WriteLine("Derived's Dispose");                 // 注意: 下一行展示了如何讓調用基類的方法                // base.Dispose();            }        }    }

四、關于調用接口方法的更多探討  FCL的System.String類型繼承了System.Object的方法簽名及其實現。此外,String類型還實現了幾個接口:IComparable,ICloneable,IEnumerable,IComparable<String>,IEnumerable<Char>等。這意味著String類型不需要實現(或重寫)其Object基類型提供的方法,但必須實現所有接口中聲明的方法。  CLR允許定義接口類型的字段,參數或局部變量。使用接口類型的一個變量,可以調用由那個接口定義的方法。例如:
  // s變量引用了String對象,使用s時,可以調用String,  // Object,IComparable等定義的方法  private String s = "abc";   // comparable變量引用指向同一個String對象,使用comparable  // 只能調用IComparable接口中的定義任何方法(包括Objetc中的方法)  private IComparable comparable = s;   // convertible變量引用指向同一個String對象,使用convertible  // 只能調用IConvertible接口中的定義任何方法(包括Objetc中的方法)  private IConvertible convertible = s;   // enumerable 變量引用同一個String對象  // 在運行時,可將變量從一種接口類型轉型到另一種類型,  // 只要該對象的類型實現了這兩個個接口  // 使用enumerable,只能調用IEnumerable聲明的任何方法(包括Objetc中的方法)  private IEnumerable enumerable = (IEnumerable) convertible; 

  和引用類型相似,值類型也可以實現零個或多個接口。不過,將值類型的實例轉型為接口類型時,值類型的實例必須裝箱。這是由于接口變量是一個引用,它必須指向堆上的一個對象,使CLR能檢查對象的類型對象指針,從而判斷對象的真實類型。然后,再調用已裝箱值類型的一個接口方法時,CLR會跟隨對象的類型對象指針,找到類型對象的方法表,從而調用正確的方法。五、 隱式和顯式接口方法實現  一個類型加載到CLR中時,會為該類型創建并初始化一個方法表。在這個方法表中,類型引入的每個新方法都有一條對象的記錄項。另外,還要為該類型繼承的所有虛方法添加記錄項。繼承的虛方法既有由繼承層次結構中的各個基類型定義的,也有由接口類型定義的。所以,對于下面這樣的一個簡單類型定義:
interter sealed class SimpleType : IDisposable {    public void Dispose() { console.WriteLine("Dispose"); }}

  類型的方法表將包含以下方法對應的記錄項:

  1)Object(隱式繼承的基類)定義的所有虛實例方法。  2)IDisposable(實現的接口)定義的所有接口方法。本例只有一個方法,即Dispose,因為IDisposable只定義了該方法。  3)SimpleType 引入的新方法Dispose。  為簡化編程,C#編譯器假定SimpleType引入的Dispose方法是對IDisposable的Dispose方法的實現。C#編譯器之所以做出這樣的假定,是因為Dispose方法的可訪問性是public,而且接口方法的簽名和新引入的方法完全一致,也就是說,這兩個方法具有相同的參數和返回類型。還有,如果新的Dispose方法被標記為virtual,C#編譯器仍會認為該方法匹配于接口方法。  C#編譯器將一個新方法和一個接口方法匹配起來之后,便會生成元數據,指明SimpleType類型的方法表中的兩個記錄項引用同一個實現。下面的代碼演示了如果調用類的公共Dispose方法以及如何調用IDisposable的Dispose方法在類中的實現:
internal static class ExplicitInterfaceMethodImpl{    public static void Go()    {        SimpleType st = new SimpleType();         // 調用公共的 Dispose 方法實現        st.Dispose();         // 調用 IDisposable 的 Dispose 方法實現        IDisposable d = st;        d.Dispose();    }     public sealed class SimpleType : IDisposable    {        public void Dispose() { Console.WriteLine("Dispose"); }    }}

  執行后,兩者是沒有任何卻別的。輸出結果都是Dispose

  現在我們更改一下SimpleType,以便看出區別:
public sealed class SimpleType : IDisposable{     public void Dispose() { Console.WriteLine("public Dispose"); }     void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); }}
  現在再次程序運行,會得到如下結果;
public DisposeIDisposable Dispose

  在C#中,將定義的那個接口的名稱作為方法名的前綴(例如IDisposable.Dispose),創建的就是顯式接口方法實現(EIMI)。注意,在C#中定義一個顯式接口方法時,不允許指定可訪問性。但是,編譯器生成的元數據時,其訪問性會被自動設為private,防止其他代碼在使用類的實例時直接調用接口方法。要調用接口方法,只能通過接口類型的一個變量來進行。

  一個EIMI方法不能標記為virtual,所以它不能被重寫這是因為EIMI方法并非真的是類型的對象模型的一部分,它是將一個接口(一組行為或方法)連接到一個類型上,同時避免公開方法的一種方式。六、 泛型接口  本節將介紹使用泛型接口的好處。  1.泛型接口提供了出色的編譯時類型安全性。有的接口(比如非泛型的IComparable接口),定義的方法使用了Object參數或Object返回類型,但這通常不是我們想要的。
private static void SomeMethod1()    {        Int32 x = 1, y = 2;        IComparable c = x;         // CompareTo 期望接口一個 Object 類型; 傳遞 y (一個 Int32 類型) 允許        c.CompareTo(y);     // Boxing occurs here         // CompareTo期望接口一個 Object 類型; 傳遞 "2" (一個 String 類型) 允許        // 但運行是拋出 ArgumentException 異常        c.CompareTo("2");    }

  在理想情況下,接口方法應該使用強類型。這也正是FCL為什么還有包含一個泛型IComparable<in T>接口的原因。

private static void SomeMethod2()    {        Int32 x = 1, y = 2;        IComparable<Int32> c = x;         // CompareTo 期望接口一個 Int32 類型; 傳遞 y (一個 Int32 類型) 允許        c.CompareTo(y);     // Boxing occurs here         // CompareTo 期望接口一個 Int32 類型; 傳遞 "2" (一個 String 類型) 編譯不通過        // 指出 String 不能被隱式轉型為 Int32        // c.CompareTo("2");    }

  2.處理值類型時,裝箱次數會少得多。  3.類可以實現
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 崇明县| 南京市| 通化市| 白银市| 滁州市| 枣庄市| 虎林市| 汉源县| 上犹县| 崇阳县| 藁城市| 阿克苏市| 濮阳县| 富宁县| 徐水县| 阜新市| 灯塔市| 永胜县| 叙永县| 乐业县| 灌南县| 泰州市| 上蔡县| 娄底市| 洪洞县| 陆川县| 新竹县| 分宜县| 宝丰县| 武城县| 栖霞市| 安平县| 德安县| 宜兰市| 兴义市| 肥城市| 龙里县| 长宁县| 无极县| 天水市| 新乡市|