Сортировать группу классов по свойству по умолчанию

TL;DR:

Есть ли способ передать коллекцию/список классов алгоритму сортировки библиотеки и заставить его возвращать отсортированный список (предпочтительно с помощью именованного свойства класса/класса по умолчанию)?

Недавно я немного изучил Python и был впечатлен функцией Sorted(), которая может сортировать любой итерируемый объект. Для чисел это просто, однако для классов можно назначить метод сравнения подобно этому. Метод сообщает операторам сравнения, как сравнивать 2 экземпляра класса. Помимо прочего, он позволяет использовать встроенные алгоритмы сортировки для сортировки коллекции класса.

В VBA мне наполовину удалось имитировать это. Установив для класса член по умолчанию Attribute, вы можете использовать операторы сравнения (<,= ,>= и т. д.) непосредственно на занятиях. Возьмите пример класса:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "defaultProp"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Private randVal As Single

Public Property Get DefaultValue() As Single
    Attribute Value.VB_UserMemId = 0
    DefaultValue = randVal
End Property

Private Property Let DefaultValue(ByVal value As Single)
    randVal = value
End Property

Private Sub Class_Initialize()
    DefaultValue = Rnd()
End Sub

Можно сравнить два экземпляра этого класса:

 Dim instance1 As New defaultProp
 Dim instance2 As New defaultProp
 Debug.Print instance1.DefaultValue > instance2.DefaultValue
 Debug.Print instance1 > instance2 'exactly equivalent, as the DefaultValue has the correct Attribute

И если бы я реализовал алгоритм сортировки VBA, который может сортировать значения, не должно быть проблем с сортировкой классов по значению по умолчанию*. Однако я бы предпочел использовать встроенный/библиотечный алгоритм сортировки (по тем же причинам, что и любой: ясность, эффективность, правильная обработка ошибок и т. д.).

*Один из этих алгоритмов работать для этого, хотя его необходимо изменить, чтобы переключить весь раунд класса, а не его значение (путем добавления Sets)

Поскольку у операторов сравнения VBA нет проблем, я предположил, что то же самое будет верно для всего, что использует библиотека. Однако, когда я попытался использовать ArrayList :

Sub testArrayList()
    Dim arr As Object
    Set arr = CreateObject("System.Collections.ArrayList")

    ' Initialise the ArrayList, for instance by generating random values
    Dim i As Long
    Dim v As defaultProp

    For i = 1 To 5
        Set v = New defaultProp
        arr.Add v 'no problem here
    Next i
    arr.Sort 'raises an error
End Sub

я получаю сообщение об ошибке

Не удалось сравнить два элемента в массиве

Так что же происходит? Является ли это недостатком моего подхода — атрибут по умолчанию не попадает в ArrayList? Или, может быть, оператор сравнения на любом языке, на котором написана библиотека, не такой тупой, как те, которые используют VBA и Python? Любые предложения по использованию дополнительных встроенных алгоритмов сортировки также будут полезны!


person Greedo    schedule 12.12.2017    source источник


Ответы (3)


Речь идет не об операторах сравнения VBA, ArrayList — это класс .NET, поэтому при его использовании вы находитесь в мире .NET.

arr.Add v 'no problem here

Вы добавляете экземпляры класса defaultProp; не имеет значения, что у вас есть свойство по умолчанию для типа, .NET не заботится о свойствах по умолчанию. Если вы хотите отсортировать значения DefaultValue, то сделайте arr.Add v.DefaultValue или arr.Add (v) - тогда ваш ArrayList будет содержать элементы типа Single, которые он умеет сортировать.

Чтобы ArrayList.Sort работал с экземплярами вашего пользовательского класса, его элементы должны реализовывать интерфейс IComparable, что имеет место для System.Int32 (т.е. Long в VBA), System.String и всех других примитивных типов .NET, и я думаю примитивные типы VBA действительно правильно маршалируются через взаимодействие .NET, но не пользовательские классы.

Попробуйте добавить ссылку на mscorlib.tlb, а затем в модуле класса defaultProp укажите это (вы не можете реализовать интерфейс, определенный в библиотеке с поздним связыванием):

Implements IComparable

