Public Function ToString() As String
'Returns a string that represents the current List object.
ToString = StringFormat("{0}<{1}>", TypeName(Me), _
Coalesce(this.ItemTypeName, "Variant"))
End Function
This means the string representation of this List is "List<Variant>" when this.ItemTypeName is empty or vbNullString.
This function should be somewhere at the very top:
Private Function ValidateItemType(value As Variant)
If this.ItemTypeName = vbNullString Then this.ItemTypeName = TypeName(value)
ValidateItemType = IsTypeSafe(value)
End Function
This is where the string representation of the List stops being "List<Variant>" and becomes List<T>
Given this information, there's a flaw in the IsReferenceType function, which might return the wrong result if the list originally contained objects and then was emptied so that Count = 0:
Private Function IsReferenceType() As Boolean
If Count = 0 Then Exit Function
IsReferenceType = IsObject(this.Encapsulated(1))
End Function
The correct code should be:
Private Function IsReferenceType() As Boolean
If this.ItemTypeName = vbNullString Then Exit Function
IsReferenceType = IsObject(this.Encapsulated(1))
End Function
In these snippets:
Private Function IsComparable() As Boolean
If IsReferenceType Then
IsComparable = TypeOf First Is IComparable
End If
End Function
Private Function IsEquatable() As Boolean
If IsReferenceType Then
IsEquatable = TypeOf First Is IEquatable
End If
End Function
It is assumed that only value types can implement IComparable and IEquatable, and that is correct in VB6. Therefore, the presence of CompareValueTypes and EquateValueTypes functions is somewhat awkward, but their usage makes a quite enjoyable reading:
If isRef Then
'...
isSmaller = CompareReferenceTypes(Item(i), smallest) < 0
'...
Else
'...
isSmaller = CompareValueTypes(Item(i), smallest) < 0
'...
End If
The Attribute Item.VB_UserMemId = 0 setting in the getter for the Item property makes that getter the type's default property, making Item(i) also be accessible with Me(i). Cool stuff. Even better:
Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
'Gets an enumerator that iterates through the List.
Set NewEnum = this.Encapsulated.[_NewEnum]
End Property
Attribute NewEnum.VB_UserMemId = -4 instructs VB to use this method in For Each loop constructs; this enables AddRange(values As List) to do what it does:
For Each value In values
Add value
Next
Given AddArray(values() As Variant), I think AddValues(ParamArray values()) could easily replace Add(value As Variant) - if there's only 1 value to add, both methods can be used and that makes it an ambiguous API. Add, AddRange and AddArray should be rewritten as follows:
Public Sub Add(ParamArray values())
'Adds the specified element(s) to the end of the List.
Dim valuesArray() As Variant
valuesArray = values
AddArray valuesArray
End Sub
Public Sub AddRange(values As List)
'Adds the specified elements to the end of the List.
AddArray values.ToArray
End Sub
Public Sub AddArray(values() As Variant)
'Adds the specified elements to the end of the List.
Dim value As Variant, i As Long
For i = LBound(values) To UBound(values)
If ValidateItemType(value) Then
this.Encapsulated.Add values(i)
Else
RaiseErrorUnsafeType "AddArray()", TypeName(value)
End If
Next
End Sub
If Count = 0 Then Exit Function, wherever it's used, is an opportunity for some RaiseErrorListContainsNoElement, instead of returning an empty Variant or a meaningless False value.
Insert(ByVal Index As Long, value As Variant) and InsertValues(ByVal Index As Long, ParamArray values()) have exactly the same issue as Add and AddValues have; InsertValues should disappear and be replaced with this:
Public Sub Insert(ByVal Index As Long, ParamArray values())
'Inserts the specified element(s) into the List at the specified index.
Dim valuesArray() As Variant
valuesArray = values
InsertArray Index, valuesArray
End Sub
The conditions for IsSortable are somewhat redundant and the firstItem variable only hides intent - accessing the first item isn't expensive enough to take this readability hit (besides this isn't called in a loop), so IsSortable() could be rewritten like this, and again If Count = 0 Then Exit Function is an opportunity for some RaiseErrorListContainsNoElement:
Public Function IsSortable() As Boolean
'Determines whether the List can be sorted.
If Count = 0 Then RaiseErrorListContainsNoElement "IsSortable()"
If IsReferenceType Then
IsSortable = IsComparable
Else
IsSortable = IsNumeric(First) _
Or IsDate(First) _
Or this.ItemTypeName = "String"
End If
End Function
IsTypeSafe is interesting. It works, but it's a little too stiff and a bit more effort could be put into accepting Long values within Integer range in a List<Integer>, and so on.
Remove(ParamArray values()) is already consistent with the changes made to Add and Insert, however RemoveRange breaks the naming convention established with AddRange and InsertRange which both take a List as a parameter. Since RemoveRange should keep its .net List<T> meaning, AddRange and InsertRange should be renamed AddList and InsertList, which would be consistent with AddArray and InsertArray.
That's all I can see.