In meiner Arbeit als Softwareentwickler stoße ich oft auf die Herausforderung, Listen von Objekten in VB.NET zu vergleichen. Gerade bei benutzerdefinierten Klassen wie Auto, die aus mehreren Eigenschaften bestehen, wird dieser Vergleich komplexer. Es ist nicht nur wichtig, die Listen zu vergleichen, sondern auch zu verstehen, was ein “Gleichsein” in diesem Kontext bedeutet – vergleichen wir die Referenzen oder die Inhalte der Objekte?

Ausgangsbasis

Ausgangsbasis ist eine sauber erstellte Klasse:

' Definiert die Klasse Auto
Public Class Auto
    ' Private Felder für Marke und Modell
    Private _marke As String = String.Empty
    Private _modell As String = String.Empty

    ' Konstruktor für die Klasse Auto
    Public Sub New(ByVal marke As String, ByVal modell As String)
        ' Setzen der internen Felder mit den übergebenen Werten
        Me._marke = marke
        Me._modell = modell
    End Sub

    ' Öffentliche Eigenschaft für die Marke des Autos
    Public Property Marke() As String
        ' Getter-Methode für die Marke
        Get
            Return _marke
        End Get
        ' Setter-Methode für die Marke
        Set(ByVal value As String)
            Me._marke = value
        End Set
    End Property

    ' Öffentliche Eigenschaft für das Modell des Autos
    Public Property Modell() As String
        ' Getter-Methode für das Modell
        Get
            Return _modell
        End Get
        ' Setter-Methode für das Modell
        Set(ByVal value As String)
            Me._modell = value
        End Set
    End Property
End Class

Erläuterung:

  • Die Klasse Auto hat zwei private Felder _marke und _modell, die die Marke und das Modell des Autos speichern.
  • Der Konstruktor New initialisiert diese Felder mit den übergebenen Werten.
  • Die öffentlichen Eigenschaften Marke und Modell ermöglichen den Zugriff auf diese Felder von außen. Sie sind mit Gettern und Settern ausgestattet, um die Werte abzurufen und zu setzen.

Hat man nun zwei Listen dieser Klasse und möchte diese miteinander vergleichen, gibt es verschiedene Möglichkeiten, mit unterschiedlichen Ergebnis.

Vergleich der Referenz

Vergleich mit .contains

' Initialisiert eine neue Liste von Auto-Objekten
Dim Autos3 As New List(Of Auto)

' Durchläuft jedes Auto-Objekt in der Liste Autos2
For Each myAuto As Auto In Autos2
    ' Überprüft, ob das Auto-Objekt in der Liste Autos1 enthalten ist
    If Autos1.Contains(myAuto) Then
        ' Fügt das Auto-Objekt zur Liste Autos3 hinzu, wenn es in beiden Listen vorkommt
        Autos3.Add(myAuto)
    End If
Next

Erläuterung des Codes:

  • Eine neue Liste Autos3 vom Typ Auto wird initialisiert.
  • Die For Each-Schleife durchläuft jedes Element (myAuto) in der Liste Autos2.
  • Innerhalb der Schleife wird mit der Contains-Methode überprüft, ob das aktuelle Auto-Objekt (myAuto) auch in der Liste Autos1 vorhanden ist.
  • Falls das Objekt in beiden Listen vorhanden ist, wird es der Liste Autos3 hinzugefügt. Dadurch enthält Autos3 am Ende alle Auto-Objekte, die sowohl in Autos1 als auch in Autos2 vorkommen.

Als etwas schönerer generische Funktion:

' Definiert eine generische Funktion zum Vergleichen zweier Listen
Private Function Compare2List_Contains(Of myType)(ByVal lst1 As List(Of myType), ByVal lst2 As List(Of myType)) As List(Of myType)
    ' Erstellt eine neue Liste, um die übereinstimmenden Elemente zu speichern
    Dim lst3 As New List(Of myType)

    ' Durchläuft jedes Element in der zweiten Liste
    For Each Element As myType In lst2
        ' Überprüft, ob das Element in der ersten Liste enthalten ist
        If lst1.Contains(Element) Then
            ' Fügt das Element zur neuen Liste hinzu, wenn es in beiden Listen enthalten ist
            lst3.Add(Element)
        End If
    Next

    ' Gibt die Liste mit den übereinstimmenden Elementen zurück
    Return lst3
