Come rendere confrontabili i nostri oggetti.
Ordiniamo un ArrayList implementando le giuste interfacce.
Difficoltà:![]()
Piattaforma: VB.NET
Se stiamo lavorando con tipi di dati valore, è possibile effettuare paragoni attraverso l’utilizzo dei classici operatori di confronto maggiore (>), minore (<) = e uguale (=).
Anche per i tipi stringa è possibile usare tali operatori, anche se in questo caso , l’ordinamento può essere condizionato da opzioni di “Option Compare” o dalle impostazioni internazionali.
Ma cosa succede se abbiamo bisogno di confrontare degli oggetti?
Il Framework .NET ci mette a disposizione l’interfaccia IComparable. Attraverso l’implementazione di questa interfaccia, e più precisamente dell’ unico metodo compareTo(Object) è possibile comunicare al metodo chiamante, se il nostro oggetto è maggiore o meno rispetto ad un secondo oggetto passato come parametro.
Mentre i valori numerici, le stringhe e le date hanno già il metodo CompareTo, per le nostre classi è necessario implementarla manualmente.
Verificandone l’implementazione negli oggetti contentuti, classi come Arraylist sono in grado, chiamando automaticamente il metodo CompareTo, di restituire elenchi ordinati.
Possiamo verificarlo creando ad esempio una lista di stringhe e chiamando successivamente il metodo Sort di un ArrayList.
Dim Product1 As String
Dim Product2 As String
Dim Product3 As String
Dim Product4 As String
Dim sSortedObject As String = “”
Try
Product1 = “Parallelepiped 1″
Product2 = “Parallelepiped 2″
Product3 = “Parallelepiped 3″
Product4 = “Parallelepiped 4″
Dim myList As New ArrayList
myList.Add(Product4)
myList.Add(Product1)
myList.Add(Product2)
myList.Add(Product3)
myList.Sort()
For Each obj In myList
sSortedObject &= obj & vbCrLf
Next
MessageBox.Show(sSortedObject)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Nel caso in cui il nostro Arraylist contenga un elenco di oggetti non confrontabili, il metodo Sort ci restituisce un errore.
Dim Product1 As ProductClass
Dim Product2 As ProductClass
Dim Product3 As ProductClass
Dim Product4 As ProductClass
Dim sSortedObject As String = “”
Try
Product1 = New ProductClass(5, 5, 5, “Parallelepiped 1″)
Product2 = New ProductClass(6, 6, 6, “Parallelepiped 2″)
Product3 = New ProductClass(3, 4, 5, “Parallelepiped 3″)
Product4 = New ProductClass(6, 6, 2, “Parallelepiped 4″)
Dim myList As New ArrayList
myList.Add(Product1)
myList.Add(Product2)
myList.Add(Product3)
myList.Add(Product4)
myList.Sort()
For Each obj In myList
sSortedObject &= obj.Name & vbCrLf
Next
MessageBox.Show(sSortedObject)
Catch ex As Exception
MessageBox.Show(ex.Message & ex.InnerException.Message)
End Try
(Non ho implemento qui la classe ProductClass per motivi di praticità, in ogni caso verrà rivista più avanti.)
Implementare il metodo CompareTo
Questo metodo prevede la restituzione di un valore minore di zero nel caso in cui l’oggetto è più piccolo di quello passato come parametro, di zero nel caso in cui siano uguali e maggiore di zero nel caso in cui sia più grande.
Creiamo una classe che implementa l’interfaccia IComparable
Public Class ProductClassComparable
Implements IComparable
Private m_height As Integer
Private m_width As Integer
Private m_length As Integer
Private m_Name As String
Public Sub New()
m_height = 0
m_width = 0
m_length = 0
End Sub
Public Sub New(ByVal iHeight As Integer, ByVal iWidth As Integer, ByVal iLength As Integer, ByVal sName As String)
m_height = iHeight
m_width = iWidth
m_length = iLength
m_Name = sName
End Sub
Public Property Height() As Integer
Get
Return m_height
End Get
Set(ByVal value As Integer)
m_height = value
End Set
End Property
Public Property Width() As Integer
Get
Return m_width
End Get
Set(ByVal value As Integer)
m_width = value
End Set
End Property
Public Property Length() As Integer
Get
Return m_length
End Get
Set(ByVal value As Integer)
m_length = value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
Public ReadOnly Property GetVolume() As Decimal
Get
Return (m_height * m_length * m_width)
End Get
End Property
Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
If Me.Name < obj.Name Then
Return -1
ElseIf Me.Name > obj.Name Then
Return 1
Else
Return 0
End If
End Function
End Class
Il metodo principale qui è CompareTo(Object) dove viene effettuato il confronto vero e proprio e viene restituito il risultato.
E’ importante notare che all’interno di questo metodo possiamo implementare il tipo di controllo che ci serve, ad esempio un operazione, il confronto sul numero di caratteri etc, ma un vincolo di questa soluzione è il fatto che stiamo definendo una regola precisa di confronto tra oggetti, e quindi, nel caso di qualsiasi futuro ordinamento, siamo vincolati a questa regola.
Dato inoltre che non stiamo lavorando con un ArrayList di generici, è inoltre consigliato aggiungere gli opportuni controlli sul tipo di oggetto passato come parametro.
Ordinare utilizzando un oggetto IComparer
Una soluzione alternativa è quella di andare a creare una classe che implementi l’interfaccia IComparer e che si occupi di effettuare il confronto tra i due oggetti. Un’istanza di questa classe verrà passata come parametro al metodo Sort dell’ArrayList.
Analogamente al metodo CompareTo, anche il metodo Compare restituisce in Int32 che dice al chiamante se il primo oggetto è maggiore o minore del secondo oggetto.
Public Class ComparerProductClass
Implements IComparer
Public Sub New()
MyBase.New()
End Sub
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
‘ … Implementazione del codice di confronto tra gli oggetti
End Function
End Class
Generalizzare il nostro oggetto Comparer
Con un po’ di reflection e un paio di property in più possiamo costruire una classe comparer che ci permetta di generalizzare il nome della property su cui effettuare il confronto oltre al verso dell’ordinamento.
Imports System.Reflection
Public Class ComparerGeneralizedClass
Implements IComparer
Private m_propertyname As String
Private m_ascending As Boolean
‘ Costruttore
Public Sub New(ByVal PropertyName As String, ByVal Ascending As Boolean)
m_propertyname = PropertyName
m_ascending = Ascending
End Sub
Public Function Compare(ByVal obj1 As Object, ByVal obj2 As Object) As Integer Implements System.Collections.IComparer.Compare
Dim iResult As Integer
Dim Type_obj1 As Type
Dim Type_obj2 As Type
Dim PropertyInfo_obj1 As PropertyInfo
Dim PropertyInfo_obj2 As PropertyInfo
Dim Valueobj1 As IComparable
Dim Valueobj2 As IComparable
Type_obj1 = obj1.GetType()
Type_obj2 = obj2.GetType()
‘ Verificare che la Property Esista
PropertyInfo_obj1 = Type_obj1.GetProperty(m_propertyname)
PropertyInfo_obj2 = Type_obj2.GetProperty(m_propertyname)
If PropertyInfo_obj1 Is Nothing Then
‘ Property non Esiste
Throw New Exception(“Property ” & m_propertyname & ” doesn’t Exist”)
End If
If PropertyInfo_obj2 Is Nothing Then
‘ Property non Esiste
Throw New Exception(“Property ” & m_propertyname & ” doesn’t Exist”)
End If
Valueobj1 = PropertyInfo_obj1.GetValue(obj1, Nothing)
Valueobj2 = PropertyInfo_obj2.GetValue(obj2, Nothing)
If Valueobj1 Is Nothing Then
‘ Property non IComparable
Throw New Exception(“Property ” & m_propertyname & ” is not IComparable”)
End If
If Valueobj2 Is Nothing Then
‘ Property non IComparable
Throw New Exception(“Property ” & m_propertyname & ” is not IComparable”)
End If
iResult = Valueobj1.CompareTo(Valueobj2)
If iResult <> 0 Then
If m_ascending Then
Return iResult
Else
Return (-iResult)
End If
End If
Return 0
End Function
End Class
Vediamone a questo punto il codice.
Come prima cosa abbiamo aggiunto due property, la prima di tipo String, dove andiamo ad inserire il nome della property su cui vogliamo effettuare l’ordinamento, mentre la seconda di tipo Boolean, ci serve per capire se vogliamo fare un ordinamento dal più piccolo al più grande o viceversa.
Nel costruttore obblighiamo l’inserimento di questi due parametri
Public Sub New(ByVal PropertyName As String, ByVal Ascending As Boolean)
Nel metodo Compare invece, come prima cosa andiamo a recuperare il tipo degli oggetti
Type_obj1 = obj1.GetType()
Type_obj2 = obj2.GetType()
e successivamente, recuperiamo i due oggetti PropertyInfo della property desiderata attraverso i due oggetti Type appena istanziati.
PropertyInfo_obj1 = Type_obj1.GetProperty(m_propertyname)
PropertyInfo_obj2 = Type_obj2.GetProperty(m_propertyname)
A questo punto se uno dei due oggetti propertyInfo e a nothing vuol dire che la proprietà non esiste. (Attenzione al case sensitive)
Proseguendo, andiamo a leggerne il valore e ad assegnarlo a due oggetti di tipo IComparable.
Valueobj1 = PropertyInfo_obj1.GetValue(obj1, Nothing)
Valueobj2 = PropertyInfo_obj2.GetValue(obj2, Nothing)
Qui è importante ricordare che numerici, stringhe e date sono già ICompareble quindi assumiamo che la nostra property sia di questo tipo o sia un oggetto che comunque implementa l’interfaccia IComparable.
Nel caso effettuiamo un controllo e se così’ non fosse, solleviamo un eccezione.
If Valueobj1 Is Nothing Then
’ Property non IComparable
Throw New Exception(“Property ” & m_propertyname & ” is not IComparable”)
End If
Il resto è semplice, usiamo il metodo CompareTo per capire quale dei due oggetti è il maggiore e la property Ascending per decidere se invertire o meno il risultato.
Di seguito possiamo vederne il risultato:
Dim myList As New ArrayList
myList.Add(Product4)
myList.Add(Product1)
myList.Add(Product2)
myList.Add(Product3)
sSortedObject = “”
myComparer = New ComparerGeneralizedClass(“Name”, True)
myList.Sort(MyComparer)
For Each obj In myList
sSortedObject &= obj.Name & vbCrLf
Next
MessageBox.Show(sSortedObject)
sSortedObject = “”
myComparer = New ComparerGeneralizedClass(“Name”, False)
myList.Sort(myComparer)
For Each obj In myList
sSortedObject &= obj.Name & vbCrLf
Next
MessageBox.Show(sSortedObject)

‘ Volume
sSortedObject = “”
myComparer = New ComparerGeneralizedClass(“GetVolume”, False)
myList.Sort(myComparer)
For Each obj In myList
sSortedObject &= obj.Name & ” Volume:” & obj.GetVolume.ToString & vbCrLf
Next
MessageBox.Show(sSortedObject)
In conclusione
Anche in questo caso è possibile apportare diverse migliorie alla nostra classe e soprattutto renderla un pochino più solida aggiungendo qualche controllo in più.
In ogni caso abbiamo costruito un semplice prototipo riutilizzabile che ci permette di ordinare i nostri oggetti potendo scegliere nome della property ed il verso di ordinamento.
Al prossimo post.



