1. 程式人生 > >c# 接口

c# 接口

實例化 等等 實現接口 sealed 覆蓋 ont 關系 winform 特殊

由於clr不支持多繼承,所以通過接口提供了“縮水版”的多繼承

並且繼承與派生的格式與C++也有所區別

1、在c++中,如果基類的某個函數是virtual的,則繼承類中與其相同聲明和名字的函數默認就是基類對應的虛函數

2、在c#中,必須在派生類的方法的前面加上override前綴,才認為是虛方法,否則就認為它是繼承類單獨聲明的一個方法,與基類沒有什麽關系,同時該方法還會將基類的同名虛方法覆蓋掉,因此這個時候一般用一個new的前綴來表示這個方法是繼承類單獨實現的方法

c#和clr允許一個類繼承多個接口,當然,繼承的所有接口方法都必須實現

接口允許定義事件,無參和有參屬性,但是不能定義任何構造方法和實例字段

如果不將接口的方法標記為virtual,則編譯器將會為它標記virtual和sealed,這將會阻止派生類重寫該接口方法。如果標記為virtual,則可以在繼承類中重寫該接口方法

在這裏的一個例子就是,實現IDisposable接口的Dispose方法

因為資源分為托管資源和非托管資源

托管資源包括在堆上分配的引用類對象等等

非托管資源包括文件,窗口句柄,流,內核對象,套接字對象等等

垃圾回收區只能自動回收托管資源,不會去回收非托管資源,此時如果不手動回收這些非托管資源,則這些資源只能在進程結束時才會被一起釋放,而回收非托管資源的機制就是通過IDisposable來實現的

不過,這一切並不這麽簡單,一個標準的繼承了IDisposable接口的類型應該像下面這樣去實現。這種實現我們稱之為Dispose模式:

技術分享 技術分享 public class SampleClass : IDisposable
{
//演示創建一個非托管資源
private IntPtr nativeResource = Marshal.AllocHGlobal(100);
//演示創建一個托管資源
private AnotherResource managedResource = new AnotherResource();
private bool disposed = false;

/// <summary>
/// 實現IDisposable中的Dispose方法
/// </summary>
public void Dispose()
{
//必須為true
Dispose(true);
//通知垃圾回收機制不再調用終結器(析構器)
GC.SuppressFinalize(this);
}

/// <summary>
/// 不是必要的,提供一個Close方法僅僅是為了更符合其他語言(如C++)的規範
/// </summary>
public void Close()
{
Dispose();
}

/// <summary>
/// 必須,以備程序員忘記了顯式調用Dispose方法
/// </summary>
~SampleClass()
{
//必須為false
Dispose(false);
}

/// <summary>
/// 非密封類修飾用protected virtual
/// 密封類修飾用private
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// 清理托管資源
if (managedResource != null)
{
managedResource.Dispose();
managedResource = null;
}
}
// 清理非托管資源
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource = IntPtr.Zero;
}
//讓類型知道自己已經被釋放
disposed = true;
}

public void SamplePublicMethod()
{
if (disposed)
{
throw new ObjectDisposedException("SampleClass", "SampleClass is disposed");
}
//省略
}
}
技術分享 技術分享

在Dispose模式中,幾乎每一行都有特殊的含義。

在標準的Dispose模式中,我們註意到一個以~開頭的方法:

技術分享 技術分享 /// <summary>
/// 必須,以備程序員忘記了顯式調用Dispose方法
/// </summary>
~SampleClass()
{
//必須為false
Dispose(false);
}
技術分享 技術分享

這個方法叫做類型的終結器。提供終結器的全部意義在於:我們不能奢望類型的調用者肯定會主動調用Dispose方法,基於終結器會被垃圾回收器調用這個特點,終結器被用做資源釋放的補救措施。

一個類型的Dispose方法應該允許被多次調用而不拋異常。鑒於這個原因,類型內部維護了一個私有的布爾型變量disposed:

private bool disposed = false;

在實際處理代碼清理的方法中,加入了如下的判斷語句:

if (disposed)
{
return;
}
//省略清理部分的代碼,並在方法的最後為disposed賦值為true
disposed = true;

