面向物件程式設計基礎入門(vb.net版)
幾乎在 Visual Basic 中執行的所有操作都與物件關聯。如果您第一次接觸面向物件的程式設計,則下列術語和概念將幫助您入門。
類和物件
單詞“類”和“物件”在面向物件的程式設計中使用得非常多,很容易將它們混淆。一般來說,“類”是一些內容的抽象表示形式,而“物件”是類所表示的內容的可用示例。共享類成員是此規則的一個例外,這種成員可在類的例項和宣告為共享類型別的物件變數中使用。
欄位、屬性、方法和事件
類由欄位、屬性、方法和事件組成。欄位和屬性表示物件包含的資訊。欄位類似於變數,因為可以直接讀取或設定它們。例如,如果有一個名為 Car 的物件,則可以在名為 Color 的欄位中儲存其顏色。
屬性的檢索和設定方法與欄位類似,但是屬性是使用 Property Get 和 Property Set 過程實現的,這些過程對如何設定或返回值提供更多的控制。在儲存值和使用此值的過程之間的間接層幫助隔離資料,並使您得以在分配或檢索值之前驗證這些值。
方法表示物件可執行的操作。例如,Car 物件可以有 StartEngine、Drive 和 Stop 方法。通過向類中新增過程(Sub 例程或函式)來定義方法。
事件是物件從其他物件或應用程式接收的通知,或者是物件傳輸到其他物件或應用程式的通知。事件使物件得以在每當特定情況發生時執行操作。Car 類的一個事件示例是 Check_Engine 事件。因為 Microsoft Windows 是事件驅動的作業系統,所以事件可來自其他物件、應用程式或使用者輸入(如滑鼠單擊或按鍵)。
封裝、繼承和多型性
欄位、屬性、方法和事件只是面向物件程式設計全部內容的一半。真正的面向物件的程式設計需要物件支援三種特性:封裝、繼承和多型性。
“封裝”意味著將一組相關屬性、方法和其他成員視為一個單元或物件。物件可以控制更改屬性和執行方法的方式。例如,物件在允許屬性更改前可驗證值。通過隱藏物件的實現細節(一種稱為“資料隱藏”的做法),封裝還使在以後對實現進行更改變得更容易。
“繼承”描述基於現有類建立新類的能力。新類繼承基類的所有屬性、方法和事件,而且可用其他屬性和方法自定義該新類。例如,可基於 Car 類建立名為 Truck 的新類。Truck 類從 Car 類繼承 Color 屬性,而且可有其他屬性,如 FourWheelDrive。
“多型性”意味著可以有多個可互換使用的類,即使每個類以不同方式實現相同屬性或方法。多型性是面向物件程式設計的精華,因為它允許使用同名的項,而不管此時在使用什麼型別的物件。例如,假設給定基類 Car,多型性使程式設計師能夠為任意數量的派生類定義不同的 StartEngine 方法。名為 DieselCar 的派生類的 StartEngine 方法可以與基類中同名的方法完全不同。其他過程或方法可用完全相同的方式使用派生類的 StartEngine 方法,不管此時使用什麼型別的 Car 物件。
過載、重寫和隱藏
可以使用欄位和屬性在物件中儲存資訊。雖然從客戶端應用程式角度來看,欄位和屬性幾乎無法區別,但在類中宣告它們的方式不同。屬性使用 Property 過程控制如何設定或返回值,而欄位只是類所公開的公共變數。
向類新增欄位
在類定義中宣告一個公共變數,如下面的程式碼所示:
Class ThisClass
Public ThisField As String
End Class
向類新增屬性
在類中宣告一個區域性變數來儲存屬性值。因為屬性不會自行分配任何儲存區,所以該步驟是必需的。若要保護它們的值不被直接修改,用於儲存屬性值的變數應當宣告為 Private。
根據需要以修飾符(如 Public 和 Shared)作為屬性宣告的開頭。使用 Property 關鍵字宣告屬性名稱,並宣告屬性儲存和返回的資料型別。
在屬性定義中定義 Get 和 Set 屬性過程。Get 屬性過程用於返回屬性值,基本等效於語法中的函式。它們不接受引數,可用於返回在類中宣告的、用於儲存屬性值的私有區域性變數的值。Set 屬性過程用於設定屬性的值,它們有引數(通常稱為 Value),該引數的資料型別與屬性本身的資料型別相同。每當屬性值更改時,Value 均會被傳遞給 Set 屬性過程,在該過程中可以驗證它並將其儲存在一個區域性變數中。
根據需要使用 End Get 和 End Set 語句終止 Get 和 Set 屬性過程。
使用 End Property 語句終止屬性塊。
注意 如果正在 Visual Studio 整合開發環境 (IDE) 下工作,可以指示它去除空的 Get 和 Set 屬性過程。鍵入 Property PropName As DataType(其中,PropName 是屬性名稱,DataType 是特定資料型別,如 Integer),相應的屬性過程將出現在程式碼編輯器中。
下面的示例在類中宣告一個屬性:
Class ThisClass
Private m_PropVal As String
Public Property One() As String
Get
Return m_PropVal ' Return the value stored in the local variable.
' Optionally, you can use the syntax One = PropVal to return
' the property value.
End Get
Set(ByVal Value As String)
m_PropVal = Value ' Store the value in a local variable.
End Set
End Property
End Class
當建立 ThisClass 的一個例項並設定 One 屬性的值時,將呼叫 Set 屬性過程且該值在 Value 引數中傳遞,該引數儲存在名為 m_PropVal 的區域性變數中。當檢索此屬性值時,將像函式那樣呼叫 Get 屬性過程並返回儲存在區域性變數 m_PropVal 中的值。
屬性和屬性過程
可以使用屬性和欄位在物件中儲存資訊。屬性使用屬性過程控制如何設定或返回值,而欄位只是公共變數。屬性過程是在屬性定義中宣告的程式碼塊,使您可以在設定或檢索屬性值時執行程式碼。Visual Basic .NET 有兩種型別的屬性過程:Get 屬性過程用於檢索屬性值;Set 屬性過程用於向屬性賦值。例如,儲存銀行帳戶餘額的屬性可能會在 Get 屬性過程中使用程式碼以在返回可用餘額之前記入利息並檢查服務費。用於可用餘額的 Set 屬性過程會提供驗證程式碼以防止不正確地更新餘額。簡而言之,屬性過程允許物件保護和驗證自己的資料
只讀和只寫屬性
大多數屬性有 Get 和 Set 這兩個屬性過程以使您可以同時讀取和修改所儲存的值。然而,您可以使用 ReadOnly 或 WriteOnly 修飾符來限制對屬性的讀取或修改。只讀屬性不能有 Set 屬性過程,它們可用於需要公開但不允許修改的項。例如,可以使用只讀屬性來提供計算機處理器的速度。只寫屬性不能有 Get 屬性過程,它們可用於需要儲存但不對其他物件公開的資料。例如,只寫屬性可以用於儲存密碼。
注意 在將物件分配給屬性時,Visual Basic 的早期版本支援使用 Let 屬性過程。Visual Basic .NET 消除了對 Let 屬性過程的需要,因為可以像處理其他任何種類的分配那樣處理物件分配。
屬性過程與欄位
屬性與欄位都可在物件中儲存和檢索資訊。它們的相似性使得在給定情況下很難確定哪個是更好的程式設計選擇。
在以下情況下使用屬性過程:
需要控制設定或檢索值的時間和方式時。
屬性有定義完善的一組值需要進行驗證時。
設定值會導致物件狀態中發生某些可察覺更改(如一個可見屬性)時。
設定屬性會導致更改其他內部變數或其他屬性的值時。
必須先執行一組步驟,然後才能設定或檢索屬性時。
在以下情況下使用欄位:
值為自驗證型別時。例如,如果將 True 或 False 以外的值分配給 Boolean 變數,就會發生錯誤或自動資料轉換。
在資料型別所支援範圍內的任何值均有效時。Single 或 Double 型別的很多屬性屬於這種情況。
屬性是 String 資料型別,且對於字串的大小或值沒有任何約束時。
類方法
類的方法就是在該類中宣告的 Sub 或 Function 過程。例如,若要為名為 Account 的類建立 Withdrawal 方法,可以向該類模組中新增此 Public 函式:
Public Function WithDrawal(ByVal Amount As Decimal, _
ByVal TransactionCode As Byte) As Double
' Add code here to perform the withdrawal,
' return a transaction code,
' or to raise an overdraft error.
End Function
共享方法
共享方法可以直接從類變數呼叫,而不必首先建立該類的例項。當不希望方法與類的特定例項關聯時,共享方法很有用。共享方法不能用 Overridable、NotOverridable 或 MustOverride 修飾符宣告。模組中宣告的方法是隱式共享的,不能顯式使用 Shared 修飾符。
示例
Class ShareClass
Shared Sub SharedSub()
MessageBox.Show("Shared method.")
End Sub
End Class
Sub Test()
' Create an object variable, but not an instance.
Dim S As ShareClass
S.SharedSub ' Call the method.
End Sub
保護實現詳細資訊
由類內部使用的實用工具過程應宣告為 Private、Protected 或 Friend。限制這類方法的可訪問性可防止它們被其他開發人員使用,而且使您得以將來在不影響使用這些物件的程式碼的情況下進行更改。
隱藏物件實現的詳細資訊是“封裝”的另一方面。封裝使您得以提高方法的效能,或完全改變實現方法的方式,而不必更改使用該方法的程式碼。
屬性與方法
屬性和方法都作為接受引數的過程實現,在這一點上它們很相似。通常,屬性儲存物件的資料,而方法是可要求物件執行的操作。物件的一些特性明顯是屬性,比如 Name,而有些明顯是方法,比如 Move 和 Show。在其他情況中,哪些類成員應是屬性哪些應是方法並不明顯。例如,集合類的 Item 方法儲存和檢索資料,可作為索引屬性實現。另一方面,將 Item 作為方法實現也是合理的。
屬性語法與方法語法
決定如何實現類成員的一個方法是考慮要如何使用它。雖然從引數化屬性檢索資訊的語法與作為函式實現的方法所用的語法幾乎相同,但修改這樣的值的語法卻稍有不同。例如,如果將類的成員實現為屬性,則下面的語法描述將如何使用它:
ThisObject.ThisProperty(Index) = NewValue
如果將類成員實現為方法,則要修改的值必須是引數。下面的程式碼片段描述等效的語法用法:
ThisObject.ThisProperty(Index,NewValue)
錯誤資訊
選擇如何實現類成員時要考慮的另一個因素是,當錯誤地使用類時將生成何種訊息。如果有人無意中試圖為只讀屬性分配一個值,則將返回一條錯誤資訊,該錯誤資訊不同於響應對方法的類似呼叫所返回的錯誤資訊。正確實現的類成員返回更容易解釋的錯誤資訊。
預設屬性
接受引數的屬性可宣告為類的預設屬性。“預設屬性”是當未給物件命名特定屬性時 Microsoft Visual Basic .NET 將使用的屬性。因為預設屬性使您得以通過省略常用屬性名使原始碼更為精簡,所以預設屬性非常有用。
最適宜作為預設屬性的是那些接受引數並且您認為將最常用的屬性。例如,Item 屬性就是集合類預設屬性的很好的選擇,因為它被經常使用。
下列規則適用於預設屬性:
一種型別只能有一個預設屬性,包括從基類繼承的屬性。此規則有一個例外。在基類中定義的預設屬性可以被派生類中的另一個預設屬性隱藏。
如果基類中的預設屬性被派生類中的非預設屬性隱藏,使用預設屬性語法仍可以訪問該預設屬性。
預設屬性不能是 Shared 或 Private。
如果某個過載屬性是預設屬性,則同名的所有過載屬性必須也指定 Default。
預設屬性必須至少接受一個引數。
示例
下面的示例將一個包含字串陣列的屬性宣告為類的預設屬性:
Class Class2
' Define a local variable to store the property value.
Private PropertyValues As String()
' Define the default property.
Default Public Property Prop1(ByVal Index As Integer) As String
Get
Return PropertyValues(Index)
End Get
Set(ByVal Value As String)
If PropertyValues Is Nothing Then
' The array contains Nothing when first accessed.
ReDim PropertyValues(0)
Else
' Re-dimension the array to hold the new element.
ReDim Preserve PropertyValues(UBound(PropertyValues) + 1)
End If
PropertyValues(Index) = Value
End Set
End Property
End Class
訪問預設屬性
可以使用縮寫語法訪問預設屬性。例如,下面的程式碼片段同時使用標準和預設屬性語法:
Dim C As New Class2()
' The first two lines of code access a property the standard way.
C.Prop1(0) = "Value One" ' Property assignment.
MessageBox.Show(C.Prop1(0)) ' Property retrieval.
' The following two lines of code use default property syntax.
C(1) = "Value Two" ' Property assignment.
MessageBox.Show(C(1)) ' Property retrieval.
過載屬性和方法
過載是在一個類中用相同的名稱但是不同的引數型別建立一個以上的過程、例項建構函式或屬性。
當物件模型指示對於在不同資料型別上進行操作的過程使用同樣名稱時,過載非常有用。例如,可顯示幾種不同資料型別的類可以具有類似如下所示 Display 過程:
Overloads Sub Display(ByVal theChar As Char)
' Add code that displays Char data.
End Sub
Overloads Sub Display(ByVal theInteger As Integer)
' Add code that displays Integer data.
End Sub
Overloads Sub Display(ByVal theDouble As Double)
' Add code that displays Double data.
End Sub
如果不使用過載,那麼即使每個過程執行相同的操作,也需要為它們建立不同的名稱,如下所示:
Sub DisplayChar(ByVal theChar As Char)
' Add code that displays Char data.
End Sub
Sub DisplayInt(ByVal theInteger As Integer)
' Add code that displays Integer data.
End Sub
Sub DisplayDouble(ByVal theDouble As Double)
' Add code that displays Double data.
End Sub
因為過載提供了對可用資料型別的選擇,所以它使得屬性或方法的使用更為容易。例如,可以用下列任一程式碼行呼叫前面討論過的過載 Display 方法:
Display("9"C) ' Call Display with a literal of type Char.
Display(9) ' Call Display with a literal of type Integer.
Display(9.9R) ' Call Display with a literal of type Double.
在執行時,Visual Basic .NET 根據指定引數的資料型別呼叫正確的過程。
注意 過載、重寫和隱藏操作是容易混淆的類似概念。有關更多資訊,請參見介紹 Visual Basic 中的物件。
過載規則
用同樣名稱新增兩個或更多屬性或方法可以建立類的一個過載成員。除了過載派生成員,每一個過載成員必須具有不同的引數列表。當過載屬性或過程時,下面的項不能用作區分特徵:
應用於成員或成員引數的修飾符,如 ByVal 或 ByRef。
引數名
過程的返回型別
過載時關鍵字 Overloads 是可選的,但如果任一過載成員使用了該 Overloads 關鍵字,則其他所有同名過載成員也必須指定該關鍵字。
派生類可以用具有相同引數和引數型別的成員過載繼承成員,該過程稱作“按名稱和簽名隱藏”。如果按名稱和簽名隱藏時使用了 Overloads 關鍵字,將使用該成員的派生類實現而非基類中的實現,並且該成員的所有其他過載對於該派生類的例項都將可用。
如果用一個具有相同引數和引數型別的成員過載繼承成員時,省略了 Overloads 關鍵字,則該過載稱為“按名稱隱藏”。按名稱隱藏替代一個成員的繼承實現,使所有其他過載對於該派生類及由其派生的類的例項都不可用。
Overloads 和 Shadows 修飾符不能同時被同一個屬性或方法所使用。
示例
下面的示例建立接受美元金額的 String 或 Decimal 表示形式並返回包含銷售稅的字串的過載方法。
使用此示例建立過載方法
開啟新專案,新增名為 TaxClass 的類。
向 TaxClass 類中新增下面的程式碼。
Public Class TaxClass
Overloads Function TaxAmount(ByVal decPrice As Decimal, _
ByVal TaxRate As Single) As String
TaxAmount = "Price is a Decimal. Tax is $" & _
(CStr(decPrice * TaxRate))
End Function
Overloads Function TaxAmount(ByVal strPrice As String, _
ByVal TaxRate As Single) As String
TaxAmount = "Price is a String. Tax is $" & _
CStr((CDec(strPrice) * TaxRate))
End Function
End Class
向窗體中新增下面的過程。
Sub ShowTax()
Const TaxRate As Single = 0.08 ' 8% tax rate
Dim strPrice As String = "64.00" ' $64.00 Purchase as a String.
Dim decPrice As Decimal = 64 ' $64.00 Purchase as a Decimal.
Dim aclass As New taxclass()
'Call the same method with two diferent kinds of data.
MessageBox.Show(aclass.TaxAmount(strPrice, TaxRate))
MessageBox.Show(aclass.TaxAmount(decPrice, TaxRate))
End Sub
向窗體中新增按鈕,並從該按鈕的 Button1_Click 事件呼叫 ShowTax 過程。
執行該專案並單擊窗體上的該按鈕,以測試過載 ShowTax 過程。
在執行時,編譯器選擇與所用引數匹配的適當過載函式。單擊該按鈕時,首先將使用型別為字串的 Price 引數呼叫被過載的方法,並顯示資訊:“Price is a String.Tax is $5.12”。第二次將使用型別為 Decimal 的值呼叫 TaxAmount,並顯示資訊“Price is a Decimal.Tax is $5.12”。
重寫屬性和方法
派生類繼承其基類中定義的屬性和方法。這很有用,因為它意味著當這些項適合於您要使用的類時,可以重用它們。如果繼承成員不能按原樣使用,則可以選擇使用 Overrides 關鍵字定義新實現,假設基類中的屬性或方法使用 Overridable 關鍵字標記,或通過重新在派生類中定義成員來隱藏該成員。
實際上,重寫的成員經常用於實現多型性。有關多型性的更多資訊,請參見多型性。
下列規則適用於重寫方法。
僅可重寫在其基類中用 Overridable 關鍵字進行標記的成員。
預設情況下,屬性和方法為 NotOverridable。
重寫的成員必須具有與從基類繼承的成員相同的引數。
成員的新實現可通過在方法名稱前指定 MyBase 來呼叫父類中的原始實現。
注意 過載、重寫和隱藏操作是容易混淆的類似概念。有關更多資訊,請參見介紹 Visual Basic 中的物件。
示例
假設您要定義類以處理工資單。您可以定義一個一般 Payroll 類,其中包含計算普通周工資單的 RunPayroll 方法。然後可將 Payroll 用作更專用的 BonusPayroll 類的基類,分發僱員獎金時可使用該 BonusPayroll 類。
BonusPayroll 類可繼承並重寫在基類 Payroll 中定義的 PayEmployee 方法。
下面的示例定義基類 Payroll 和派生類 BonusPayroll,該派生類重寫繼承方法 PayEmployee。過程 RunPayroll 建立 Payroll 物件和 BonusPayroll 物件,然後將其傳遞給函式 Pay,該函式執行這兩個物件的 PayEmployee 方法。
Const BonusRate As Decimal = 1.45
Const PayRate As Decimal = 14.75
Class Payroll
Overridable Function PayEmployee(ByVal HoursWorked As Decimal, _
ByVal PayRate As Decimal) As Decimal
PayEmployee = HoursWorked * PayRate
End Function
End Class
Class BonusPayroll
Inherits Payroll
Overrides Function PayEmployee(ByVal HoursWorked As Decimal, _
ByVal PayRate As Decimal) As Decimal
' The following code calls the original method in the base
' class, and then modifies the returned value.
PayEmployee = MyBase.PayEmployee(HoursWorked, PayRate) * BonusRate
End Function
End Class
Sub RunPayroll()
Dim PayrollItem As Payroll = New Payroll()
Dim BonusPayrollItem As New BonusPayroll()
Dim HoursWorked As Decimal = 40
MessageBox.Show("Normal pay is: " & _
PayrollItem.PayEmployee(HoursWorked, PayRate))
MessageBox.Show("Pay with bonus is: " & _
BonusPayrollItem.PayEmployee(HoursWorked, PayRate))
End Sub
重寫修飾符
可使用 NotOverridable 和 MustOverride 修飾符控制如何在派生類中重寫屬性和方法。
NotOverridable 修飾符定義無法在派生類中重寫的基類的方法。所有方法都為 NotOverridable,除非用 Overridable 修飾符進行標記。當不希望允許在派生類中再次重寫 overridden 方法時,可使用 NotOverridable 修飾符。
用 MustOverride 修飾符定義的方法在基類中沒有實現,必須在派生類中實現。包含 MustOverride 方法的類必須使用 MustInherit 修飾符進行標記。
示例
MustInherit Class BaseClass
Public MustOverride Sub aProcedure()
End Class
Class DerivedClass
Inherits BaseClass
Public NotOverridable Overrides Sub aProcedure()
' Override a procedure inherited from the base class
' and mark it with the NotOverridable modifier so that
' it cannot be overridden in classes derived from this class.
End Sub
End Class
用集合管理物件
集合提供一種管理各種物件的理想方法。可以在集合中新增和移除物件、根據索引或鍵檢索它們,以及使用 For Each...Next 語句迴圈通過集合中的項。但是,集合極強的靈活性可能會破壞類的可靠性。
可以採用三種常規方法使用集合來實現物件管理。請考慮組織小部件並將其公開給客戶端元件的元件。若要使用集合實現此元件,則可以:
在 WidgetRepository 類中,將 Widgets 變數宣告為集合,並使其成為公共變數。
通過繼承 CollectionBase 類來實現您自己的 WidgetsCollection 類。授予 WidgetRepository 類一個 WidgetsCollection 類的屬性。
通過編寫適當的類和方法在 WidgetRepository 類中實現集合型別功能來實現此功能。如果要在類中擁有集合型別功能,但無法從任何集合型別類繼承時,這種方法最為有用。例如,如果希望從集合類之外的其他類繼承。
可以向類中新增集合以管理可能由該類公開的物件。實現此功能的最簡單的方法是將型別為 Collection 的公共變數新增到類中。請考慮名為 WidgetRepository 的假設類,該類管理和公開小部件。可以建立 WidgetCollection 變數作為小部件的集合,如下面的示例中所示:
Public Class WidgetRepository
Public WidgetCollection As New Collection()
' Insert code to implement additional functionality.
End Class
現在此類有一個公共集合,可以向其新增 Widget 物件。但這種方法有一些問題。首先,此集合不是強型別的。如果執行下面程式碼將會發生什麼呢?
Dim myString As String = "This is not a Widget object!"
WidgetCollection.Add(myString)
答案是它將被新增到該集合中,即使它不是 Widget 物件。事實上,任何物件都可新增到集合中,不管是什麼型別。如果接著嘗試使用 For Each...Next 語句處理小部件,問題隨即發生,如下所示:
Dim aWidget As Widget
For Each aWidget in WidgetCollection
' Insert code to process Widgets
Next
在該示例中,程式在執行時引發 ArgumentException 異常,因為集合的一個成員不是 Widget 型別。
另一個問題涉及公開集合的成員。因為 Collection 類接受並返回 Object 型別的成員,所以當 Option Strict 語句為 On 時,無法使用集合所管理的物件的任何功能。若要將該集合提供的引用轉換為適當的型別,則必須改用 CType 函式,如下所示:
Dim myWidget As Widget
MyWidget = CType(WidgetCollection(1), Widget)
請注意,當 Option Strict 為 Off 時,將隱式執行轉換,儘管晚期繫結將導致效能的下降。
如果需要避免這些問題,或您的類要求更可靠型別的集合,應考慮使用 CollectionBase 建立您自己的集合類。有關詳細資訊,請參見演練:建立您自己的集合類。
事件和事件處理程式
Visual Studio 專案很容易被看作一系列順序執行的過程。事實上,多數程式都是事件驅動的,即執行流程是由外界發生的事件所確定的。
事件是一個訊號,它告知應用程式有重要情況發生。例如,使用者單擊窗體上的某個控制元件時,窗體引發一個 Click 事件並呼叫一個處理該事件的過程。事件還允許在不同任務之間進行通訊。比方說,您的應用程式脫離主程式執行一個排序任務。若使用者取消這一排序,應用程式可以傳送一個取消事件讓排序過程停止。
事件術語和概念
本節描述 Visual Basic .NET 中與事件一起使用的術語和概念。
宣告事件
使用 Event 關鍵字在類、結構、模組和介面內部宣告事件,如以下示例所示:
Event AnEvent(ByVal EventNumber As Integer)
引發事件
事件就像是通告已發生重要情況的訊息。廣播該訊息的行為叫引發事件。在 Visual Basic .NET 中,使用 RaiseEvent 語句引發事件,如以下示例所示:
RaiseEvent AnEvent(EventNumber)
必須在宣告事件的範圍內引發事件。例如,派生類不能引發從基類繼承的事件。
事件傳送器
任何能引發事件的物件都是事件傳送者,也稱事件源。窗體、控制元件和使用者定義的物件都是事件傳送器。
事件處理程式
事件處理程式是相應事件發生時呼叫的過程。您可以將任何有效子例程用作事件處理程式。可是,不能將函式用作事件處理程式,因為它不能將值返回給事件源。
Visual Basic 採用標準命名約定對事件處理程式進行命名,即用下劃線把事件傳送器和事件的名稱組合起來。例如,名為 button1 的按鈕的單擊事件應命名為 Sub button1_Click。
注意 建議使用此命名約定定義您自己事件的事件處理程式。但這並不是必選的方法。您可以使用任何有效的子例程。
關聯事件與事件處理程式
在事件處理程式生效之前,首先必須使用 Handles 或 AddHandler 語句將它與事件關聯。
WithEvents 語句和 Handles 子句提供了陳述性指定事件處理程式的方法。WithEvents 所宣告物件引發的事件可以由任何子例程用命名此事件的 Handles 子句來處理。雖然 Handles 子句是關聯事件與事件處理程式的標準方法,它僅限於在編譯時關聯事件與事件處理程式。
AddHandler 和 RemoveHandler 語句要比 Handles 子句更靈活。它們允許在執行時動態地將事件與一個或更多的事件處理程式連線或者斷開,而並不要求使用 WithEvents 來宣告物件變數。
在某些情況下,比如使用窗體或控制元件所關聯的事件,Visual Basic .NET 會自動引出一個空事件處理程式並將它與某個事件關聯。例如,在設計模式下雙擊窗體上的命令按鈕時,Visual Basic .NET 會為命令按鈕建立一個空事件處理程式和一個 WithEvents 變數,如以下示例所示:
Friend WithEvents Button1 As System.Windows.Forms.Button
Protected Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
End Sub
介面概述
和類一樣,介面也定義了一系列屬性、方法和事件。但與類不同的是,介面並不提供實現。它們由類來實現,並從類中被定義為單獨的實體。
介面表示一種約定,實現介面的類必須嚴格按其定義來實現介面的每個方面。
有了介面,就可以將功能定義為一些緊密相關成員的小組。可以在不危害現有程式碼的情況下,開發介面的增強型實現,從而使相容性問題最小化。也可以在任何時候通過開發附加介面和實現來新增新的功能。
雖然介面實現可以進化,但介面本身一旦被髮布就不能再更改。對已釋出的介面進行更改會破壞現有的程式碼。若把介面視為約定,很明顯約定雙方都各有其承擔的義務。介面的釋出者同意不再更改該介面,介面的實現者則同意嚴格按設計來實現介面。
Visual Basic .NET 以前的 Visual Basic 版本可以使用介面,但不能直接建立它們。Visual Basic .NET 允許用 Interface 語句定義真正的介面,並允許用改進版本的 Implements 關鍵字來實現這些介面。
繼承的基礎知識
Inherits 語句用於基於現有類(稱為“基類”)來宣告新類(稱為“派生類”)。派生類繼承並可擴充套件基類中定義的屬性、方法、事件、欄位和常數。下面一節描述一些繼承規則,以及一些可用來更改類繼承或被繼承方式的修飾符:
預設情況下,所有類都是可繼承的,除非用 NotInheritable 關鍵字標記。類可以從專案中的其他類繼承,也可以從專案引用的其他程式集中的類繼承。
與允許多重繼承的語言不同,Visual Basic .NET 只允許類中有單一繼承,即派生類只能有一個基類。雖然類中不允許有多重繼承,但類“可以”實現多個介面,這樣可以有效地實現同一目的。
若要防止公開基類中的受限項,派生類的訪問型別必須與其基類一樣或比其基類所受限制更多。例如,Public 類無法繼承 Friend 或 Private 類,而 Friend 類無法繼承 Private 類。
繼承修飾符
Visual Basic .NET 引入了下列類級別語句和修飾符以支援繼承:
Inherits 語句 — 指定基類。
NotInheritable 修飾符 — 防止程式設計師將該類用作基類。
MustInherit 修飾符 — 指定該類僅適於用作基類。無法直接建立 MustInherit 類的例項,只能將它們建立為派生類的基類例項。(其他程式語言,如 C++ 和 C#,使用術語“抽象類”來描述這樣的類。)
重寫派生類中的屬性和方法
預設情況下,派生類從其基類繼承方法。如果繼承的屬性或方法需要在派生類中有不同的行為,則可以“重寫”它,即,可以在派生類中定義該方法的新實現。下列修飾符用於控制如何重寫屬性和方法:
Overridable — 允許某個類中的屬性或方法在派生類中被重寫。
Overrides — 重寫基類中定義的 Overridable 屬性或方法。
NotOverridable — 防止某個屬性或方法在繼承類中被重寫。預設情況下,Public 方法為 NotOverridable。
MustOverride — 要求派生類重寫屬性或方法。當使用 MustOverride 關鍵字時,方法定義僅由 Sub、Function 或 Property 語句組成。不允許有其他語句,尤其是不能有 End Sub 或 End Function 語句。必須在 MustInherit 類中宣告 MustOverride 方法。
有關重寫方法的更多資訊,請參見重寫屬性和方法
MyBase 關鍵字
當重寫派生類中的方法時,可以使用 MyBase 關鍵字呼叫基類中的方法。例如,假設您正在設計一個重寫從基類繼承的方法的派生類。重寫的方法可以呼叫基類中的該方法,並修改返回值,如下面的程式碼片段中所示:
Class DerivedClass
Inherits BaseClass
Public Overrides Function CalculateShipping(ByVal Dist As Double, _
ByVal Rate As Double) As Double
' Call the method in the base class and modify the return value.
Return MyBase.CalculateShipping(Dist, Rate) * 2
End Function
End Class
下面的列表描述對使用 MyBase 的限制:
MyBase 引用直接基類及其繼承成員。它無法用於訪問類中的 Private 成員。
MyBase 是關鍵字,不是實際物件。MyBase 無法分配給變數,無法傳遞給過程,也無法用在 Is 比較中。
MyBase 限定的方法不需要在直接基類中定義,它可以在間接繼承的基類中定義。為了正確編譯 MyBase 限定的引用,一些基類必須包含與呼叫中出現的引數名稱和型別匹配的方法。
不能使用 MyBase 來呼叫 MustOverride 基類方法。
MyBase 無法用於限定自身。因此,下面的程式碼是非法的:
MyBase.MyBase.BtnOK_Click() ' Syntax error.
MyBase 無法用在模組中。
如果基類在不同的程式集中,則不能使用 MyBase 來訪問標記為 Friend 的基類成員。
MyClass 關鍵字
MyClass 關鍵字使您得以呼叫在類中實現的 Overridable 方法,並確保呼叫此類中該方法的實現,而不是呼叫派生類中重寫的方法。
MyClass 是關鍵字,不是實際物件。MyClass 無法分配給變數,也無法傳遞給過程,而且也無法用在 Is 比較中。
MyClass 引用包含類及其繼承成員。
MyClass 可用作 Shared 成員的修飾符。
MyClass 無法用在標準模組中。
MyClass 可用於限定這樣的方法,該方法在基類中定義但沒有在該類中提供該方法的實現。這種引用的意義與 MyBase.Method 相同。
基於繼承的多型性
大部分面向物件的程式設計系統都通過繼承提供多型性。基於繼承的多型性涉及在基類中定義方法並在派生類中使用新實現重寫它們。
例如,可以定義一個類 BaseTax,該類提供計算某個州/省的銷售稅的基準功能。從 BaseTax 派生的類,如 CountyTax 或 CityTax,如果適合的話可實現如 CalculateTax 這樣的方法。
多型性來自這樣一個事實:可以呼叫屬於從 BaseTax 派生的任何類的某個物件的 CalculateTax 方法,而不必知道該物件屬於哪個類。
下面示例中的 TestPoly 過程演示基於繼承的多型性:
Const StateRate As Double = 0.053 ' %5.3 State tax
Const CityRate As Double = 0.028 ' %2.8 City tax
Public Class BaseTax
Overridable Function CalculateTax(ByVal Amount As Double) As Double
Return Amount * StateRate ' Calculate state tax.
End Function
End Class
Public Class CityTax
' This method calls a method in the base class
' and modifies the returned value.
Inherits BaseTax
Private BaseAmount As Double
Overrides Function CalculateTax(ByVal Amount As Double) As Double
' Some cities apply a tax to the total cost of purchases,
' including other taxes.
BaseAmount = MyBase.CalculateTax(Amount)
Return CityRate * (BaseAmount + Amount) + BaseAmount
End Function
End Class
Sub TestPoly()
Dim Item1 As New BaseTax()
Dim Item2 As New CityTax()
ShowTax(Item1, 22.74) ' $22.74 normal purchase.
ShowTax(Item2, 22.74) ' $22.74 city purchase.
End Sub
Sub ShowTax(ByVal Item As BaseTax, ByVal SaleAmount As Double)
' Item is declared as BaseTax, but you can
' pass an item of type CityTax instead.
Dim TaxAmount As Double
TaxAmount = Item.CalculateTax(SaleAmount)
MsgBox("The tax is: " & Format(TaxAmount, "C"))
End Sub
在此示例中,ShowTax 過程接受 BaseTax 型別的名為 Item 的引數,但還可以傳遞從形狀類派生的任何類,如 CityTax。這種設計的優點在於可新增從 BaseTax 類派生的新類,而不用更改 ShowTax 過程中的客戶端程式碼。