vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
Zippen wie die Profis!  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   RSS-Feeds  | Newsletter  | Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2019
 
zurück
Rubrik: Variablen/Strings · Array/ArrayList   |   VB-Versionen: VB.NET30.01.06
Instanzen in einer ArrayList sortieren II

Wie man Instanzen eigener Klassen sortiert, die in einer ArrayList stehen

Autor:   DaveSBewertung:     [ Jetzt bewerten ]Views:  10.655 
ohne HomepageSystem:  WinNT, Win2k, WinXP, Vista, Win7, Win8, Win10 Beispielprojekt auf CD 

Bezugnehmend auf den Tipp  Instanzen in einer ArrayList sortieren (VB.NET) möchten wir Ihnen heute eine verbesserte Version vorstellen, bei der die Basisrichtlinien für .NET Code berücksichtig werden:
 Design Guidelines oder Cwalina und Abrams "Framework Design Guidelines" (Adison Wesley).

Es wird sehr empfohlen solche Klassen mit Properties zu versehen, und nicht mit Public Fields. Das erfordert, dass man nach Properties sucht. Aber hier sollten wir nicht ausser Acht lassen, dass Properties auch virtuell sein können (etwa ICustomTypeDescriptor ist implementiert). Das ist nicht völlig irrelevant, weil Windows Forms DataBinding überall mit Properties arbeitet, und man durchaus eigene Klassen mit ArrayLists und DataBinding verwenden könnte. Das verlangt aber, dass man nicht Reflection, sondern das Component Model PropertyDescriptor Mechanismus verwendet. Aber lassen wir das, und bleiben wir der Einfachkeit halber bei einfachen Properties und Reflection.

Dann hätten wir immerhin eine saubere Klasse:

Public Class Schiff
  Implements IComparable 
 
  Private _name As String
  Private _heimathafen As String
  Private _baujahr As Integer
  Private _kaufpreis As Single
  Private _stapellauf As New Date
 
  Public Property Name() As String
    Get
      Return _name
    End Get
    Set(ByVal Value As String)
      _name = Value
    End Set
  End Property
 
  Public Property Heimathafen() As String
    Get
      Return _heimathafen
    End Get
    Set(ByVal Value As String)
      _heimathafen = Value
    End Set
  End Property
 
  Public Property Baujahr() As Integer
    Get
      Return _baujahr
    End Get
    Set(ByVal Value As Integer)
      _baujahr = Value
    End Set
  End Property
 
  Public Property Kaufpreis() As Single
    Get
      Return _kaufpreis
    End Get
    Set(ByVal Value As Single)
      _kaufpreis = Value
    End Set
  End Property
 
  Public Property Stapellauf() As DateTime
    Get
      Return _stapellauf
    End Get
    Set(ByVal Value As DateTime)
      _stapellauf = Value
    End Set
  End Property
 
  Public Shared SortierFeld As String = "Heimathafen"
  Public Shared Sortierung As SortOrder = SortOrder.Ascending
 
  Public Sub New(ByVal Name As String, ByVal Heimathafen As String, _
    ByVal Baujahr As Integer, ByVal Kaufpreis As Single, ByVal Stapellauf As Date)
 
    Me.Name = Name
    Me.Heimathafen = Heimathafen
    Me.Baujahr = Baujahr
    Me.Kaufpreis = Kaufpreis
    Me.Stapellauf = Stapellauf
  End Sub
 
  Public Sub New()
  End Sub
 
  ' IComparable...
 
End Class

Die CompareTo() Routine ist sehr viel komplizierter als notwendig. Weil alles was man vernünftig vergleichen will (oder kann) IComparable unterstützt (Integer, String, Single usw) müssen wir die Parameter nur zu IComparable casten, und wenn das nicht klappt wird eine Exception ausgelöst, was sinnvoll ist weil wir die Werte dann eh nicht vergleichen können. So auszig Zeilen Code wird einfach

Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
  Dim s As Schiff
  s = CType(obj, Schiff)
  Dim f1 As Reflection.PropertyInfo
  f1 = Me.GetType.GetProperty(Me.SortierFeld)
  Dim f2 As Reflection.PropertyInfo
  f2 = s.GetType.GetProperty(Me.SortierFeld)
  Dim Wert1 As IComparable = CType(f1.GetValue(Me, Nothing), IComparable)
  Dim Wert2 As Object = f2.GetValue(obj, Nothing)
  Return Wert1.CompareTo(Wert2)