End Function

Erläuterung des Codes:

  • Diese Funktion ist generisch, was bedeutet, dass sie mit verschiedenen Datentypen (myType) arbeiten kann.
  • Zwei Listen lst1 und lst2 vom Typ myType werden als Parameter übergeben.
  • Eine neue Liste lst3 wird erstellt, um alle übereinstimmenden Elemente zu speichern.
  • Die Funktion durchläuft jedes Element in lst2 und überprüft, ob dieses Element auch in lst1 vorhanden ist.
  • Wenn ein Element in beiden Listen vorhanden ist, wird es zur Liste lst3 hinzugefügt.
  • Schließlich gibt die Funktion die Liste lst3 zurück, die alle Elemente enthält, die in beiden Eingabelisten vorhanden sind.

Das funktioniert aber nur unter folgender Voraussetzung: 

funktioniertDim myAuto As New Auto(“BMW”, “X3”)
Autos1.Add(myAuto)
Autos2.Add(myAuto)
funktioniert nichtAutos1.Add(New Auto(“Volkswagen”, “Passat”))
Autos2.Add(New Auto(“Volkswagen”, “Passat”))

Vergleich mit .GetHashCode

Eine andere Methode, wäre über den Vergleich der Elemente mittels .GetHashCode. Dise funktioniert ebenfalls nur bei identischen Referenzen. Als generische Funktion, würde das dann beispielsweise so aussehen:

' Definiert eine generische Funktion zum Vergleichen zweier Listen basierend auf GetHashCode
Private Function Compare2List_GetHashCode(Of myType)(ByVal lst1 As List(Of myType), ByVal lst2 As List(Of myType)) As List(Of myType)
    ' Erstellt eine neue Liste, um die übereinstimmenden Elemente zu speichern
    Dim lst3 As New List(Of myType)

    ' Durchläuft jedes Element in der ersten Liste
    For Each Element As myType In lst1
        ' Temporäre Variable zur Speicherung des aktuellen Elements
        Dim tmpElement As myType = Element

        ' Überprüft, ob ein Element mit dem gleichen HashCode in der zweiten Liste existiert
        If lst2.Exists(Function(x As myType) x.GetHashCode = tmpElement.GetHashCode) Then
            ' Fügt das Element zur neuen Liste hinzu, wenn ein übereinstimmender HashCode gefunden wird
            lst3.Add(Element)
        End If
    Next

    ' Gibt die Liste mit den übereinstimmenden Elementen zurück
    Return lst3
End Function

Erläuterung des Codes:

  • Diese Funktion ist generisch und kann mit verschiedenen Datentypen (myType) verwendet werden.
  • Zwei Listen lst1 und lst2 vom Typ myType werden als Parameter übergeben.
  • Eine neue Liste lst3 wird erstellt, um alle Elemente zu speichern, die in beiden Listen vorhanden sind.
  • Die Funktion durchläuft jedes Element in lst1 und verwendet eine Exists-Lambda-Funktion, um zu überprüfen, ob ein Element mit dem gleichen HashCode in lst2 existiert.
  • Wenn ein Element in lst2 mit dem gleichen HashCode gefunden wird, wird es zur Liste lst3 hinzugefügt.
  • Nachdem alle Elemente überprüft wurden, gibt die Funktion die Liste lst3 zurück. Diese Liste enthält alle Elemente aus lst1, für die ein Element mit dem gleichen HashCode in lst2 existiert.

Vergleich der Inhalte

Einen etwas anderen Weg muss man beschreiten, wenn man die Inhalte vergleichen möchte und nicht nur die Referenz. Eine mit Contains vergleichbare Funktion gibt es nicht. 

Vergleich mit .Exists

Das Grundkonzept funktioniert folgendermaßen:

' Initialisiert eine neue Liste von Auto-Objekten
Dim Autos3 As New List(Of Auto)

