Visual Basic 6/VBScript 與
Visual Basic.NET(Visual Basic.NET) 的比較

恆逸資訊教育訓練中心首頁

作者: 恆逸資訊 胡百敬

因為未來微軟 .NET 平台的執行環境與現今大大的不同,未來 Class 可以跨語言地繼承,也就是 VB 可以繼承 C# 的 Class 等等。原始程式碼編譯成 Managed Code,平台提供執行時期將 IL 編譯成執行碼等等。在再讓 VB 必須要脫胎換骨。所以 Visual Basic.NET 將會有絕大的改版,與以往的六個版本截然不同。

Visual Basic.NET 與以往的版本實在差別太多,在這裡無法一一詳述。僅列舉明顯且重用的部分。
< Visual Basic 6 與 Visual Basic.NET 差異的部分 >
以下先介紹 VB6/Visual Basic.NET 都有的部分,但 Visual Basic.NET 改變使用方式或關鍵字更新。內容有
不再使用 Set 和 Let
不再使用 Set 和 Let 關鍵字,除非在 class 檔案中設定屬性。因為在 Visual Basic.NET 中不再使用預設(default)屬性和方法,所以不需要靠他們來辨別,你在使用物件的方法與屬性時,若要設定變數值,一定要指明清楚。也就是原先在 VB6 若要設定變數 obj 內容為 objThis,必須要
Set obj=objThis

而接收預設屬性則可以
att=objThis 以及 att=objThis.DefaultAtt 兩種寫法

所以若 objThis 物件有一個預設屬性叫 DefaultAtt,若不寫 Set 會不清楚到底要存取 objThis 物件的參照還是 objThis.DefaultAtt 這個預設屬性。但到了 Visual Basic.NET,若你要讀取 DefaultAtt,因為沒有預設屬性的使用方式,所以只有(且一定要)以 objThis.DefaultAtt 這種寫法來讀取 DefaultAtt 屬性。
所以當你使用下列程式碼時
obj=objThis

那一定是要 objThis 這個物件參照,而不會模擬兩可。而這種寫法讓 Visual Basic.NET 的語法與 JavaScript 和 C# 的語法更為接近。

在 Visual Basic.NET 中 Class 的屬性撰寫

在 VB 6 要撰寫一個物件的屬性會利用以下的程式碼,其中 Property Get 是用來讓使用者讀取屬性,而 Property Set/Let 則是用來設定屬性,至於是要使用 Set 還是 Let 則要看賦予的屬性是物件還是一般的變數型別。

Private m_myProperty As Variant


Public Property Get myProperty() As Variant
If IsObject(m_myProperty)
Then Set myProperty = m_myProperty
Else
myProperty = m_myProperty
End If
End Property


Public Property Set myProperty(ByVal vNewValue As Variant)
Set m_myProperty = vNewValue
End Property


Public Property Let myProperty(ByVal vNewValue As Variant)
m_myProperty = vNewValue
End Property

但在 Visual Basic.NET 的程式碼要改成

Private m_myProperty As String
Public Property myProperty() As String
Get
myProperty = m_myProperty
End Get


Set
m_myProperty = Value
End Set
End Property

這個語法也是與 C# 的語法更為相似。

另外在 Visual Basic.NET 你必須要明確的利用 ReadOnly 或 WriteOnly 關鍵字來設定屬性是否為唯讀或唯寫,所以程式碼為

Public ReadOnly Property myProperty() As String
Get
myProperty = m_myProperty
End Get
End Property

或是

Public WriteOnly Property myProperty() As String
Set
m_myProperty = Value
End Se
t End Property

Subroutines 和 Functions

Visual Basic.NET 在呼叫與使用 Subroutine 和 Function 上也有許多改變

■ 方法呼叫的語法改變 --
所有的方法、Subroutine 和 Function 的呼叫都需要加上小括號,在以往若呼叫 Function 但不需要 Function 的回傳值﹔或 Function 不需要傳入參數,可以使用不加小括號的方式,或呼叫 Subroutine 不使用小括號等等是可以的,但在 Visual Basic.NET 都不再允許。
所以在 VB 6 的
datTime=Now '呼叫系統的 Now() 函數


DoSomething "par1",int2,"par3" '呼叫副函數 DoSomething,傳入三個參數
等方式要改成
datTime=Now()


DoSomething("par1",int2,"par3")

所以在 ASP+ 要注意
Response.Write "某些文字"

也要改成
Response.Write("某些文字")

■ 參數傳遞預設以 ByVal --
在 VB6 參數傳遞若不設定,預設是以 ByRef 也就是傳變數位址給副函數的方式,但在 Visual Basic.NET 改成預設以 ByVal 將變數的值傳遞給副函數。
但要注意的是對 Classes 和 Interfaces 以及 array 和 string 的傳遞預設仍然是採用 ByRef

■ 使用 Return 語法 --
Visual Basic.NET 不支援 VB6 為了向前相容(自 Basic 以來就存在 J )而使用的 GoSub 語法。現在 Return 語法專門用來在 Function 或 Sub 內還回控制權到呼叫者端。

完整的資料型態範圍


在 VBScript 只支援 Variant 資料型別,而在 Visual Basic.NET 所有的東西都是物件(object),包含 intrinsic 資料型別。而 Variant 型別只是一個特殊型態的物件。

Variant 型別在使用上一般比較沒有效率,但可能在某些地方用起來比較方便,例如從資料庫承接可能會傳回 NULL 的欄位,程式碼如右
Dim rec As Recordset
Set rec = New Recordset
rec.Open "Select Region From Northwind.dbo.Customers WHERE CustomerID='ALFKI'", "Provider=SQLOLEDB;Data Souce=(local);Initail Catalog=Northiwnd;User ID=sa;"
Dim res As Variant
res = rec.Fields(0)
If IsNull(res) Then
MsgBox "內容是 Null"
Else
MsgBox res
End If
在 Visual Basic.NET 的 Intrinsic 資料型別有
型態
資料型態名稱
大小
預設值
數字 Byte 1 byte(8 bits) 0
  Short 2 bytes(16 bits) 0
  Integer 4 bytes(32 bits) 0
  Long 8 bytes(64 bits) 0
  Single 4 bytes(32 bits) 0.0
  Double 8 bytes(64 bits) 0.0
  Decimal 12 bytes(96 bits) 0.0
Unicode 文字 String   ""
  Char 2 bytes(16 bits) ""
其他型別 Boolean   False
  Date   #01/01/0001 12:00:00AM#
注意,不再支援 Currency 型別,可以使用 Decimal 型別取代

宣告變數、常數和陣列


在 Visual Basic.NET 有增加一些在宣告變數、常數和陣列方面的語法,在宣告的同時可以賦予值--宣告的語句與執行的語法相似
Dim x As Integer=24
Dim y As Integer= x*24


Const My_Number=42
Const My_Number As Integer=42
Const My_String = "Hello World"
Const My_String As String = "Hello World"
注意,與 VB6 不相容的是在同一個宣告的語句中只能宣告一種型態,所以不能有下列的語法。
Dim x As Integer, y as String

也因此
Dim x,y As Integer

會宣告 x 和 y 兩個變數都是整數型別。 Dim 關鍵字也可以宣告初始化過的陣列
  Dim MyArray(20) As Integer
Dim MyArray(5) As Integer = (1,2,3,4,5)
Dim MyArray(3) As String = ("Bill", "Fred", "Mary")

陣列一定要透過 Dim 宣告,若以空的括號,內不定義數字,則該陣列可以在之後重新定義大小。ReDim 可以用來重新定義陣列的大小,但不能改變陣列的維度。
  Dim MyArray() As Integer
ReDim MyArray(20)


Dim MyArray(,,) '<---透過逗號來定義陣列的維度
ReDim MyArray(20,10,5)

變數範圍(Variable Scope)

Visual Basic.NET 讓 Local 變數支援 block 範圍,也就是在迴圈或 If 等等區塊內宣告的變數在外部是看不到的,所以若程式碼撰寫如下
Imports System


Namespace MyNamespace


Module MyModule
Sub Main()
Dim j as integer
For j=1 to 10
If True Then
Dim i As Integer
i=i+1
Console.WriteLine("在內部的 i= " & cstr(i))
End If
Console.WriteLine("可否使用在內部的 i= " & cstr(i))
Next j
End Sub


End Module


End Namespace

