VB 编程中类型设计的规范形式与最佳实践
在 VB 编程中,设计新类型时遵循一定的规范形式能帮助开发者创建出更健壮、高效且易于维护的软件。本文将详细介绍 VB 中引用类型和值类型的规范形式,以及设计新类型时的检查清单。
1. 引用类型的规范形式
1.1 默认使用 NotInheritable 类
创建新类时,应默认将其标记为 NotInheritable,仅在设计明确需要作为基类使用时才移除该关键字。因为若不采取特定设计措施支持继承,很难预测类的使用方式。例如,没有 Overridable 方法的类通常不适合被继承。即使类有 Overridable 方法,作为基类使用时也可能产生问题,如重写方法时调用基类版本的时机和顺序难以确定。因此,除非有充分理由,否则应优先选择密封类,并为可继承的类提供详细文档。
1.2 使用 NVI 模式
设计可作为基类的类时,常声明 Overridable 方法让派生类修改行为。但这种设计在需要对方法进行预处理或后处理时可能导致兼容性问题。NVI(Non - Virtual Interface)模式可解决此问题,它将公共接口设为不可重写,将可重写行为移至受保护的方法中。示例代码如下:
Imports System
Public Class Base
Public Sub DoWork()
CoreDoWork()
End Sub
Protected Overridable Sub CoreDoWork()
Console.WriteLine("Base.DoWork()")
End Sub
End Class
Public Class Derived
Inherits Base
Protected Overrides Sub CoreDoWork()
Console.WriteLine("Derived.DoWork()")
End Sub
End Class
Public Class EntryPoint
Shared Sub Main()
Dim b As Base = New Derived()
b.DoWork()
End Sub
End Class
此模式使设计更健壮,修改基类方法时无需强制派生类修改。
1.3 对象是否可克隆
VB 和 CLR 中,对象通过引用访问,赋值操作通常不复制对象。若需复制对象,可实现 ICloneable 接口。但该接口未明确规定返回的是深拷贝还是浅拷贝,这需类设计者决定。
-
浅拷贝与深拷贝
:对象包含其他对象引用时,浅拷贝仅复制引用,深拷贝则复制所有包含的对象。
-
实现方式
:对象仅含值类型时,可使用 Object.MemberwiseClone() 创建浅拷贝,但该方法不调用构造函数,若对象依赖构造函数工作,需将工作提取到单独方法。也可使用私有拷贝构造函数实现克隆,这种方式更安全,能完全控制复制过程。示例代码如下:
Imports System
Public NotInheritable Class Dimensions
Implements ICloneable
Private width As Long
Private height As Long
Public Sub New(ByVal width As Long, ByVal height As Long)
Me.width = width
Me.height = height
End Sub
'ICloneable implementation
Public Function Clone() As Object Implements ICloneable.Clone
Return New Dimensions(Me)
End Function
Private Sub New(ByVal other As Dimensions)
Me.width = other.width
Me.height = other.height
End Sub
End Class
- 注意事项 :Microsoft 建议避免实现 ICloneable,因接口未明确拷贝类型。若类需要克隆方法,可在公共契约中实现,而不实际实现 ICloneable。为明确克隆方式,可使用自定义属性标记 Clone 方法。
1.4 对象是否可处置
若对象管理非托管资源或包含可处置的其他对象,则应实现 IDisposable 接口。实现该接口时,需注意以下几点:
-
调用包含对象的 Dispose 方法
:若对象包含其他可处置对象,在 Dispose 方法中调用它们的 Dispose 方法。
-
处理多次调用
:允许客户端多次调用 Dispose 方法,后续调用应不做任何操作。可使用内部标志避免重复操作,并在调用已处置对象的方法时抛出 ObjectDisposedException。
示例代码如下:
Imports System
Imports System.Runtime.InteropServices
Public NotInheritable Class Win32Heap
Implements IDisposable
Private theHeap As IntPtr
Private disposed As Boolean = False
<DllImport("kernel32.dll")> _
Shared Function HeapCreate(ByVal flOptions As UInteger, _
ByVal dwInitialSize As UIntPtr, ByVal dwMaximumSize As UIntPtr) As IntPtr
End Function
<DllImport("kernel32.dll")> _
Shared Function HeapDestroy(ByVal hHeap As IntPtr) As Boolean
End Function
Public Sub New()
theHeap = HeapCreate(0, CType(4096, UIntPtr), UIntPtr.Zero)
End Sub
'IDisposable implementation
Public Sub Dispose() Implements IDisposable.Dispose
If (Not disposed) Then
HeapDestroy(theHeap)
theHeap = IntPtr.Zero
disposed = True
End If
End Sub
End Class
为避免资源泄漏,可实现终结器(Finalizer)。终结器在垃圾回收器清理对象前调用,但它与析构函数不同,是不确定的销毁方式。实现终结器时,需注意线程问题,避免使用对象中的引用对象,因对象的终结顺序无保证。可在 Dispose 方法中调用 GC.SuppressFinalize() 避免对象进入终结队列,提高垃圾回收效率。
1.5 对象的相等性含义
Object.Equals() 方法用于确定两个对象是否相等,但在 VB 中,引用类型和值类型的默认相等性语义不同。引用类型默认进行身份比较,即两个引用指向同一对象时认为相等;值类型使用值相等性,即对象的字段值相等时认为相等。
-
引用类型的身份相等性
:比较引用变量时,默认进行身份比较。但客户端代码比较对象引用时需小心,避免空引用异常。示例代码如下:
Public Class EntryPoint
Private Shared Function TestForEquality(ByVal obj1 As Object, _
ByVal obj2 As Object) As Boolean
If obj1 Is Nothing AndAlso obj2 Is Nothing Then
Return True
End If
If obj1 Is Nothing Then
Return False
End If
Return obj1.Equals(obj2)
End Function
Shared Sub Main()
Dim obj1 As Object = New System.Object()
Dim obj2 As Object = Nothing
System.Console.WriteLine("obj1 Equals obj2 is {0}", _
TestForEquality(obj1, obj2))
End Sub
End Class
- 值相等性 :对于某些类型,如 ComplexNumber,按字段比较更符合直觉。此时需重写 Object.Equals() 方法,并遵循一定规则,如自反性、对称性、传递性等。示例代码如下:
Public Class ComplexNumber
Private real As Double
Private imaginary As Double
Public Sub New(ByVal real As Integer, ByVal imaginary As Integer)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim other As ComplexNumber = TryCast(obj, ComplexNumber)
If other Is Nothing Then
Return False
End If
Return (Me.real = other.real) AndAlso (Me.imaginary = other.imaginary)
End Function
Public Overrides Function GetHashCode() As Integer
Return Fix(real) Xor Fix(imaginary)
End Function
Public Shared Operator =(ByVal CN As ComplexNumber, _
ByVal other As ComplexNumber) As Boolean
Return Equals(CN, other)
End Operator
Public Shared Operator <>(ByVal CN As ComplexNumber, _
ByVal other As ComplexNumber) As Boolean
Return Equals(CN, other)
End Operator
End Class
Public Class EntryPoint
Shared Sub Main()
Dim referenceA As ComplexNumber = New ComplexNumber(1, 2)
Dim referenceB As ComplexNumber = New ComplexNumber(1, 2)
System.Console.WriteLine("Result of Equality is {0}", _
referenceA = referenceB)
'If we really want referential equality.
System.Console.WriteLine("Identity of references is {0}", _
ReferenceEquals(referenceA, referenceB))
End Sub
End Class
重写 Equals() 方法时,还需重写 GetHashCode() 方法,因对象作为哈希表键时会调用 GetHashCode() 方法确定存储桶。GetHashCode() 方法的实现需满足一定规则,如相等对象的哈希码相等。可根据哈希码优化相等运算符的实现,但需注意性能影响。
1.6 对象是否支持排序
设计可存储在集合中的类且集合需排序时,需实现 IComparable 接口。IComparable 接口包含 CompareTo 方法,该方法返回正值、负值或零,分别表示大于、等于或小于。实现 CompareTo 方法时需注意:返回值仅表示符号,不表示不等程度;无法比较两个对象时抛出 ArgumentException;IComparable 接口语义是 Object.Equals() 的超集,派生类需确保 Equals() 和 CompareTo() 方法的实现一致。示例代码如下:
Imports System
Public NotInheritable Class ComplexNumber
Implements IComparable
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overloads Overrides Function Equals(ByVal other As Object) As Boolean
Dim result As Boolean = False
Dim that As ComplexNumber = TryCast(other, ComplexNumber)
If Not that Is Nothing Then
result = InternalEquals(that)
End If
Return result
End Function
Public Overrides Function GetHashCode() As Integer
Return Fix(Me.Magnitude)
End Function
Public Shared Operator =(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return Object.Equals(num1, num2)
End Operator
Public Shared Operator <>(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return Not Object.Equals(num1, num2)
End Operator
Public Function CompareTo(ByVal other As Object) As Integer _
Implements IComparable.CompareTo
Dim that As ComplexNumber = TryCast(other, ComplexNumber)
If that Is Nothing Then
Throw New ArgumentException("Bad Comparison!")
End If
Dim result As Integer
If InternalEquals(that) Then
result = 0
ElseIf Me.Magnitude > that.Magnitude Then
result = 1
Else
result = -1
End If
Return result
End Function
Private Function InternalEquals(ByVal that As ComplexNumber) As Boolean
Return (Me.real = that.real) AndAlso (Me.imaginary = that.imaginary)
End Function
Public ReadOnly Property Magnitude() As Double
Get
Return Math.Sqrt(Math.Pow(Me.real, 2) + Math.Pow(Me.imaginary, 2))
End Get
End Property
End Class
1.7 对象是否可格式化
创建新对象时,通常需重写 Object.ToString() 方法以提供有意义的字符串表示。但默认实现可能无法满足所有需求,如在不同文化环境中表示浮点数。为解决此问题,可实现 IFormattable 接口。该接口的 ToString 方法接受格式字符串和格式提供程序,可根据用户需求指定输出格式。示例代码如下:
Imports System
Imports System.Globalization
Public NotInheritable Class ComplexNumber
Implements IFormattable
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overrides Function ToString() As String
Return ToString("G", Nothing)
End Function
'IFormattable implementation
Public Overloads Function ToString(ByVal format As String, _
ByVal formatProvider As IFormatProvider) As String _
Implements IFormattable.ToString
Dim result As String = "(" & real.ToString(format, formatProvider) & _
" " & real.ToString(format, formatProvider) & ")"
Return result
End Function
End Class
Public NotInheritable Class EntryPoint
Shared Sub Main()
Dim num1 As ComplexNumber = New ComplexNumber(1.12345678, 2.12345678)
Console.WriteLine("US format: {0}", num1.ToString("F5", _
New CultureInfo("en - US")))
Console.WriteLine("DE format: {0}", num1.ToString("F5", _
New CultureInfo("de - DE")))
Console.WriteLine("Object.ToString(): {0}", num1.ToString())
End Sub
End Class
1.8 对象是否可转换
VB 支持通过类型转换转换简单内置值类型,但复杂转换需借助 System.Convert 类。该类提供多种转换方法,可处理大多数内置类型之间的转换。对于自定义类型的转换,可使用 Convert.ChangeType 方法,但需实现 IConvertible 接口。示例代码如下:
Imports System
Public NotInheritable Class ComplexNumber
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
' Other methods removed for clarity
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
End Class
Public NotInheritable Class EntryPoint
Shared Sub Main()
Dim num1 As ComplexNumber = New ComplexNumber(1.12345678, 2.12345678)
Dim str As String = CStr(Convert.ChangeType(num1, GetType(String)))
End Sub
End Class
此外,还可使用 System.ComponentModel.TypeConverter 进行类型转换,它可在设计时和运行时使用。
1.9 始终优先考虑类型安全
在 VB 中,使用 System.Object 引用泛型处理所有对象效率较低,尤其是值类型会产生不必要的装箱操作。使用强类型可在编译时捕获错误,提高代码效率。例如,在 For Each 语句中,若枚举器的 Current 属性类型为 System.Object,编译器需进行类型转换;若将其类型指定为具体类型,可生成更高效的代码。示例代码如下:
Imports System
Imports System.Collections
Public Class Employee
Public Sub Evaluate()
Console.WriteLine("Evaluating Employee...")
End Sub
End Class
Public Class WorkForceEnumerator
Implements IEnumerator
Private enumerator As IEnumerator
Public Sub New(ByVal employees As ArrayList)
Me.enumerator = employees.GetEnumerator()
End Sub
Public ReadOnly Property Current() As Employee
Get
Return CType(enumerator.Current, Employee)
End Get
End Property
Private ReadOnly Property IEnumerator_Current() As Object _
Implements IEnumerator.Current
Get
Return enumerator.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Return enumerator.MoveNext()
End Function
Public Sub Reset() Implements IEnumerator.Reset
enumerator.Reset()
End Sub
End Class
Public Class WorkForce
Implements IEnumerable
Private employees As ArrayList
Public Sub New()
employees = New ArrayList()
'Let's put an employee in here for demo purposes.
employees.Add(New Employee())
End Sub
Public Overloads Function GetEnumerator() As WorkForceEnumerator
Return New WorkForceEnumerator(employees)
End Function
Private Overloads Function IEnumerable_GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return New WorkForceEnumerator(employees)
End Function
End Class
Public Class EntryPoint
Shared Sub Main()
Dim staff As WorkForce = New WorkForce()
For Each emp As Employee In staff
emp.Evaluate()
Next emp
End Sub
End Class
1.10 使用不可变引用类型
设计良好的契约或接口时,需考虑类型的可变性。对于引用类型,若要保证方法调用期间对象不被修改,对象本身应是不可变的。例如,System.String 类是不可变的,若为可变类型,可能导致意外结果。对于可变的引用类型,可通过引入不可变的包装类或使用接口来保证对象在方法调用期间不被修改。示例代码如下:
Imports System
Public NotInheritable Class ComplexNumber
Private mReal As Double
Private mImaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.mReal = real
Me.mImaginary = imaginary
End Sub
Public Property Real() As Double
Get
Return mReal
End Get
Set(ByVal value As Double)
mReal = value
End Set
End Property
Public Property Imaginary() As Double
Get
Return mImaginary
End Get
Set(ByVal value As Double)
mImaginary = value
End Set
End Property
'Other methods removed for clarity
End Class
Public NotInheritable Class ConstComplexNumber
Private ReadOnly pimpl As ComplexNumber
Public Sub New(ByVal pimpl As ComplexNumber)
Me.pimpl = pimpl
End Sub
Public ReadOnly Property Real() As Double
Get
Return pimpl.Real
End Get
End Property
Public ReadOnly Property Imaginary() As Double
Get
Return pimpl.Imaginary
End Get
End Property
End Class
Public NotInheritable Class EntryPoint
Shared Sub Main()
Dim someNumber As ComplexNumber = New ComplexNumber(1, 2)
SomeMethod(New ConstComplexNumber(someNumber))
'We are guaranteed by the contract of ConstComplexNumber that
'someNumber has not been changed at this point.
End Sub
Private Shared Sub SomeMethod(ByVal number As ConstComplexNumber)
Console.WriteLine("( {0}, {1} )", number.Real, number.Imaginary)
End Sub
End Class
2. 值类型的规范形式
2.1 值类型与引用类型规范形式的异同
值类型的规范形式与引用类型有部分相同之处,如都可考虑实现 IComparable、IFormattable 和 IConvertible 接口。但也有明显差异,如值类型实现 ICloneable 意义不大,因返回的通常是装箱后的副本;值类型一般不需要终结器,也无需实现 IDisposable 接口,除非包含可处置对象或管理稀缺系统资源。
2.2 为提高性能重写 Equals()
引用类型默认使用身份比较,值类型使用值相等性。但 System.ValueType 的 Equals() 方法使用反射,效率较低。因此,应自定义重写 Equals() 方法。例如,将 ComplexNumber 类型改为结构并实现自定义 Equals() 方法。示例代码如下:
Imports System
Public Structure ComplexNumber
Implements IComparable
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overloads Overrides Function Equals(ByVal other As Object) As Boolean
Dim result As Boolean = False
If TypeOf other Is ComplexNumber Then
Dim that As ComplexNumber = CType(other, ComplexNumber)
result = InternalEquals(that)
End If
Return result
End Function
Public Overrides Function GetHashCode() As Integer
Return CInt(Fix(Me.Magnitude))
End Function
Public Shared Operator =(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return num1.Equals(num2)
End Operator
Public Shared Operator <>(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return Not num1.Equals(num2)
End Operator
Public Function CompareTo(ByVal other As Object) As Integer _
Implements IComparable.CompareTo
If Not (TypeOf other Is ComplexNumber) Then
Throw New ArgumentException("Bad Comparison!")
End If
Dim that As ComplexNumber = CType(other, ComplexNumber)
Dim result As Integer
If InternalEquals(that) Then
result = 0
ElseIf Me.Magnitude > that.Magnitude Then
result = 1
Else
result = -1
End If
Return result
End Function
Private Function InternalEquals(ByVal that As ComplexNumber) As Boolean
Return (Me.real = that.real) AndAlso (Me.imaginary = that.imaginary)
End Function
Public ReadOnly Property Magnitude() As Double
Get
Return Math.Sqrt(Math.Pow(Me.real, 2) + Math.Pow(Me.imaginary, 2))
End Get
End Property
'Other methods removed for clarity
End Structure
为避免装箱和拆箱操作,可定义两个 Equals() 方法的重载:一个类型安全的版本接受具体值类型作为参数,另一个重写 Object.Equals 方法。示例代码如下:
Imports System
Public Structure ComplexNumber
Implements IComparable
Implements IComparable(Of ComplexNumber)
Implements IEquatable(Of ComplexNumber)
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overloads Function Equals(ByVal other As ComplexNumber) As Boolean
Return (Me.real = other.real) AndAlso (Me.imaginary = other.imaginary)
End Function
Public Overloads Overrides Function Equals(ByVal other As Object) As Boolean
Dim result As Boolean = False
If TypeOf other Is ComplexNumber Then
Dim that As ComplexNumber = CType(other, ComplexNumber)
result = Equals(that)
End If
Return result
End Function
Public Function IEquatableOfT_Equals(ByVal other As ComplexNumber) As Boolean _
Implements System.IEquatable(Of ComplexNumber).Equals
End Function
Public Overrides Function GetHashCode() As Integer
Return CInt(Fix(Me.Magnitude))
End Function
Public Shared Operator =(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return num1.Equals(num2)
End Operator
Public Shared Operator <>(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return Not num1.Equals(num2)
End Operator
Public Function CompareTo(ByVal other As Object) As Integer _
Implements IComparable.CompareTo
If Not (TypeOf other Is ComplexNumber) Then
Throw New ArgumentException("Bad Comparison!")
End If
Return CompareTo(CType(other, ComplexNumber))
End Function
Public Function CompareTo(ByVal that As ComplexNumber) As Integer
Dim result As Integer
If Equals(that) Then
result = 0
ElseIf Me.Magnitude > that.Magnitude Then
result = 1
Else
result = -1
End If
Return result
End Function
Public Function IComparableOfT_CompareTo(ByVal other As ComplexNumber) _
As Integer Implements System.IComparable(Of ComplexNumber).CompareTo
End Function
Public ReadOnly Property Magnitude() As Double
Get
Return Math.Sqrt(Math.Pow(Me.real, 2) + Math.Pow(Me.imaginary, 2))
End Get
End Property
'Other methods removed for clarity
End Structure
2.3 值类型是否支持接口
值类型和引用类型在 CLR 中的行为差异可能导致混淆。将值类型转换为 Object 类型时,会发生装箱操作,即 CLR 创建一个新对象包含值类型的副本。将值类型实例转换为接口类型时,也需先进行装箱。接口是修改装箱值类型内部值的唯一方式,但装箱操作可能带来性能问题。例如,将 ComplexNumber 实例转换为 IComparable 接口类型时,会进行装箱操作。可通过实现类型安全的接口方法避免装箱,示例代码如下:
Imports System
Public Structure ComplexNumber
Implements IComparable
Implements IComparable(Of ComplexNumber)
Implements IEquatable(Of ComplexNumber)
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overloads Function Equals(ByVal other As ComplexNumber) As Boolean
Return (Me.real = other.real) AndAlso (Me.imaginary = other.imaginary)
End Function
Public Overloads Overrides Function Equals(ByVal other As Object) As Boolean
Dim result As Boolean = False
If TypeOf other Is ComplexNumber Then
Dim that As ComplexNumber = CType(other, ComplexNumber)
result = Equals(that)
End If
Return result
End Function
Public Function IEquatableOfT_Equals(ByVal other As ComplexNumber) As Boolean _
Implements System.IEquatable(Of ComplexNumber).Equals
End Function
Public Overrides Function GetHashCode() As Integer
Return CInt(Fix(Me.Magnitude))
End Function
Public Shared Operator =(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return num1.Equals(num2)
End Operator
Public Shared Operator <>(ByVal num1 As ComplexNumber, _
ByVal num2 As ComplexNumber) As Boolean
Return Not num1.Equals(num2)
End Operator
Public Function CompareTo(ByVal that As ComplexNumber) As Integer
Dim result As Integer
If Equals(that) Then
result = 0
ElseIf Me.Magnitude > that.Magnitude Then
result = 1
Else
result = -1
End If
Return result
End Function
Private Function CompareTo(ByVal other As Object) As Integer _
Implements IComparable.CompareTo
If Not (TypeOf other Is ComplexNumber) Then
Throw New ArgumentException("Bad Comparison!")
End If
Return CompareTo(CType(other, ComplexNumber))
End Function
Public Function IComparableOfT_CompareTo(ByVal other As ComplexNumber) _
As Integer Implements System.IComparable(Of ComplexNumber).CompareTo
End Function
Public ReadOnly Property Magnitude() As Double
Get
Return Math.Sqrt(Math.Pow(Me.real, 2) + Math.Pow(Me.imaginary, 2))
End Get
End Property
'Other methods removed for clarity
End Structure
Public NotInheritable Class EntryPoint
Shared Sub Main()
Dim num1 As ComplexNumber = New ComplexNumber(1, 3)
Dim num2 As ComplexNumber = New ComplexNumber(1, 2)
Dim result As Integer = num1.CompareTo(num2)
'Now, try the type - generic version
result = (CType(num1, IComparable)).CompareTo(num2)
End Sub
End Class
3. 设计检查清单
3.1 引用类型检查清单
| 检查项 | 说明 |
|---|---|
| 类是否应解封 | 类默认应声明为 NotInheritable,除非明确作为基类使用,且需提供详细使用文档 |
| 对象是否可克隆 | 实现 ICloneable 接口,可变对象默认进行深拷贝,不可变对象可考虑浅拷贝优化;避免使用 MemberwiseClone() |
| 对象是否可处置 | 实现 IDisposable 接口;实现终结器捕获未处置对象或警告用户;在 Dispose 方法中调用 GC.SuppressFinalize() |
| 对象的相等性检查是否应携带值语义 | 重写 Object.Equals() 方法,提供充分理由;重写 GetHashCode() 方法 |
| 对象是否可比较 | 实现 IComparable 接口,重写 Equals() 和 GetHashCode() 方法 |
| 对象是否可转换为 System.String 或反之 | 重写 Object.ToString() 方法;若用户需要更精细的字符串格式化,实现 IFormattable 接口 |
| 对象是否可转换 | 重写 IConvertible 接口,使类与 System.Convert 类协同工作 |
| 对象是否应不可变 | 考虑将字段设为只读,仅提供只读属性 |
| 是否需要将对象作为常量不可变方法参数传递 | 考虑实现包含可变对象引用的不可变包装类,或使用接口实现相同效果 |
3.2 值类型检查清单
| 检查项 | 说明 |
|---|---|
| 是否期望值类型更高效 | 重写 Equals() 和 GetHashCode() 方法,提供类型安全版本的 Equals() 方法;为接受或返回 System.Object 类型参数的方法提供具体值类型的重载 |
| 是否需要修改装箱值实例 | 实现接口以修改装箱值类型的内部值 |
| 值类型是否可比较 | 实现 IComparable 接口,重写 Equals() 和 GetHashCode() 方法,提供类型安全版本的 Equals() 方法 |
| 值是否可转换为 System.String 或反之 | 重写 ValueType.ToString() 方法;若用户需要更精细的字符串格式化,实现 IFormattable 接口 |
| 值是否可转换 | 重写 IConvertible 接口,使结构与 System.Convert 类协同工作 |
| 结构是否应不可变 | 考虑将字段设为只读,仅提供只读属性 |
在 VB 编程中,设计新类型时遵循这些规范形式和检查清单,能帮助开发者创建出更健壮、高效且易于维护的软件。通过合理运用引用类型和值类型的特性,以及遵循类型安全和性能优化的原则,可提升代码质量和开发效率。
4. 规范形式的流程总结
4.1 引用类型设计流程
graph LR
A[开始设计引用类型] --> B{类是否作为基类}
B -- 否 --> C[声明为 NotInheritable]
B -- 是 --> D[详细文档说明使用方式]
C --> E{对象是否可克隆}
D --> E
E -- 是 --> F[实现 ICloneable,考虑深拷贝]
E -- 否 --> G{对象是否可处置}
F --> G
G -- 是 --> H[实现 IDisposable 和终结器]
G -- 否 --> I{相等性检查是否用值语义}
H --> I
I -- 是 --> J[重写 Object.Equals() 和 GetHashCode()]
I -- 否 --> K{对象是否可排序}
J --> K
K -- 是 --> L[实现 IComparable]
K -- 否 --> M{对象是否可格式化}
L --> M
M -- 是 --> N[实现 IFormattable]
M -- 否 --> O{对象是否可转换}
N --> O
O -- 是 --> P[重写 IConvertible]
O -- 否 --> Q{是否需不可变对象}
P --> Q
Q -- 是 --> R[设计不可变对象或包装类]
Q -- 否 --> S[结束设计]
R --> S
4.2 值类型设计流程
graph LR
A[开始设计值类型] --> B{是否需更高效率}
B -- 是 --> C[重写 Equals() 和 GetHashCode()]
B -- 否 --> D{是否需修改装箱值}
C --> D
D -- 是 --> E[实现接口修改装箱值]
D -- 否 --> F{值是否可比较}
E --> F
F -- 是 --> G[实现 IComparable,重写相关方法]
F -- 否 --> H{值是否可转换为字符串}
G --> H
H -- 是 --> I[重写 ToString(),考虑 IFormattable]
H -- 否 --> J{值是否可转换}
I --> J
J -- 是 --> K[重写 IConvertible]
J -- 否 --> L{结构是否应不可变}
K --> L
L -- 是 --> M[设计不可变结构]
L -- 否 --> N[结束设计]
M --> N
5. 关键技术点分析
5.1 克隆技术分析
在实现对象克隆时,对于不同类型的对象有不同的处理方式。对于仅包含值类型的对象,使用 MemberwiseClone() 可以快速创建浅拷贝,但由于不调用构造函数,可能会导致对象初始化不完整。例如:
Imports System
Public NotInheritable Class SimpleValue
Implements ICloneable
Private value As Integer
Public Sub New(ByVal val As Integer)
Me.value = val
End Sub
Public Function Clone() As Object Implements ICloneable.Clone
Return Me.MemberwiseClone()
End Function
End Class
若对象依赖构造函数进行初始化,如记录日志等操作,使用 MemberwiseClone() 就不合适,应使用私有拷贝构造函数:
Imports System
Public NotInheritable Class LoggedValue
Implements ICloneable
Private value As Integer
Public Sub New(ByVal val As Integer)
Console.WriteLine("LoggedValue 构造函数调用")
Me.value = val
End Sub
Private Sub New(ByVal other As LoggedValue)
Console.WriteLine("LoggedValue 拷贝构造函数调用")
Me.value = other.value
End Sub
Public Function Clone() As Object Implements ICloneable.Clone
Return New LoggedValue(Me)
End Function
End Class
5.2 相等性和哈希码技术分析
重写 Equals() 和 GetHashCode() 方法时,需要遵循严格的规则。例如,对于 ComplexNumber 类:
Public Class ComplexNumber
Private real As Double
Private imaginary As Double
Public Sub New(ByVal real As Integer, ByVal imaginary As Integer)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim other As ComplexNumber = TryCast(obj, ComplexNumber)
If other Is Nothing Then
Return False
End If
Return (Me.real = other.real) AndAlso (Me.imaginary = other.imaginary)
End Function
Public Overrides Function GetHashCode() As Integer
Return Fix(real) Xor Fix(imaginary)
End Function
Public Shared Operator =(ByVal CN As ComplexNumber, _
ByVal other As ComplexNumber) As Boolean
Return Equals(CN, other)
End Operator
Public Shared Operator <>(ByVal CN As ComplexNumber, _
ByVal other As ComplexNumber) As Boolean
Return Equals(CN, other)
End Operator
End Class
在这个例子中,Equals() 方法通过比较对象的实部和虚部来判断是否相等,GetHashCode() 方法根据实部和虚部计算哈希码。这样可以确保在使用哈希表等数据结构时,对象的相等性和哈希码的一致性。
5.3 接口实现技术分析
实现接口时,对于值类型和引用类型有不同的注意事项。对于值类型,如 ComplexNumber 实现 IComparable 接口:
Imports System
Public Structure ComplexNumber
Implements IComparable
Private ReadOnly real As Double
Private ReadOnly imaginary As Double
Public Sub New(ByVal real As Double, ByVal imaginary As Double)
Me.real = real
Me.imaginary = imaginary
End Sub
Public Function CompareTo(ByVal other As Object) As Integer _
Implements IComparable.CompareTo
Dim that As ComplexNumber = TryCast(other, ComplexNumber)
If that Is Nothing Then
Throw New ArgumentException("Bad Comparison!")
End If
Dim result As Integer
If (Me.real = that.real) AndAlso (Me.imaginary = that.imaginary) Then
result = 0
ElseIf Math.Sqrt(Math.Pow(Me.real, 2) + Math.Pow(Me.imaginary, 2)) > Math.Sqrt(Math.Pow(that.real, 2) + Math.Pow(that.imaginary, 2)) Then
result = 1
Else
result = -1
End If
Return result
End Function
End Structure
在实现过程中,需要注意参数类型的转换和异常处理。同时,为了避免装箱操作,可使用泛型接口 IComparable(Of T) 提供类型安全的版本。
6. 总结
在 VB 编程中,设计新类型时遵循规范形式和检查清单至关重要。引用类型和值类型各有其特点和适用场景,通过合理运用克隆、相等性判断、接口实现等技术,可以创建出更健壮、高效且易于维护的软件。
引用类型在设计时,要谨慎考虑继承性,合理处理对象的克隆、处置、相等性和排序等问题。值类型则要注重性能优化,避免不必要的装箱操作,同时合理实现接口以满足特定需求。
在实际开发中,开发者应根据具体的业务需求和场景,灵活运用这些规范和技术,不断提升代码质量和开发效率。通过遵循类型安全和性能优化的原则,能够有效减少错误,提高软件的稳定性和可维护性。
超级会员免费看
957

被折叠的 条评论
为什么被折叠?