' Durchläuft jedes Auto-Objekt in der Liste Autos2
For Each myAuto2 As Auto In Autos2
    ' Erstellt eine temporäre Kopie des aktuellen Auto-Objekts
    Dim myAuto2tmp As Auto = myAuto2

    ' Überprüft, ob ein Auto mit gleicher Marke und Modell nicht in Autos1 existiert
    If Not Autos1.Exists(Function(x As Auto) x.Marke = myAuto2tmp.Marke And x.Modell = myAuto2tmp.Modell) Then
        ' Fügt das Auto-Objekt zur Liste Autos3 hinzu, wenn es nicht in Autos1 existiert
        Autos3.Add(myAuto2)
    End If
Next

Erläuterung des Codes:

  • Eine neue Liste Autos3 vom Typ Auto wird initialisiert.
  • Die For Each-Schleife durchläuft jedes Element (myAuto2) in der Liste Autos2.
  • Innerhalb der Schleife wird eine temporäre Kopie des aktuellen Elements erstellt, um es im nächsten Schritt zu überprüfen.
  • Die Exists-Lambda-Funktion überprüft, ob ein Auto mit derselben Marke und demselben Modell wie myAuto2tmp in der Liste Autos1 nicht existiert.
  • Falls kein entsprechendes Auto in Autos1 gefunden wird, wird das Auto-Objekt myAuto2 zur Liste Autos3 hinzugefügt. Dadurch enthält Autos3 am Ende alle Auto-Objekte aus Autos2, die nicht in Autos1 vorhanden sind.

Will man das in eine Funktion packen, steht man jedoch vor dem Problem, dass man nicht weiß welche Properties der Klasse verglichen werden sollen. In diesem Fall muss man die Klasse um eine Overrides Function erweitern, die beispielsweise .toString oder .GetHashCode ersetzt.

Die Klasse Auto, erweitert um ToString als Overrides Function müsste folgendermaßen aussehen:

' Definiert die Klasse Auto
Public Class Auto
    ' Private Felder für Marke und Modell
    Private _marke As String = String.Empty
    Private _modell As String = String.Empty

    ' Konstruktor für die Klasse Auto
    Public Sub New(ByVal marke As String, ByVal modell As String)
        ' Setzen der internen Felder mit den übergebenen Werten
        Me._marke = marke
        Me._modell = modell
    End Sub

    ' Öffentliche Eigenschaft für die Marke des Autos
    Public Property Marke() As String
        ' Getter-Methode für die Marke
        Get
            Return _marke
        End Get
        ' Setter-Methode für die Marke
        Set(ByVal value As String)
            Me._marke = value
        End Set
    End Property

    ' Öffentliche Eigenschaft für das Modell des Autos
    Public Property Modell() As String
        ' Getter-Methode für das Modell
        Get
            Return _modell
        End Get
        ' Setter-Methode für das Modell
        Set(ByVal value As String)
            Me._modell = value
        End Set
    End Property

    ' Überschreibt die ToString-Methode für die Klasse Auto
    Public Overrides Function ToString() As String
        ' Gibt eine formatierte Zeichenkette mit Marke und Modell zurück
        Return LCase(Me._marke) & "|" & LCase(Me._modell)
    End Function
End Class

Erläuterungen:

  • Die Klasse Auto verfügt über zwei private Felder: _marke und _modell, die jeweils die Marke und das Modell eines Autos speichern.
  • Der Konstruktor der Klasse nimmt die Marke und das Modell als Parameter entgegen und weist diese Werte den privaten Feldern zu.
  • Für die beiden Felder gibt es jeweils öffentliche Eigenschaften (Marke und Modell) mit entsprechenden Gettern und Settern, um den Zugriff von außen zu ermöglichen.
  • Die ToString-Methode wird überschrieben, um eine nützlichere Darstellung der Auto-Instanzen als Zeichenkette zu ermöglichen. Diese Methode gibt eine Kombination aus Marke und Modell in Kleinbuchstaben zurück, getrennt durch ein Pipe-Symbol (|).

Dann könnte man beispielsweise folgende generische Funktion zum Vergleich verwenden. 