在 If 區塊外使用變數 i 會導致編譯錯誤,如下圖
若拿掉該行,便可以正確執行。

但要注意的是,雖然在區塊之外看不見變數,但變數的生命週期(lifetime)並未結束。也就是說若你重新進入該區塊,你仍然可以使用該變數。所以上述的程式碼經編譯後的執行結果如下

結構化例外處理(Structured Exception Handling)

Visual Basic.NET 支援結構化例外處理,使用其他語言如 C++ 早已經使用的語法 Try...Catch...Finally 以受保護的程式區塊(protected blocks)搭配過濾(filter)的使用﹔來做例外處理。 使用原先 On Error... 這種非結構化的例外處理效率較差,並較難維護程式碼。

■ Try...Catch...Finally 的程式碼結構如下 --
Try ' 開始結構化例外處理,在這一段裡面的程式碼可能會產生例外狀況
Catch [選擇性的過濾] '如果在 Try 程式段之中有例外發生,就會執行這裡面的程式碼
[其他的 Catch 區塊]
Finally '在離開 Try 區塊之前一定會執行的程式碼
End Try  

在 Try 的區塊中放有例外處理需要監控的程式碼。如果在這一個區段中執行的任何程式碼發生錯誤,執行權會傳送到 Catch 區塊中的第一行程式碼。在 Catch 區塊中可以放置處理一般例外(錯誤)的程式碼。你可以定義多個 Catch 區塊,並定義在不同的狀況下執行不同的 Catch 區塊。在 Finally 區塊可以放置結尾的程式碼,如關閉檔案,釋放物件等等。

■ 使用 Try...Catch...Finally --
使用 Try...Catch...Finally 區塊可以包住可能會發生錯誤的程式碼,你可以利用巢狀(nest)的方式在例外處理中再包例外處理,在每一個區塊中宣告的變數屬於該區塊的區域變數。
程式範例如下

Function GetStringsFromFile(ByVal FileName As String) As String
Dim strTest, Strings As String
Dim Stream As StreamReader = File.OpenText(FileName) '開啟檔案


Try
While True '迴圈一直執行到 EndOfStreamException 錯誤發生
strTest = Stream.readline()
If strTest = "" Then
Throw New EndOfStreamException
End If
Strings = Strings + strTest
End While
Catch EOFExcep As EndOfStreamException
' 不需要做任何事,已經達到檔案結尾
Catch IOExcep As IOException
' 有一些錯誤發生了,回報錯誤的發生
MsgBox(IOExcep.Message)
Strings = Nothing
Finally
Stream.Close() ' 關閉檔案
End Try
Return Strings
End Function


使用正確的資料型別


在定義 subroutines 和 functions 時,可以利用 VB 提供的資料型別來定義參數,以及 function 回傳的型別
Sub DoSomething(ByVal strValue1 As String, ByVal intCount As Integer, ByRef strValue2 As String = "初始內容")
Function DoSomething(ByVal strValue1 As String, ByVal intCount As Integer, ByRef strValue2 As String = "初始內容") As String

你可以利用 Optional 關鍵字來定義可以選擇性輸入的參數,但因為 Visual Basic.NET 不再支援 IsMissing 關鍵字,所以一定要給預設值。
Function DoSomething(ByVal strValue1 As String, ByVal intCount As Integer, ByRef strValue2 As String = "初始內容"
_ Optional ByVal strType = "預設內容" As String) As String

■ 整數和長整數資料型別改變 --
整數(Integer)現在是 32 bits 而不是以往的 16 bits,長整數(long) 現在是 64 bits 而不是 32 bits。這讓 VB 與其他的語言更容易合作,尤其是在呼叫 API 的時候。呼叫 API 往往要注意 bits 的長度,所以你特別要注意現在 VB 對資料型別定義的改變。

■ 轉變(Cast)到正確的資料型別 --
 
Visual Basic.NET 要求使用正確的資料型別,所以當你在轉換資料型別時要使用適當的函數,列表如下
函數名稱
傳回的資料型別
CBool(value) Boolean
CByte(value) Byte
CChar(value) Char
CDate(value) Date
CDec(value) Decimal
CDbl(value) Double
CInt(value) Integer
CLng(value) Long
CObj(value) Object
CShort(value) Short
CSng(value) Single
CStr(value) String

使用者自訂資料結構(Structure)

在 Visual Basic.NET 使用關鍵字 Structure 來定義使用者自訂的變數結構(User-defined type UDT),且不支援 VB6 使用 Type 關鍵字。
程式碼範例如下
Structure abc
Public UserName As String
Public UserId As Integer
Dim UserAge As Integer
End Structure

...


Dim emp As abc
emp.UserAge = 30
emp.UserName = "胡百敬"
emp.UserId = 12345
在結構中的成員必須要定義存取範圍如 Public、Private 或是 Protected。你也可以使用 Dim 語句,它的預設值是 Public 存取。

< Visual Basic.NET 新增而 VB6 沒有的部分 >
以下介紹純粹是 Visual Basic.NET 新增的功能,在 Visual Basic 以往的版本沒有這些功能。內容有

Assemblies

Assembly 組成.NET 平台的應用程式。它是在 .NET 執行環境上安裝、散佈應用程式的單元,並以 .exe 或是動態連結程式庫(.dll)的方式存在。你在 Visual Basic.NET 中使用 assemblies 的內容,並加入對 assemblies 的參照,就好像在之前版本的 VB 中使用 Type library 一樣。讓 assembly 與之前的 .exe 或 .dll 檔案不同的是它會包含所有執行程式的資訊,包括 type library 以及程式會用到的相關元件等等。
在 assembly 之內包含一份 assembly manifest,有點像一個 assembly 內所有內容的清單列表,包含的資訊如下:assembly 的身分,如他的名稱、版本等等用來描述組成 assembly 的所有檔案列表,包含你為你某個 assembly(.exe 或 .dll) 建立的其他 assembly,以及圖形(bitmap) 或 Readme 檔案等等。
一個 assembly 的參照(reference),也就是所有相依的外部檔案列表 -- DLLs 或是其他你的應用程式需要的檔案,但不是你建立的。Assembly 參照同時包含了對公共的(global)或是私有的(private)物件的參照。公共的物件存在公共的 assembly 快取中,也就是其他的應用程式也可以存取得到的地方,有點像 System32 目錄。Microsoft.VisualBasic 就是一個會存在於公共 assembly 快取區的範例。私有的物件必須存在於你應用程式安裝相同的目錄或是它的子目錄。
因為 Assemblies 自我描述的資訊已經足夠,所以利用 Visaul Basic.NET 所做的應用程式不再需要 Regisry 的資訊。這可以減少 DLL 的衝突,讓你的應用程式在安裝執行時更為穩定。在大多數的狀況下,安裝 .NET 應用程式只要複製檔案到目標電腦內就可以了。

■ 使用 Assemblies --
要使用 Assemblies 你必須要先加入對它的參照,接著利用 Imports 語法來選擇要使用的物件的命名空間(namespace)。一但參照與 imports 完成,你的應用程式就可以使用同一命名空間之下所有的 classes、屬性、方法以及其他成員。一個單一的 assembly 可以包含多個命名空間,以及每一個命名空間之下都可以有多個項目。
在 Visual Studio.NET 之內,你只要編譯應用程式就可以建立 assembly。利用 Visual Basic.NET 你同時可以建製 .exe 或 .dll 檔案,並讓支援共通語言定義(Common Language Specification)的其他語言使用。

新的簡易設定(assignment)語法


Visual Basic.NET 提供新的簡易設定語法,當程式碼撰寫如下時
  MyVal = 10
MyVal += 10

這時 MyVal 的值為 20 ,程式碼等於
  MyVal = MyVal + 10

繼承(Inheritance)


Visual Basic.NET 現今是一個完整的物件導向(Object Orient)語言,也就是完整支援封裝、繼承、多型等等程式撰寫的方式,並提供建構/解構子。所以它增加了以往 Visual Basic 在這一方面所缺乏的功能,如繼承。
Visual Basic.NET 增加的繼承讓你可以其他的 Class 為基礎來建立新的 Class,衍生的(Derived) Class 可以繼承(inherit)和延伸原始 Class 的屬性和方法。雖然衍生的 Class 只能繼承自單一的基礎(base) Class,但它可以實做(implement)無限數目的介面(interface)。衍生的 Class 也可以撰寫新的方法 override 繼承下來的方法,所有 Visual Basic.NET 建立的 Class 預設都是可以被繼承的。
繼承和介面讓你可以實做多型(polymorphism),讓不同的 class 可以重新定義相同名稱的屬性和方法。多型是物件導向(object-orient)程式設計的基礎,因為它讓你可以呼叫相同名稱的方法,而不管在使用的當下是哪一種型態的物件。例如給一個 NormalPayroll 的基礎 class,多型讓程式設計師可以定義多個衍生 Class 不同的 PayEmployee 方法。其他的屬性和方法可以相同的方式使用衍生物件的 PayEmployee 方法,而不管是用到哪一個衍生的物件。

Visual Basic.NET 提供下列的語句來定義和支援繼承

  • Inherits 語句 -- 定義當下 Class 要繼承的 Class(也稱為基礎 class)。通常可以透過屬性視窗(properties window)來設定。
  • NotInheritable 定義子 -- 防止使用者將該 Class 當作基礎 class 來使用
  • MustInherit 定義子 -- 定義該 class 不能用來建立 instance,唯一使用它的方式是繼承它。

衍生的 class 可以新的實做方式 override 一些繼承來的方法。Visual Basic.NET 使用下列的定義子來控制方法和屬性的 overriding

  • Overridable -- 讓屬性和方法在繼承的 Class 可以 override
  • Overrides -- 定義要 override 哪一個基礎的屬性或方法
  • NotOverridable(預設) -- 防止屬性或方法被繼承的 class override
  • MustOverride -- 要求繼承的 class 一定要 override 該屬性或方法

程式碼範例如下

Imports System

Namespace MyNamespace
Class Payroll
Overridable Function PayEmployee(ByVal HoursWorked As Single, ByVal PayRate As Single)
PayEmployee = HoursWorked * PayRate
End Function
End Class
Class BonusPayroll
Inherits Payroll
Overrides Function PayEmployee(ByVal HoursWorked As Single, ByVal PayRate As Single)
PayEmployee = (HoursWorked * PayRate) * 1.45 ' 45% 紅利
End Function
End Class
Module
MyModule Sub Main()
Dim BonusPayrollItem As Bonuspayroll = New Bonuspayroll
Dim PayrollItem As Payroll = New Payroll
Dim PayRate As Single = 14.75
Dim HoursWorked As Single = 40


Console.WriteLine("Normal pay is: " & Pay(PayrollItem, HoursWorked, PayRate))
Console.WriteLine("Pay with bonus is: " & Pay(BonusPayrollItem, HoursWorked, PayRate))


End Sub
Function Pay(ByVal PayObject As Payroll, ByVal HoursWorked As Single, ByVal
PayRate As Single) Pay = PayObject.PayEmployee(HoursWorked, PayRate) * 0.75 ' withold tax
End Function
End Module
End Namespace
執行結果如下
在程式碼中你可以看到Class BonusPayroll 繼承自 Payroll,並 Override PayEmployee 函數。所以當 Pay 函數傳入不同的 Class 物件但呼叫相同的方法時,可以有不同的結果。所以以上的程式範例也同時展現了多型的使用方式。

Free Threading


在以往版本的 VB 中,開發者除非藉由 Win 32 API 否則無法開發多 threading 的應用程式,所以只能做同步的動作,也就是程式碼必須要循序地執行。Visual Basic.NET 讓你可以撰寫 multiple task 的應用程式。每一個 Task 可以在一條 thread 內執行,程序(process)可以是 free thread。程式碼範例如下
Imports System.Threading

...

Private iVal, iary(1) As Integer

...

Protected Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim t As New Thread(AddressOf AddVal)
t.Start()
End Sub
Sub AddVal()
SyncLock iary
Dim i As Integer
For i = 1 To 1000000
ival += 1
Next textbox1.text = CStr(ival)
end synclock
End Sub

建構子(Constructor)和解構子(Destructor)

建構子是用來控制 Class 在建立一個新的 Instance 時所需要做的初始化程序。同樣地,解構子是當 Class 的 Instance 結束時(可能是設定變數為 Nothing)所需要做的程序,如釋放系統資源等等。 在原有的 VB 中,可以利用 Class_Initialize() Class_Terminate() 事件程序(Event Procedure)來初始化或在 Instance 結束時執行。


在 Visual Basic.NET 中改以 Sub New() Sub Destruct() 程序來做建構子與解構子的動作。


Sub New()
方法只會在 Class 建立時執行一次,且除了以同一 Class 其他的建構子或是衍生 Class 的建構子的第一行程式碼呼叫之外,不可以在任何其他地方呼叫使用。且 Sub New() 方法之內的程式碼在執行時,沒有任何該 Class 內的成員已經被建立出來。 .NET 執行環境會在清掉物件時自動呼叫它的 Sub Destruct() 方法,這一個副函數也不可以被呼叫使用。


.NET 執行環境在系統決定該物件不需要存在時﹔會自動清掉該物件。與 Class_Terminate Sub New() 不同的是,你無法確切地知道 .NET 執行環境會何時呼叫 Sub Destruct(),只能確定的是對該物件最後的一個參照(reference)被釋放後,系統會自動呼叫 Sub Destruct()

■ 使用 Sub New() 來建立參數化的建構子 --
要建立一個 class 的建構子可以在 class 定義範圍內的任何地方撰寫 Sub New() 副函數。建構子的第一行程式碼必須是呼叫基礎 Class 的建構子或是同一 Class 內其他的建構子,這保證被繼承的物件會在衍生的物件之前初始化完成。甚至是當你在建立你自己的基礎 class 時,也通常會呼叫 MyBase.New 來初始化更基礎的物件,因為所有的 Class 最終都是繼承自 Object Class。


在呼叫完父物件的建構子後,在 Sub New() 副函數內加上初始化該物件所需要的動作。Sub New() 副函數可以接受參數,如下列的程式碼範例。

■ 使用 Sub Destruct() 當解構子 --
要建立 class 的解構子,可以在 class 的定義範圍內任何地方加上 Sub Destruct() 副函數。你可以在該副函數內加上釋放物件,關閉檔案等等工作。
程式碼範例如下

Imports System


Namespace MyNamespace


Module MyModule
Class Truck
Private iWheels As Integer
Sub New(iInitialWheels As Integer)
MyBase.New '呼叫基礎 Class 的建構子
iWheels=iInitialWheels
End Sub
Sub Destruct()
'執行一些需要的動作
End Sub
Property Wheels As Integer
Get
Wheels=iWheels
End Get
Set
iWheels = Value
End Set
End Property
End Class


Sub Main()
Dim t as Truck = New Truck(18)
Console.WriteLine("這是一部 {0} 輪大卡車",t.Wheels)
Console.Write("請輸入您想要改裝的輪數: ")
t.Wheels = CInt(Console.ReadLine())
Console.WriteLine("你已經把它改裝成 " & t.Wheels & " 輪大卡車")
End Sub


End Module


End Namespace

執行結果如下

Delegate

Delegates 的行為像一個 type-safe,物件導向式函數指標(object-oriented function pointer)。利用 delegate 傳遞用 VB 的 addressof 運算子取得 class 副函數的指標給其他的 VB class 副函數。通常會利用 Delegate 來做事件處理函數,也就是當事件發生時要自動執行的函數。


每一個 delegate class 定義一個建構子,使用時會傳入一個物件方法的指標當作是參數。參數的格式如下
  AddressOf [.]<方法名稱>

在編譯時期, 必須是一個 class 或是一個介面(interface),並且有著一個符合 delegate class 所宣告的方法格式的方法。
這個方法可以是共享的(class)方法或是 instance 方法。 要觸發 delegate class 的 instance 內的方法必須要呼叫內建的 Invoke 方法,整個程式碼範例如下

Imports System
Namespace TestDelegate
Public Delegate Sub EventHandler(ByVal strText As String)


Class EventTake
Sub GetEvent(ByVal strIn As String)
console.WriteLine("Delegate 事件 " & strIn)
End Sub
End Class


Class EventRaise
Sub UseInvoke()
Dim c As New EventTake()
Dim DelSub As EventHandler
delsub = New EventHandler(AddressOf c.getevent)
delsub.Invoke("一些狀況")
End Sub
End Class


Module Module1


Shared Sub Main()
Dim c2 As New EventRaise()
c2.UseInvoke()
End Sub


End Module


End Namespace
執行結果如下