patternMinor
Dictionary<TKey, TValue> Implementation
Viewed 0 times
dictionaryimplementationtkeytvalue
Problem
This class encapsulates a
The class enforces some "type safety", in the sense that if you add a
The
As with the
```
Private Type tDictionary
Encapsulated As List
TKey As String
IsRefTypeKey As Boolean
TValue As String
IsRefTypeValue As Boolean
End Type
Private Enum DictionaryErrors
TypeMismatchUnsafeType = vbObjectError + 1001
End Enum
Private this As tDictionary
Option Explicit
Private Sub Class_Initialize()
Set this.Encapsulated = New List
this.Encapsulated.OptionStrict = True
End Sub
Private Sub Class_Terminate()
Set this.Encapsulated = Nothing
End Sub
Public Property Get Count() As Long
Count = this.Encapsulated.Count
End Property
Public Property Get Keys() As List
Dim result As New List
Dim kvp As KeyValuePair
result.OptionStrict = this.Encapsulated.OptionStrict
For Each kvp In this.Encapsulated
result.Add kvp.Key
Next
Set Keys = result
End Property
Public Property Get Values() As List
Dim result As New List
Dim kvp As KeyValuePair
result.OptionStrict = this.Encapsulated.OptionStrict
For Each kvp In this.Encapsulated
result.Add kvp.value
Next
Set Value
List (see List implementation here, and KeyValuePair implementation here) and exposes a richer set of members than the typical Scripting.Dictionary, ..not to mention the anemic Collection class.The class enforces some "type safety", in the sense that if you add a
KeyValuePair, then you'll only be allowed to add KeyValuePair instances, the object will raise an error if you try adding, say, a KeyValuePair, or anything that's not a KeyValuePair.The
OptionStrict property enables allowing more flexibility and adding a KeyValuePair to a Dictionary, for example (but not the opposite).As with the
List implementation, this class uses procedure attributes (not shown) which make the Item property the default property (so myDictionary(i) returns the value at index i), and the NewEnum property enables iterating all values with a For Each loop construct.```
Private Type tDictionary
Encapsulated As List
TKey As String
IsRefTypeKey As Boolean
TValue As String
IsRefTypeValue As Boolean
End Type
Private Enum DictionaryErrors
TypeMismatchUnsafeType = vbObjectError + 1001
End Enum
Private this As tDictionary
Option Explicit
Private Sub Class_Initialize()
Set this.Encapsulated = New List
this.Encapsulated.OptionStrict = True
End Sub
Private Sub Class_Terminate()
Set this.Encapsulated = Nothing
End Sub
Public Property Get Count() As Long
Count = this.Encapsulated.Count
End Property
Public Property Get Keys() As List
Dim result As New List
Dim kvp As KeyValuePair
result.OptionStrict = this.Encapsulated.OptionStrict
For Each kvp In this.Encapsulated
result.Add kvp.Key
Next
Set Keys = result
End Property
Public Property Get Values() As List
Dim result As New List
Dim kvp As KeyValuePair
result.OptionStrict = this.Encapsulated.OptionStrict
For Each kvp In this.Encapsulated
result.Add kvp.value
Next
Set Value
Solution
Although
However exposing all these methods through your
And since that code was copy-pasted from an existing
That change would require the
```
Private Function ValidateItemType(kvp As KeyValuePair, Optional ThrowOnUnsafeType As Boolean = False) As Boolean
If this.TKey = vbNullString And this.TValue = vbNullString Then
this.TKey = TypeName(kvp.key)
this.IsRefTypeKey = IsObject(kvp.key)
this.TValue = TypeName(kvp.value)
this.IsRefTypeValue = IsObject(kvp.value)
this.KeyValidator.TValue = this.TKey '<<< here
this.ValueValidator.TValue = this.TValue '<<< here
End If
ValidateItemType = IsTypeSafe(kvp)
CallByName doesn't seem to have a return value (from the parameter tooltip), it does. This means if IsSafeKeyXxxxxx methods were Public you could use CallByName instead of the Select..Case block.However exposing all these methods through your
Dictionary interface would be rather ugly. How about extracting all these small methods into their own TypeValidator class?Private Type tValueTypeValidator
TValue As String
OptionStrict As Boolean
End Type
Private this As tValueTypeValidator
Option Explicit
Public Property Get TValue() As String
TValue = this.TValue
End Property
Public Property Let TValue(ByVal value As String)
this.TValue = value
End Property
Public Property Get OptionStrict() As Boolean
OptionStrict = this.OptionStrict
End Property
Public Property Let OptionStrict(ByVal value As Boolean)
this.OptionStrict = value
End Property
Public Function ToString() As String
ToString = TypeName(Me) & ""
End Function
Public Function IsSafeBoolean(value As Variant) As Boolean
On Error Resume Next
IsSafeBoolean = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeBoolean Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CBool(value)
IsSafeBoolean = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeByte(value As Variant) As Boolean
On Error Resume Next
IsSafeByte = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeByte Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CByte(value)
IsSafeByte = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeCurrency(value As Variant) As Boolean
On Error Resume Next
IsSafeCurrency = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeCurrency Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CCur(value)
IsSafeCurrency = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeDate(value As Variant) As Boolean
On Error Resume Next
IsSafeDate = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeDate Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CDate(value)
IsSafeDate = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeDouble(value As Variant) As Boolean
On Error Resume Next
IsSafeDouble = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeDouble Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CDbl(value)
IsSafeDouble = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeInteger(value As Variant) As Boolean
On Error Resume Next
IsSafeInteger = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeInteger Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CInt(value)
IsSafeInteger = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeLong(value As Variant) As Boolean
On Error Resume Next
IsSafeLong = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeLong Or OptionStrict Then Exit Function
Dim result As Boolean
result = CLng(value)
IsSafeLong = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeSingle(value As Variant) As Boolean
On Error Resume Next
IsSafeSingle = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeSingle Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CSng(value)
IsSafeSingle = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeString(value As Variant) As Boolean
On Error Resume Next
IsSafeString = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeString Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CStr(value)
IsSafeString = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End FunctionAnd since that code was copy-pasted from an existing
List class in the first place, there's already a case for reusing that TypeValidator class.That change would require the
ValidateItemType method to be modified as such:```
Private Function ValidateItemType(kvp As KeyValuePair, Optional ThrowOnUnsafeType As Boolean = False) As Boolean
If this.TKey = vbNullString And this.TValue = vbNullString Then
this.TKey = TypeName(kvp.key)
this.IsRefTypeKey = IsObject(kvp.key)
this.TValue = TypeName(kvp.value)
this.IsRefTypeValue = IsObject(kvp.value)
this.KeyValidator.TValue = this.TKey '<<< here
this.ValueValidator.TValue = this.TValue '<<< here
End If
ValidateItemType = IsTypeSafe(kvp)
Code Snippets
Private Type tValueTypeValidator
TValue As String
OptionStrict As Boolean
End Type
Private this As tValueTypeValidator
Option Explicit
Public Property Get TValue() As String
TValue = this.TValue
End Property
Public Property Let TValue(ByVal value As String)
this.TValue = value
End Property
Public Property Get OptionStrict() As Boolean
OptionStrict = this.OptionStrict
End Property
Public Property Let OptionStrict(ByVal value As Boolean)
this.OptionStrict = value
End Property
Public Function ToString() As String
ToString = TypeName(Me) & "<" & this.TValue & ">"
End Function
Public Function IsSafeBoolean(value As Variant) As Boolean
On Error Resume Next
IsSafeBoolean = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeBoolean Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CBool(value)
IsSafeBoolean = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeByte(value As Variant) As Boolean
On Error Resume Next
IsSafeByte = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeByte Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CByte(value)
IsSafeByte = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeCurrency(value As Variant) As Boolean
On Error Resume Next
IsSafeCurrency = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeCurrency Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CCur(value)
IsSafeCurrency = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeDate(value As Variant) As Boolean
On Error Resume Next
IsSafeDate = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeDate Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CDate(value)
IsSafeDate = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeDouble(value As Variant) As Boolean
On Error Resume Next
IsSafeDouble = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeDouble Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CDbl(value)
IsSafeDouble = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeInteger(value As Variant) As Boolean
On Error Resume Next
IsSafeInteger = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeInteger Or this.OptionStrict Then Exit Function
Dim result As Boolean
result = CInt(value)
IsSafeInteger = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Function IsSafeLong(value As Variant) As Boolean
On Error Resume Next
IsSafeLong = (this.TValue = vbNullString Or this.TValue = TypeName(value))
If IsSafeLong Or OptionStrict Then Exit FunctiPrivate Function ValidateItemType(kvp As KeyValuePair, Optional ThrowOnUnsafeType As Boolean = False) As Boolean
If this.TKey = vbNullString And this.TValue = vbNullString Then
this.TKey = TypeName(kvp.key)
this.IsRefTypeKey = IsObject(kvp.key)
this.TValue = TypeName(kvp.value)
this.IsRefTypeValue = IsObject(kvp.value)
this.KeyValidator.TValue = this.TKey '<<< here
this.ValueValidator.TValue = this.TValue '<<< here
End If
ValidateItemType = IsTypeSafe(kvp)
If ThrowOnUnsafeType And Not ValidateItemType Then RaiseErrorUnsafeType "ValidateItemType()", kvp.ToString
End FunctionPublic Property Let OptionStrict(value As Boolean)
this.Encapsulated.OptionStrict = value
this.KeyValidator.OptionStrict = value ' <<< here
this.ValueValidator.OptionStrict = value ' <<< here
End PropertyPrivate Sub Class_Initialize()
Set this.Encapsulated = New List
Set this.KeyValidator = New TypeValidator ' <<< here
Set this.ValueValidator = New TypeValidator ' <<< here
this.Encapsulated.OptionStrict = True
this.KeyValidator.OptionStrict = True ' <<< here
this.ValueValidator.OptionStrict = True ' <<< here
End SubPrivate Type tDictionary
Encapsulated As List
TKey As String
IsRefTypeKey As Boolean
TValue As String
IsRefTypeValue As Boolean
KeyValidator As TypeValidator ' <<< here
ValueValidator As TypeValidator ' <<< here
End TypeContext
StackExchange Code Review Q#45666, answer score: 5
Revisions (0)
No revisions yet.