' Definiert eine generische Funktion zum Vergleichen zweier Listen basierend auf der ToString-Methode
Private Function Compare2List_Exists(Of myType)(ByVal lst1 As List(Of myType), ByVal lst2 As List(Of myType)) As List(Of myType)
    ' Erstellt eine neue Liste, um die übereinstimmenden Elemente zu speichern
    Dim lst3 As New List(Of myType)

    ' Durchläuft jedes Element in der ersten Liste
    For Each Element As myType In lst1
        ' Temporäre Variable zur Speicherung des aktuellen Elements
        Dim tmpElement As myType = Element

        ' Überprüft, ob ein Element mit der gleichen ToString-Ausgabe in der zweiten Liste existiert
        If lst2.Exists(Function(x As myType) x.ToString = tmpElement.ToString) Then
            ' Fügt das Element zur neuen Liste hinzu, wenn ein übereinstimmendes Element gefunden wird
            lst3.Add(Element)
        End If
    Next

    ' Gibt die Liste mit den übereinstimmenden Elementen zurück
    Return lst3
End Function

Erläuterung des Codes:

  • Die Funktion Compare2List_Exists ist generisch, was bedeutet, dass sie für verschiedene Datentypen (myType) verwendet werden kann.
  • Zwei Listen lst1 und lst2 vom Typ myType werden als Parameter übergeben.
  • Eine neue Liste lst3 wird erstellt, um alle Elemente zu speichern, die in beiden Listen vorhanden sind.
  • Die Funktion durchläuft jedes Element in lst1 und verwendet eine Exists-Lambda-Funktion, um zu überprüfen, ob ein Element mit der gleichen ToString-Ausgabe in lst2 existiert.
  • Wenn ein Element in lst2 mit der gleichen ToString-Ausgabe gefunden wird, wird es zur Liste lst3 hinzugefügt.
  • Nachdem alle Elemente überprüft wurden, gibt die Funktion die Liste lst3 zurück, die alle Elemente aus lst1 enthält, für die ein Element mit der gleichen ToString-Ausgabe in lst2 existiert.

Ausgangslisten bereinigen

Spinnen wir den Gedanken noch ein Stück weiter. Die 2 Listen sollen nun nicht mehr nur auf in beiden vorkommenden Elemente untersucht werden, sondern es sollen auch in beiden Listen, die Elemente, die in beiden Listen vorkommen, entfernt werden.

Geht man davon aus, dass in Liste1 alle Elemente nur einmal vorkommen, in Liste2 es jedoch auch möglich sein kann, dass Elemente mehrfach vorkommen, könnte man die Listen folgendermaßen bereinigen:

' Definiert eine generische Funktion, um zwei Listen zu vergleichen, übereinstimmende Elemente zu speichern und diese aus den Originallisten zu entfernen
Private Function Compare2List_andRemove(Of myType)(ByRef lst1 As List(Of myType), ByRef lst2 As List(Of myType)) As List(Of myType)
    ' Liste zur Speicherung der übereinstimmenden Elemente
    Dim lst3 As New List(Of myType)

    ' Initialisierung des Index für die Durchlauf-Überprüfung
    Dim i As Integer = 0

    ' Liste zur temporären Speicherung der gefundenen Elemente
    Dim foundItems As New List(Of myType)

    ' Durchläuft die erste Liste, solange der Index kleiner als die Anzahl der Elemente in lst1 ist
    Do
        ' Speichert das aktuelle Element der ersten Liste in einer temporären Variable
        Dim tmpElement1 As myType = lst1(i)

        ' Findet alle Elemente in der zweiten Liste, die mit dem aktuellen Element der ersten Liste übereinstimmen
        foundItems = lst2.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)

        ' Überprüft, ob übereinstimmende Elemente gefunden wurden
        If foundItems.Count <> 0 Then
            ' Fügt das gefundene Element zur Liste der Übereinstimmungen hinzu
            lst3.Add(tmpElement1)

            ' Entfernt das gefundene Element aus der ersten Liste
            lst1.RemoveAt(i)

            ' Entfernt alle gefundenen Elemente aus der zweiten Liste
            For Each foundItem As myType In foundItems
                lst2.Remove(foundItem)
            Next

            ' Löscht die Liste der gefundenen Elemente für den nächsten Durchlauf
            foundItems.Clear()
        Else
            ' Erhöht den Index, wenn kein übereinstimmendes Element gefunden wurde
            i = i + 1
        End If
    Loop Until (i = lst1.Count) ' Fortsetzung der Schleife bis das Ende der ersten Liste erreicht ist

    ' Gibt die Liste der übereinstimmenden Elemente zurück
    Return lst3