End Function

Aber so sind wir längst nicht fertig, zweimal wird ein PropertyInfo (ursprünglich FieldInfo) mittels Reflection geholt (eine teure Operation) obwohl wir wissen, dass wir es in beiden Fällen mit einem Objekt vom Typ Schiff zu tun haben. Wenn wir diese Schwäche auch ausbügeln haben wir:

Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
  Dim s As Schiff
  s = CType(obj, Schiff)
  Dim f1 As Reflection.PropertyInfo
  f1 = Me.GetType.GetProperty(Me.SortierFeld)
  Dim Wert1 As IComparable = CType(f1.GetValue(Me, Nothing), IComparable)
  Dim Wert2 As Object = f1.GetValue(obj, Nothing)
  Return Wert1.CompareTo(Wert2)
End Function

Allerdings, damit sind wir mit der Kritik auch längst nicht fertig. Reflection bei jedem Vergleich zwischen zwei Elementen des Arrays zu verwenden ist extrem ineffizient, und es ist sehr ungeschickt Parameter an CompareTo() mittels statischen (Shared) Variablen zu übergeben, und nicht nur ungeschickt sondern umständlich und fehleranfällig. Und reverse Sort zu implementieren indem wir das ganze Array umschaufeln ist auch nicht gerade elegent. Wesentlich sinnvoller wäre es ein IComparer Objekt bei arraylist.Sort() zu übergeben, wo die notwendigen Parameter im Klassenobjekt kapsuliert sind. Wir sind dann gleich alle drei Schwächen los, weil wir Reflection nur im Klassenkonstruktor benutzen müssen, und eine Sortoption auch übergeben können. Dann hätten wir die Klasse:

Public Class Schiff
  ' Fields, Properties, Constructors...(Shared Felder entfallen)
 
  Public Class Comparer
    Implements IComparer
    Private _sortProperty As Reflection.PropertyInfo
    Private _sortDescending As Boolean
 
    Public Sub New(ByVal sortFieldName As String, ByVal sortDescending As Boolean)
      _sortProperty = GetType(Schiff).GetProperty(sortFieldName)
      _sortDescending = sortDescending
    End Sub
 
    Public Function Compare(ByVal obj1 As Object, ByVal obj2 As Object) As Integer Implements IComparer.Compare
      Dim Wert1 As IComparable = CType(_sortProperty.GetValue(obj1, Nothing), IComparable)
      Dim Wert2 As Object = _sortProperty.GetValue(obj2, Nothing)
      Dim res As Integer = Wert1.CompareTo(Wert2)
      If _sortDescending Then Return -res
      Return res
    End Function
  End Class
End Class

Der Aufruf im Hauptcode sieht denn so aus:

AL.Sort(New Schiff.Comparer("Baujahr", False))

AL.Reverse() entfällt ebenfalls.

Ein etwas erweitertes Beispiel
Dieser Code benutzt PropertyDescriptor. Die Daten werden im DataGrid dargestellt, was wesentlich übersichtlicher ist, und uns erlaubt die Sortreihenfolge mittels Mausklick auf Spaltenkopf zu setzen, eine etwas bessere Demonstration, denke ich.