這意味著類型如果被清理過一次,則清理工作將不再進行。

應該註意到:在標準的Dispose模式中,真正實現IDisposable接口的Dispose方法,並沒有實際的清理工作,它實際調用的是下面這個帶布爾參數的受保護的虛方法:

技術分享 技術分享 /// <summary>
/// 非密封類修飾用protected virtual
/// 密封類修飾用private
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
//省略代碼
}
技術分享 技術分享

之所以提供這樣一個受保護的虛方法,是為了考慮到這個類型會被其他類繼承的情況。如果類型存在一個子類,子類也許會實現自己的Dispose模式。受保護 的虛方法用來提醒子類必須在實現自己的清理方法的時候註意到父類的清理工作,即子類需要在自己的釋放方法中調用base.Dispose方法。

還有,我們應該已經註意到了真正撰寫資源釋放代碼的那個虛方法是帶有一個布爾參數的。之所以提供這個參數,是因為我們在資源釋放時要區別對待托管資源和非托管資源。

在供調用者調用的顯式釋放資源的無參Dispose方法中,調用參數是true:

public void Dispose()
{
//必須為true
Dispose(true);
//其他省略
}

這表明,這個時候代碼要同時處理托管資源和非托管資源。

在供垃圾回收器調用的隱式清理資源的終結器中,調用參數是false:

~SampleClass()
{
//必須為false
Dispose(false);
}

這表明,隱式清理時,只要處理非托管資源就可以了。

那麽,為什麽要區別對待托管資源和非托管資源。在認真闡述這個問題之前,我們需要首先弄明白:托管資源需要手動清理嗎? 不妨先將C#中的類型分為兩類,一類繼承了IDisposable接口,一類則沒有繼承。前者,我們暫時稱之為非普通類型,後者我們稱之為普通類型。非普 通類型因為包含非托管資源,所以它需要繼承IDisposable接口,但是,這個包含非托管資源的類型本身,它是一個托管資源。所以說,托管資源需要手 動清理嗎?這個問題的答案是:托管資源中的普通類型,不需要手動清理,而非普通類型,是需要手動清理的(即調用Dispose方法)。

Dispose模式設計的思路基於:如果調用者顯式調用了Dispose方法,那麽類型就該按部就班為自己的所以資源全部釋放掉。如果調用者忘記調用 Dispose方法,那麽類型就假定自己的所有托管資源(哪怕是那些上段中闡述的非普通類型)全部交給垃圾回收器去回收,而不進行手工清理。理解了這一 點,我們就理解了為什麽Dispose方法中,虛方法傳入的參數是true,而終結器中,虛方法傳入的參數是false。

在實際中我們可以通過try...finally語句來顯示調用Dispose方法,也可以通過using語句來隱式調用Dispose方法,其實這兩者是一樣的,編譯器也是把using語句翻譯為try...finally的形式

using (Font font1 = new Font("Arial", 10.0f)) 
{
    byte charset = font1.GdiCharSet;
}

實際上就是

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

在winform中,經常要用到的Form類也是繼承自IDisposable接口的,所以它也要實現Dispose方法,vs會自動生成protected override void Dispose(bool disposing)的方法的實現,如下

        /// <summary>
        /// 清理所有正在使用的資源。
        /// </summary>
        /// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

因此在實例化Form類對象時,要在using語句裏面new

選擇基類還是接口的原則

1、IS-A對比CAN-DO

如果派生類和基類建立不起IS-A關系,就不用基類而用接口,接口意味著CAN-do關系,表示某些功能,如果類需要實現這些功能,就可以實現該接口

2、易用性

基類比接口要容易。因為接口必須要實現所有方法,而基類已經有了方法的默認實現,繼承基類後,只需要修改不需要默認動作的方法

3、版本控制

向基類中添加新的方法,如果保持原來的方法不變,就不會影像當前的使用,也不用重新編譯代碼。如果向接口中添加了新的方法,就必須在當前的代碼中的重新實現接口並編譯才可以。

因此,盡量使用基類,如果基類不滿足,在考慮添加接口

c# 接口