End Function

Erläuterung des Codes:

  • Diese Funktion ist generisch und kann mit verschiedenen Datentypen (myType) verwendet werden.
  • Sie nimmt zwei Listen (lst1 und lst2) als Referenzparameter an, was bedeutet, dass Änderungen an diesen Listen sich auch außerhalb der Funktion widerspiegeln.
  • Eine neue Liste lst3 wird erstellt, um alle übereinstimmenden Elemente zu speichern.
  • Die Funktion durchläuft lst1, vergleicht jedes Element mit den Elementen in lst2, und wenn Übereinstimmungen gefunden werden, werden diese Elemente sowohl aus lst1 als auch aus lst2 entfernt und in lst3 gespeichert.
  • Die Schleife wird fortgesetzt, bis das Ende von lst1 erreicht ist. Beachten Sie, dass der Index i nicht erhöht wird, wenn ein Element entfernt wird, da dies die Liste verkürzt und so das Überspringen von Elementen verhindert wird.
  • Schließlich gibt die Funktion lst3 zurück, die alle Elemente enthält, die in beiden Listen gefunden und entfernt wurden.

Wenn man davon ausgeht, dass sowohl Liste1 als auch Liste2 Elemente mehrfach enthält, dann könnte man folgendermaßen vorgehen:

' Definiert eine generische Funktion, um zwei Listen zu vergleichen und übereinstimmende Elemente zu finden und aus beiden Listen zu entfernen
Private Function Compare2List_andRemove(Of myType)(ByRef lst1 As List(Of myType), ByRef lst2 As List(Of myType)) As List(Of myType)
    ' Liste zur Speicherung der übereinstimmenden Elemente
    Dim lst3 As New List(Of myType)

    ' Liste zur temporären Speicherung der gefundenen Elemente
    Dim foundItems As New List(Of myType)

    ' Durchläuft jedes Element in der ersten Liste
    For Each element As myType In lst1
        ' Speichert das aktuelle Element der ersten Liste in einer temporären Variable
        Dim tmpElement1 As myType = element

        ' Findet alle Elemente in der zweiten Liste, die mit dem aktuellen Element der ersten Liste übereinstimmen
        foundItems = lst2.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)

        ' Überprüft, ob übereinstimmende Elemente gefunden wurden
        If foundItems.Count <> 0 Then
            ' Fügt das gefundene Element zur Liste der Übereinstimmungen hinzu
            lst3.Add(tmpElement1)

            ' Entfernt alle gefundenen Elemente aus der zweiten Liste
            For Each foundItem As myType In foundItems
                lst2.Remove(foundItem)
            Next

            ' Löscht die Liste der gefundenen Elemente für den nächsten Durchlauf
            foundItems.Clear()
        End If
    Next

    ' Durchläuft jedes Element in der Liste der Übereinstimmungen
    For Each element As myType In lst3
        ' Speichert das aktuelle Element der Liste der Übereinstimmungen in einer temporären Variable
        Dim tmpElement1 As myType = element

        ' Findet alle Elemente in der ersten Liste, die mit dem aktuellen Element der Liste der Übereinstimmungen übereinstimmen
        foundItems = lst1.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)

        ' Überprüft, ob übereinstimmende Elemente gefunden wurden
        If foundItems.Count <> 0 Then
            ' Entfernt alle gefundenen Elemente aus der ersten Liste
            For Each foundItem As myType In foundItems
                lst1.Remove(foundItem)
            Next

            ' Löscht die Liste der gefundenen Elemente für den nächsten Durchlauf
            foundItems.Clear()
        End If
    Next

    ' Gibt die Liste der übereinstimmenden Elemente zurück
    Return lst3
End Function

