ott 25

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. ;-)

Leave a Reply

preload preload preload