Затем реализуйте интерфейс — он должен выглядеть примерно так (используйте раскрывающиеся списки кодовой панели, чтобы убедиться, что подпись правильная — не просто скопируйте и вставьте этот фрагмент):

Private Function IComparable_CompareTo(ByVal obj As Variant) As Long
    Dim other As defaultProp
    Set other = obj
    ' return Less than zero (-1) if this object 
    ' is less than the object specified by the CompareTo method.

    ' return Zero (0) if this object is equal to the object 
    ' specified by the CompareTo method.

    ' return Greater than zero (1) if this object is greater than 
    ' the object specified by the CompareTo method.
End Function

Теперь, когда ваш пользовательский класс реализует интерфейс, который ArrayList.Sort использует для определения того, как ваши defaultProp элементы соотносятся друг с другом, я не вижу причин для его отказа.

person Mathieu Guindon    schedule 12.12.2017

ИМО, вы злоупотребляете вещами, смешивая вещи, выходящие за границы. Вы используете свойства VBA по умолчанию (что я обычно считаю плохой практикой), затем вы используете .NET ArrayList и пытаетесь Sort его.

Я думаю, было бы гораздо логичнее посмотреть, сможете ли вы реализовать IComparable в классе VBA, а затем позволить ArrayList использовать интерфейс IComparable для сравнения объекта с другим, как вы хотите, чтобы он сравнивался, без использования каких-либо взломанных свойств по умолчанию.

person this    schedule 12.12.2017
comment
Что ж, как указывает Mat's Mug, я не думаю, что есть способ реализовать сортировку с помощью ArrayList без реализации IComparable (свойства по умолчанию не помогают!) Это был просто возможный подход, но теперь я вижу, что есть другие варианты - person Greedo; 12.12.2017

Если вы добавите DefaultValue к arr, это сработает:

Sub testArrayList()
    '... code
    For i = 1 To 5
        Set v = New defaultProp
        arr.Add v.DefaultValue 
    Next i
    arr.Sort         
End Sub

Очевидно, что реализация .Sort из ArrayList немного странная и не любит сравнивать объекты и их значения по умолчанию (не удалось найти реализацию метода Sort()). Хотя это будет работать безупречно:

For i = 1 To 5
    Set v = New defaultProp
    arr.Add v
Next i    
Debug.Print arr(1) > arr(2)

Это возможная реализация сортировки, которая будет работать для объекта arr, как и ожидалось. Однако он не является частью библиотеки ArrayList:

Public Function varBubbleSort(varTempArray As Object) As Object

    Dim varTemp                 As Object
    Dim lngCounter              As Long
    Dim blnNoExchanges          As Boolean

    Do
        blnNoExchanges = True
        For lngCounter = 0 To varTempArray.Count - 2
            If varTempArray(lngCounter) > varTempArray(lngCounter + 1) Then
                blnNoExchanges = False
                Set varTemp = varTempArray(lngCounter)
                varTempArray(lngCounter) = varTempArray(lngCounter + 1)
                varTempArray(lngCounter + 1) = varTemp
            End If
        Next lngCounter

    Loop While Not (blnNoExchanges)
    Set varBubbleSort = varTempArray

   On Error GoTo 0
   Exit Function

End Function

Но сортировка в порядке:

введите здесь описание изображения

person Vityata    schedule 12.12.2017
comment
Ах, но тогда у меня есть массив значений, а не классов. Я хотел бы отсортировать список классов по некоторому значению, но на самом деле перемещать все объекты класса вокруг - person Greedo; 12.12.2017
comment
Итак, операторы сравнения работают, когда классы добавляются в ArrayList (ваш второй бит кода), но сортировка по ним напрямую не работает? Любая идея обходного пути, поскольку это объекты, которые мне нужно сортировать, а не их значения по умолчанию (хотя, конечно, вы можете сопоставить последнее обратно с первым, но это в первую очередь отвлекает от использования библиотечной сортировки) - person Greedo; 12.12.2017
comment
@Greedo - я полагаю, что это просто реализация .Sort(), сделанная таким образом. Например, вероятно, в нем используются LBound и UBound, и это нарушает функцию. Я только предполагаю здесь, насколько я не могу видеть это. - person Vityata; 12.12.2017
comment
FWIW для справки.... Реализация сортировки в ArrayList - person this; 12.12.2017