Erläuterung des Codes:

  • Diese Funktion ist generisch und kann für verschiedene Datentypen (myType) verwendet werden.
  • Sie nimmt zwei Listen (lst1 und lst2) als Referenzparameter an, sodass Änderungen an diesen Listen sich auch außerhalb der Funktion widerspiegeln.
  • Eine neue Liste lst3 wird erstellt, um alle übereinstimmenden Elemente zu speichern.
  • Zunächst durchläuft die Funktion lst1, vergleicht jedes Element mit den Elementen in lst2, und speichert übereinstimmende Elemente in lst3.
  • Dann durchläuft sie lst3 und entfernt alle Elemente aus lst1, die in lst3 enthalten sind. Dies stellt sicher, dass Elemente, die in beiden Listen vorhanden sind, aus beiden Listen entfernt werden.
  • Schließlich gibt die Funktion die Liste lst3 zurück, die alle Elemente enthält, die in beiden Listen gefunden und entfernt wurden.

Duplikate aus Liste entfernen

Selbiges könnte man auch auf eine einzelne List(of T) anwenden, um die Dublikate der Liste zu entfernen.

' Definiert eine generische Subroutine zur Bereinigung einer Liste von Duplikaten
Private Sub ListeBereinigen(Of myType)(ByRef lst As List(Of myType))
    ' Liste zur Speicherung identifizierter Duplikate
    Dim Lst_of_Duplicates As New List(Of myType)

    ' Durchläuft jedes Element in der übergebenen Liste
    For Each element As myType In lst
        ' Speichert das aktuelle Element in einer temporären Variable
        Dim tmpElement As myType = element

        ' Findet alle Elemente in der Liste, die mit dem aktuellen Element übereinstimmen
        Dim foundItems As List(Of myType) = lst.FindAll(Function(x As myType) x.ToString = tmpElement.ToString)

        ' Überprüft, ob mehr als ein übereinstimmendes Element gefunden wurde (d.h. Duplikate existieren)
        If foundItems.Count > 1 Then
            ' Fügt das gefundene Duplikat zur Liste der Duplikate hinzu
            Lst_of_Duplicates.Add(tmpElement)
        End If
    Next

    ' Durchläuft jedes Element in der Liste der Duplikate
    For Each element As myType In Lst_of_Duplicates
        ' Speichert das aktuelle Duplikat in einer temporären Variable
        Dim tmpElement As myType = element

        ' Findet alle Elemente in der Original-Liste, die mit dem Duplikat übereinstimmen
        Dim foundItems As List(Of myType) = lst.FindAll(Function(x As myType) x.ToString = tmpElement.ToString)

        ' Überprüft, ob Duplikate gefunden wurden
        If foundItems.Count <> 0 Then
            ' Initialisiert einen Zähler, um das erste Vorkommen des Elements zu überspringen
            Dim i As Integer = 0

            ' Entfernt alle nachfolgenden Vorkommen des Duplikats aus der Liste
            For Each foundItem As myType In foundItems
                If i <> 0 Then ' Überspringt das erste Vorkommen
                    lst.Remove(foundItem)
                End If
                i = i + 1
            Next
        End If
    Next
End Sub

Erläuterung des Codes:

  • Diese Prozedur ist generisch und kann für verschiedene Datentypen (myType) verwendet werden.
  • Sie nimmt eine Liste (lst) als Referenzparameter an, sodass Änderungen an dieser Liste sich auch außerhalb der Prozedur widerspiegeln.
  • Die Prozedur identifiziert zunächst alle Duplikate in der Liste und speichert diese in Lst_of_Duplicates.
  • Anschließend durchläuft sie die Liste der Duplikate und entfernt jedes Vorkommen der Duplikate aus der Original-Liste, mit Ausnahme des ersten Vorkommens. Dadurch bleibt jedes Element nur einmal in der Liste erhalten, und alle weiteren Duplikate werden entfernt.
  • Diese Art der Duplikatbereinigung ist besonders nützlich, wenn die Elemente der Liste keine einfache Identität wie bei primitiven Datentypen haben, sondern komplexere Objekte sind, deren Gleichheit basierend auf bestimmten Eigenschaften oder Zuständen definiert ist.

Download