Syntax für die Verwendung eines eigenen Comparers für String Vergleich.

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 28 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Syntax für die Verwendung eines eigenen Comparers für String Vergleich.

    Hi,

    ich habe einen eigenen Comparer geschrieben:

    VB.NET-Quellcode

    1. Public Class KeyCompare
    2. Implements IComparer(Of String)
    3. Public Function Compare(KeyOld As String, KeyNew As String) As Integer Implements IComparer(Of String).Compare
    4. ...


    Das Ding funktioniert auch prima, etwa beim Arrray.Sort

    VB.NET-Quellcode

    1. Array.Sort(SubkeyList, New KeyCompare())


    Nun möchte ich abfragen, ob zwei Keys in der richtigen Sortierreihenfolge sind. Blöderweise kriege ich das nicht hin. Z.B.:

    VB.NET-Quellcode

    1. If KeyCompare.Compare(HoldKey, Subkey) = -1 Then


    liefert die Fehlermeldung:

    Fehler 1 Der Verweis auf einen nicht freigegebenen Member erfordert einen Objektverweis.

    Muss man den Comparer vorher instanzieren? Meine diversen Versuche "New" anzubringen sind aber auch gescheitert.

    Kann man mir jemand auf die Sprünge helfen, wie die Syntax richtig lauten muss?

    LG
    Peter
    so gehts als Singleton:

    VB.NET-Quellcode

    1. Public Class KeyCompare
    2. Implements IComparer(Of String)
    3. Public Shared ReadOnly Instance As New KeyCompare
    4. Private Sub New()
    5. End Sub
    6. Public Function Compare(ByVal x As String, ByVal y As String) As Integer Implements System.Collections.Generic.IComparer(Of String).Compare
    7. End Function
    8. End Class
    Ich habe jetzt versucht die 1. Methode von "blaze" umzusetzen, also die Methode "CompareKey" sowohl für den Vergleich als auch für den StringComparer bereitzustellen.

    Das Problem ist, dass ich wohl nicht so ganz verstehe, wie man eine instanzenübergreifende Funktion schreibt.

    Ich habe das mit "Public" in der frmMain versucht:

    VB.NET-Quellcode

    1. Public Function CompareKey(KeyOld As String, KeyNew As String) As String
    2. ...
    3. Public Class KeyCompare
    4. Implements IComparer(Of String)
    5. Public Function Compare(KeyOld As String, KeyNew As String) As Integer Implements IComparer(Of String).Compare
    6. Select Case CompareKey(KeyOld, KeyNew)
    7. ...


    Der Aufruf in der Klasse KeyCompare findet diese Funktion aber nicht:

    Fehler 1 "CompareKey" wurde nicht deklariert. Auf das Objekt kann aufgrund seiner Schutzstufe möglicherweise nicht zugegriffen werden.

    Wie mache ich das also richtig?

    LG
    Peter
    @Peter329
    @~blaze~ hat das so gemeint:

    VB.NET-Quellcode

    1. Public Class KLASSENNAME
    2. Public Shared Function CompareKey(KeyOld As String, KeyNew As String) As String
    3. ' returne den gewünschten String
    4. End Function
    5. ' ...
    6. End Class

    VB.NET-Quellcode

    1. ' Aufruf:
    2. MessageBox.Show(KLASSENNAME.CompareKey(KeyOld, KeyNew)

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    okie dokie ... Ich habe die Funktion "CompareKey" als public shared in die Klasse "KeyCompare" mit aufgenommen. Und das funzt jetzt prima! Na, meine Namensvergabe werde ich vielleicht besser noch mal überdenken. :)

    Recht herzlichen Dank also an alle Ratgeber! Und einen schönen Abend!

    LG
    Peter
    Dann hast du jetzt 2 Compare-Methoden in deiner KeyComparer-Klasse? So ist das eiglich nicht gedacht.

    Gedacht ists, so wie ichs dir hingeschrieben habe. Und mit der Methode kannst du überall vergleichen, weil die Shared Instance global zugreifbar ist.

    In genau derselben Weise arbeiten auch die Public Shared Instanzen der StringComparer-Klasse - du erinnerst dich vlt..

    Tatsächlich ist dein KeyComparer ja nichts anneres als ein weiterer StringComparer, was dich drauf bringen könnte, statt IComparer(Of String) zu implementieren, stattdessen von StringComparer zu erben.

    Wobei das in diesem Fall ungefähr aufs selbe hinausläuft...
    Gedacht hatte ich es so:

    VB.NET-Quellcode

    1. Public Class SomeStringComparer
    2. Implements IComparer(Of String)
    3. Public Shared Function Compare(a As String, b As String) As Integer
    4. If a Is Nothing Then Throw New ArgumentNullException("a")
    5. If b Is Nothing Then Throw New ArgumentNullException("b")
    6. For i As Integer = 0 To Math.Min(a.Length, b.Length) - 1
    7. Dim cmp As Integer = a(i).CompareTo(b(i))
    8. If cmp <> 0 Then Return cmp
    9. Next
    10. Return a.Length.CompareTo(b.Length)
    11. End Function
    12. Public Function Compare(a As String, b As String) As Integer Implements IComparer(Of String).Compare
    13. Return Compare(a, b)
    14. End Function
    15. End Class


    oder die Variante, die bereits ErfinderDesRades vorgeschlagen hat. Die ist vmtl. sowieso besser, weil du vom SomeStringComparer eh nur ein Singleton brauchst.

    Gruß
    ~blaze~
    @blaze

    Also irgendwie sieht mein Coding nicht so grundverschieden von deinem aus.

    @EDR

    Nachdem ich mich jetzt erst mal schlau gemacht habe, was denn ein "Singleton" ist, habe ich versucht dein Coding nachzuvollziehen. Wenn ich das richtig verstehe, soll mit einem Singleton genau eine Instanz des Comparers global verfügbar gemacht werden. Auch wenn ich das noch nicht so richtig nachvollziehen kann.

    So sieht das bei mir jetzt aus:

    VB.NET-Quellcode

    1. Public Class KeyCompare2
    2. Implements IComparer(Of String)
    3. Public Shared ReadOnly Instance As New KeyCompare2
    4. Private Sub New()
    5. End Sub
    6. Public Function Compare(ByVal KeyOld As String, ByVal KeyNew As String) As Integer _
    7. Implements System.Collections.Generic.IComparer(Of String).Compare
    8. ...
    9. Return 0 bzw. -1 bzw 1
    10. End Function
    11. End Class


    Nun will ich dieses Konstrukt für den ArraySort aufrufen:

    VB.NET-Quellcode

    1. Array.Sort(SubkeyList, New KeyCompare2())


    Fehler 1 "CheckRegistry.KeyCompare2.Private Sub New()" ist in diesem Kontext nicht zugreifbar, da es "Private" ist.

    "CheckRegistry" ist übrigens der Name der Hauptklasse.

    Auch der direkte Vergleich klappt nicht.

    VB.NET-Quellcode

    1. If Not KeyCompare2.Compare(HoldKey, Subkey) - 1 Then Debug.Print("LT")


    Fehler 1 Der Verweis auf einen nicht freigegebenen Member erfordert einen Objektverweis.

    Wie rufe ich denn das Ding jetzt auf?

    LG
    Peter

    VB.NET-Quellcode

    1. Array.Sort(SubkeyList, New KeyCompare2())

    VB.NET-Quellcode

    1. If Not KeyCompare2.Compare(HoldKey, Subkey) - 1 Then Debug.Print("LT")

    =>

    VB.NET-Quellcode

    1. Array.Sort(SubkeyList, KeyCompare2.Instance)

    VB.NET-Quellcode

    1. If Not KeyCompare2.Instance.Compare(HoldKey, Subkey) - 1 Then Debug.Print("LT")

    Übrigens: Ich glaube, das sollte = 1, anstelle von - 1 heißen.

    Uuund übrigens: Es ist nirgends definiert, dass die Compare-Funktion nur 0, -1 und 1 zurückgeben darf. Erlaubt ist alles, was kleiner als 0 und größer als 0 ist, bzw. 0 bei Gleichheit. Und der Code, der die Funktion aufruft, sollte das auch so behandeln. Also nicht auf = 1 prüfen, sondern auf > 0.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Danke erst mal für die Hilfe ... jetzt funktioniert auch das alternative Coding.

    Was mich überrascht: die "Singleton" Lösung ist deutlich langsamer als meine erste Lösung.

    Hier das komplette Coding:

    VB.NET-Quellcode

    1. Public Class KeyCompare
    2. Implements IComparer(Of String)
    3. Public Function Compare(KeyOld As String, KeyNew As String) As Integer _
    4. Implements IComparer(Of String).Compare
    5. Return CompareKey(KeyOld, KeyNew)
    6. End Function
    7. Public Shared Function CompareKey(KeyOld As String, KeyNew As String) As Integer
    8. 'Compare Registry keys
    9. Dim strWork0() As String = KeyOld.Split("\"c)
    10. Dim strWork1() As String = KeyNew.Split("\"c)
    11. Dim i As Integer = -1
    12. Do
    13. i += 1
    14. If UBound(strWork0) = i - 1 AndAlso UBound(strWork1) = i - 1 Then Return 0
    15. If UBound(strWork0) = i - 1 Then Return -1
    16. If UBound(strWork1) = i - 1 Then Return 1
    17. strWork0(i) = strWork0(i).ToUpper
    18. strWork1(i) = strWork1(i).ToUpper
    19. If strWork0(i) < strWork1(i) Then Return -1
    20. If strWork0(i) > strWork1(i) Then Return 1
    21. Loop
    22. End Function
    23. End Class
    24. Public Class KeyCompare2
    25. Implements IComparer(Of String)
    26. Public Shared ReadOnly Instance As New KeyCompare2
    27. Private Sub New()
    28. End Sub
    29. Public Function Compare(ByVal KeyOld As String, ByVal KeyNew As String) As Integer _
    30. Implements System.Collections.Generic.IComparer(Of String).Compare
    31. 'Compare Registry keys
    32. Dim strWork0() As String = KeyOld.Split("\"c)
    33. Dim strWork1() As String = KeyNew.Split("\"c)
    34. Dim i As Integer = -1
    35. Do
    36. i += 1
    37. If UBound(strWork0) = i - 1 AndAlso UBound(strWork1) = i - 1 Then Return 0
    38. If UBound(strWork0) = i - 1 Then Return -1
    39. If UBound(strWork1) = i - 1 Then Return 1
    40. strWork0(i) = strWork0(i).ToUpper
    41. strWork1(i) = strWork1(i).ToUpper
    42. If strWork0(i) < strWork1(i) Then Return -1
    43. If strWork0(i) > strWork1(i) Then Return 1
    44. Loop
    45. End Function
    46. End Class


    KeyCompare braucht für den Abgleich von 850.000 Sätzen ca 5 Sekunden, während KeyCompare2 dafür 6 Sekunden benötigt. Das sind 20% mehr an Laufzeit.

    Irgendwelche Ideen woran das liegen könnte? Hab ich irgendwas falsch gemacht?

    LG
    Peter

    P.S.: Irgendwie spielen die Farben im Code Listing verrückt ... warum auch immer ... muss wohl mit dem .Split zusammen hängen ...
    verbugter Code-Highlighter
    Das Problem ist der Backslash vor dem Anführungszeichen.
    Du kannst dem hier im Forum entgegenwirken, indem Du ans Ende der Zeile ein Kommentar mit einem Anführungszeichen setzt:

    VB.NET-Quellcode

    1. Dim a = "\"c '"
    2. Dim b = 0

    Wovon die Laufzeit abhängt, hängt vom genauen Aufruf ab. Dazu müsstest Du den ganzen Code posten.
    Die Variante mit dem Singleton müsste ein klein Wenig schneller sein, weil nur einmal ein Objekt erstellt wird (und nicht für jeden Aufruf ein neues).
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Na gut, da machen wir halt strWork0.GetUpperBound(0) draus, damit die arme Seele Ruhe hat. Und damit kommen wir wieder zum Thema. :)

    Die Aufrufe sehen wie folgt aus:

    VB.NET-Quellcode

    1. Array.Sort(SubkeyList, New KeyCompare())
    2. 'Array.Sort(SubkeyList, KeyCompare2.Instance)
    3. If Not KeyCompare.CompareKey(HoldKey, Subkey) = -1 Then
    4. 'If Not KeyCompare2.Instance.Compare(HoldKey, Subkey) = -1 Then


    Ich habe für die Messungen nur die Apostrophes ausgetauscht.

    Nach der reinen Lehre hätte ich jetzt auch gedacht, dass die Singleton Lösung schneller sein sollte, weil nur einmal instanziert wird. Aber es ist eben genau anders herum ...

    Jetzt irgendwelche Ideen?

    LG
    Peter
    zunächstmal ühaupt den Code ordentlich machen. Die Längen-Abfrage gehört nicht in die Schleife hinein

    VB.NET-Quellcode

    1. Public Class KeyCompare2
    2. Implements IComparer(Of String)
    3. Public Shared ReadOnly Instance As New KeyCompare2
    4. Private Sub New()
    5. End Sub
    6. Public Function Compare(ByVal KeyOld As String, ByVal KeyNew As String) As Integer _
    7. Implements System.Collections.Generic.IComparer(Of String).Compare
    8. Dim segments0() As String = KeyOld.Split("\"c)
    9. Dim segments1() As String = KeyNew.Split("\"c)
    10. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    11. Dim s0 = segments0(i).ToUpper
    12. Dim s1 = segments1(i).ToUpper
    13. If s0 > s1 Then Return 1
    14. If s0 < s1 Then Return -1
    15. Next
    16. Return segments1.Length - segments0.Length
    17. End Function
    18. End Class

    Weiters glaube ich aber, dass man statt des VergleichsOperators ieine der vielen Compare-Methoden heranziehen kann, mit IgnoreCase oder so.
    Aber dafür fällt es mir schwer, Tests zu schreiben, man muss dafür letztlich einen RiesenHaufen Strings generieren und untereinander vergleichen.
    Kannst du vlt. deine Methode posten, mit der du die vielen RegKey-Strings in eine Liste füllst? Ich bin zu faul, selbst eine zu schreiben.

    Auch handelt es sich um einen segment-weisen Vergleich, und da sollte man prüfen, ob man die Segmentiereung nicht weglassen kann, und gleich die Komplett-Strings vergleichen. Nee, kann man leider nicht :(

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    zunächstmal ühaupt den Code ordentlich machen.


    Na, ob der Code jetzt wirklich besser ist? Ich meine, er ist erst mal falsch! Denn der Vergleich

    "A\b" mit "A\b\c\d"

    liefert bei mir -1 ... während deine Routine 2 zurückgibt. :)

    Ich werd das mal in deinem Sinne korrigieren und dann noch mal testen. Aber da die Routine ja in beiden Verfahren in gleicher Weise verwendet wird, verstehe ich eigentlich nicht so recht die Logik, warum das nun etwas an den unterschiedlich langen Laufzeiten ändern sollte.

    Wegen der Routine zum extrahieren der Keys schaue ich mal, ob ich das geeignet abspecken kann.

    LG
    Peter
    ups - muss heißen:

    VB.NET-Quellcode

    1. Return segments0.Length - segments1.Length


    so, hier mal 5 Segment-Comparisons und ein Test dazu.
    Alle ausser der ersten sortieren in glaub deinem Sinne:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim WrongComparison As Comparison(Of String) = _
    2. Function(x, y)
    3. Dim segments0() As String = x.Split("\"c)
    4. Dim segments1() As String = y.Split("\"c)
    5. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    6. Dim s0 = segments0(i).ToUpper
    7. Dim s1 = segments1(i).ToUpper
    8. If s0 > s1 Then Return 1
    9. If s0 < s1 Then Return -1
    10. Next
    11. Return segments1.Length - segments0.Length
    12. End Function
    13. Dim comparison1 As Comparison(Of String) = _
    14. Function(x, y)
    15. Dim segments0() As String = x.Split("\"c)
    16. Dim segments1() As String = y.Split("\"c)
    17. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    18. Dim s0 = segments0(i).ToUpper
    19. Dim s1 = segments1(i).ToUpper
    20. If s0 > s1 Then Return 1
    21. If s0 < s1 Then Return -1
    22. Next
    23. Return segments0.Length - segments1.Length
    24. End Function
    25. Dim comparison2 As Comparison(Of String) = _
    26. Function(x, y)
    27. Dim segments0() As String = x.Split("\"c)
    28. Dim segments1() As String = y.Split("\"c)
    29. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    30. Dim val = String.Compare(segments0(i).ToUpper, segments1(i).ToUpper, StringComparison.Ordinal)
    31. If val <> 0 Then Return val
    32. Next
    33. Return segments0.Length - segments1.Length
    34. End Function
    35. Dim comparison3 As Comparison(Of String) = _
    36. Function(x, y)
    37. Dim segments0() As String = x.Split("\"c)
    38. Dim segments1() As String = y.Split("\"c)
    39. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    40. Dim val = String.Compare(segments0(i), segments1(i), StringComparison.OrdinalIgnoreCase)
    41. If val <> 0 Then Return val
    42. Next
    43. Return segments0.Length - segments1.Length
    44. End Function
    45. Dim comparison4 As Comparison(Of String) = _
    46. Function(x, y)
    47. Dim segments0() As String = x.Split("\"c)
    48. Dim segments1() As String = y.Split("\"c)
    49. For i = 0 To Math.Min(segments0.Length, segments1.Length) - 1
    50. Dim val = StringComparer.OrdinalIgnoreCase.Compare(segments0(i), segments1(i))
    51. If val <> 0 Then Return val
    52. Next
    53. Return segments0.Length - segments1.Length
    54. End Function
    55. Private Sub Test()
    56. Dim lst = GetKeyNames(Microsoft.Win32.Registry.CurrentUser)
    57. Dim lst2 = lst.ToList
    58. lst.Sort(comparison1)
    59. lst2.Sort(comparison4)
    60. Dim b = lst.SequenceEqual(lst2)
    61. End Sub

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    eine kurze Frage: So ein Comparer muss nicht zwingend -1, 0 oder +1 zurückliefern? Es ist also zulässig einfach eine negative ganze Zahl, Null oder eine positive ganze Zahl für "kleiner", "gleich" bzw. "größer" abzuliefern?

    Peter329 schrieb:

    -1, 0 oder +1
    wird beim Sortieren ausgewertet, probiere einfach, ob andere Werte auch funktionieren.
    Ich würde nur -1, 0, 1 verwenden.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!