Public Class Form1
  Inherits System.Windows.Forms.Form
 
  #Region " Windows Form Designer generated code "
 
  Public Class Schiff
 
    Private _name As String
    Private _heimathafen As String
    Private _baujahr As Integer
    Private _kaufpreis As Single
    Private _stapellauf As New Date
 
    Public Property Name() As String
      Get
        Return _name
      End Get
      Set(ByVal Value As String)
       _name = Value
      End Set
    End Property
 
    Public Property Heimathafen() As String
      Get
        Return _heimathafen
      End Get
      Set(ByVal Value As String)
        _heimathafen = Value
      End Set
    End Property
 
    Public Property Baujahr() As Integer
      Get
        Return _baujahr
      End Get
      Set(ByVal Value As Integer)
        _baujahr = Value
      End Set
    End Property
 
    Public Property Kaufpreis() As Single
      Get
        Return _kaufpreis
      End Get
      Set(ByVal Value As Single)
        _kaufpreis = Value
      End Set
    End Property
 
    Public Property Stapellauf() As DateTime
      Get
        Return _stapellauf
      End Get
      Set(ByVal Value As DateTime)
        _stapellauf = Value
      End Set
    End Property
 
    Public Sub New(ByVal Name As String, ByVal Heimathafen As String, _
      ByVal Baujahr As Integer, ByVal Kaufpreis As Single, ByVal Stapellauf As Date)
 
      Me.Name = Name
      Me.Heimathafen = Heimathafen
      Me.Baujahr = Baujahr
      Me.Kaufpreis = Kaufpreis
      Me.Stapellauf = Stapellauf
    End Sub
 
    Public Sub New()
    End Sub
 
    Public Class Comparer
      Implements IComparer
      Private Shared _sortProperties As System.ComponentModel.PropertyDescriptorCollection
      Private _sortProperty As System.ComponentModel.PropertyDescriptor
      Private _sortDescending As Boolean
 
      Public Sub New(ByVal sortFieldName As String, ByVal sortDescending As Boolean)
        If _sortProperties Is Nothing Then
          _sortProperties = System.ComponentModel.TypeDescriptor.GetProperties(GetType(Schiff))
        End If
        _sortProperty = _sortProperties(sortFieldName)
        _sortDescending = sortDescending
      End Sub
 
      Public Sub New(ByVal sortFieldNumber As Integer, ByVal sortDescending As Boolean)
        If _sortProperties Is Nothing Then
          _sortProperties = System.ComponentModel.TypeDescriptor.GetProperties(GetType(Schiff))
        End If
        _sortProperty = _sortProperties(sortFieldNumber)
        _sortDescending = sortDescending
      End Sub
 
      Public Function Compare(ByVal obj1 As Object, ByVal obj2 As Object) As Integer Implements IComparer.Compare
        Dim Wert1 As IComparable = CType(_sortProperty.GetValue(obj1), IComparable)
        Dim Wert2 As Object = _sortProperty.GetValue(obj2)
        Dim res As Integer = Wert1.CompareTo(Wert2)
        If _sortDescending Then Return -res
        Return res
      End Function
    End Class
  End Class
 
  Dim AL As New ArrayList
 
  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ' Erstellen einiger Instanzen
    Dim s1 As New Schiff("Albatros", "Rostock", 1864, 492.9, New Date(1863, 3, 5))
    Dim s2 As New Schiff("Ubena von Bremen", "Bremen", 1380, 482.5, New Date(1379, 8, 27))
    Dim s3 As New Schiff("Adler von Lübeck", "Lübeck", 1565, 285.7, New Date(1563, 12, 9))
    Dim s4 As New Schiff("Rath von Riga", "Riga", 1341, 450.34, New Date(1340, 11, 24))
 
    ' Füllen der ArrayList mit den Schiffen
    AL.Add(s1)
    AL.Add(s2)
    AL.Add(s3)
    AL.Add(s4)
 
    ' Show data
    DataGrid1.DataSource = AL
  End Sub
 
  Dim currentSortColumn As Integer = -1
 
  Private Sub DataGrid1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles DataGrid1.Click
    Dim mp As Point = DataGrid1.PointToClient(MousePosition)
    Dim hti As DataGrid.HitTestInfo = DataGrid1.HitTest(mp.X, mp.Y)
    If hti.Type = DataGrid.HitTestType.ColumnHeader Then
      Dim sd As Boolean = hti.Column = currentSortColumn
      AL.Sort(New Schiff.Comparer(hti.Column, sd))
      If sd Then
        currentSortColumn = -1
      Else
        currentSortColumn = hti.Column
      End If
    End If
    DataGrid1.DataSource = AL
  End Sub
End Class

Dieser Tipp wurde bereits 10.655 mal aufgerufen.

Voriger Tipp   |   Zufälliger Tipp   |   Nächster Tipp

Über diesen Tipp im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Tipp, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Neue Diskussion eröffnen

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Tipps & Tricks finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6
(einschl. Beispielprojekt!)

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
   

Druckansicht Druckansicht Copyright ©2000-2019 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel