ConcurrentDictionary Liste

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

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

    ConcurrentDictionary Liste

    Hallo,

    in einer ConcurrentDictionary-Liste sollen HostInformationen abgelegt werden.

    Standardmäßig werden im Key keine doppelte Werte zugelassen.

    In diesem Falle handelt es sich beim Key um einen eigenen Typ "HostInfosKey". Damit "ConcurrentDictionary" doppelte Keys prüfen kann, müssen zwei Methoden vorhanden sein:

    GetHashCode
    Equals

    Quelle: stackoverflow.com/questions/27…-same-keys-more-than-once
    Die Info steht in der Antwort auf die in der Webseite gestellte Frage.


    Beide Methoden sind in der Klasse "HostInfosKey" vorhanden.

    Im Beispiel werden auch für beide Variablen myKey1 und myKey2 identische HashWerte ausgegeben.
    Dennoch werden immer in der Liste myHosts zwei Einträge mit identischem Key angelegt.

    Im Beispiel werden beide Variablen myKey1 und myKey2 mit equals miteinander verglichen und als identisch ausgegeben.

    Wenn zweimal der Befehl TryAdd mit der identischer Variable ausgeführt wird, wird in der Liste myHosts korrekterweise nur ein Eintrag angelegt:

    VB.NET-Quellcode

    1. myHosts.TryAdd(key:=myKey1, value:="systemA")
    2. myHosts.TryAdd(key:=myKey1, value:="SystemB")


    Kann mir jemand erklären, warum das so ist?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Module Module2
    2. Public Class HostInfosKey
    3. Enum HostProperty
    4. Hostname = 0
    5. PingReply = 1
    6. Ping = 2
    7. IPAddress = 3
    8. MacAddress = 4
    9. End Enum
    10. Public HostID As Double
    11. Public Name As HostProperty
    12. Overrides Function ToString() As String
    13. Return HostID.ToString & "/" & Name.ToString
    14. End Function
    15. ''' <summary>
    16. ''' Hash-Code bilden
    17. ''' </summary>
    18. ''' <returns>Integer-Wert</returns>
    19. Overrides Function GetHashCode() As Integer
    20. Dim myText = Me.ToString
    21. Dim myHash = myText.GetHashCode
    22. Console.WriteLine("String: {0}, Hash: {1}", myText, myHash)
    23. Return myHash
    24. End Function
    25. ''' <summary>
    26. ''' Vergleicht zwei HostInfosKey-Werte
    27. ''' </summary>
    28. ''' <param name="value">zu vergleichender HostInfosKey-Wert</param>
    29. ''' <returns></returns>
    30. Overloads Function Equals(value As HostInfosKey) As Boolean
    31. Return CBool(value.HostID = Me.HostID And value.Name = Me.Name)
    32. End Function
    33. End Class
    34. Public myHosts As New Concurrent.ConcurrentDictionary(Of HostInfosKey, Object)
    35. Sub Main()
    36. Dim myHosts As New Concurrent.ConcurrentDictionary(Of HostInfosKey, Object)
    37. Dim myKey1 = New HostInfosKey With {.HostID = 0, .Name = HostInfosKey.HostProperty.Hostname}
    38. Dim myKey2 = New HostInfosKey With {.HostID = 0, .Name = HostInfosKey.HostProperty.Hostname}
    39. Dim myHash1 = myKey1.GetHashCode
    40. Dim myHash2 = myKey2.GetHashCode
    41. If myKey1.Equals(myKey2) Then
    42. Console.WriteLine("identisch!")
    43. End If
    44. myHosts.TryAdd(key:=myKey1, value:="systemA")
    45. myHosts.TryAdd(key:=myKey2, value:="SystemB")
    46. Console.WriteLine("Anzahl Einträge in myHosts: {0}", myHosts.Count)
    47. Console.WriteLine("")
    48. Console.WriteLine("Auslesen aller Werte")
    49. Console.WriteLine("=========================")
    50. For Each myItem In myHosts
    51. Console.WriteLine("Eintrag: HostID: {0} / Property: {1} / Value: {2}", myItem.Key.HostID, myItem.Key.Name.ToString, myItem.Value)
    52. Next
    53. Console.WriteLine("Ende.. beliebige Taste betätigen")
    54. Console.ReadKey()
    55. End Sub
    56. End Module
    @BigBen2003 Es sieht so aus, als wäre das ein Framework-Fehler.
    Ich hab Deinen Code probiertr und sdann nochmal mit .GetOrAdd(...), geht auch nicht. ;(
    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!
    @Bluespide Jou.
    @BigBen2003 So sieht das dann aus:

    VB.NET-Quellcode

    1. Public Overloads Overrides Function Equals(obj As Object) As Boolean
    2. Dim value As HostInfosKey = CType(obj, HostInfosKey)
    3. Return CBool(value.HostID = Me.HostID And value.Name = Me.Name)
    4. End Function
    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!
    Der Fehler liegt darin, dass Equals überladen wurde, statt es zu überschreiben - so gehts:

    VB.NET-Quellcode

    1. Public Overrides Function Equals(obj As Object) As Boolean
    2. Dim value = TryCast(obj, HostInfosKey)
    3. Return value IsNot Nothing AndAlso value.HostID = Me.HostID AndAlso value.Name = Me.Name
    4. End Function

    @ErfinderDesRades Mein Code ist aus der MSDN.
    Freundlicherweise funktionieren unsere beiden Codes. :D
    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!

    ErfinderDesRades schrieb:

    odr
    msdn.microsoft.com/de-de/library/336aedhh(v=vs.100).aspx
    Nur dass das Studio die Attribute umgedreht hat.
    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!
    Du meinst die Modifizierer (Public Overrides Overloads), odr? Weil Attribute sind was anneres.

    Darauf bezog sich meine Beurteilung als "GrottenCode" aber garnet.
    Sondern dass die kein TryCast verwenden - sogar gar keinen Cast, sondern das langsamere CType.
    Und dass da ein Boolean-Ausdruck nochmal ganz unnötig mit CBool() behandelt wird. Und die TeilAusdrücke werden dort mit And verknüpft statt mit AndAlso.
    Und wenn das HostInfosKey mal mit einem FileInfo verglichen wird (was ja eine legale Operation ist), gibts eine InvalidCastException.
    Derlei Ineffizienz und sogar Fehlverhalten bezeichne ich mit GrottenCode - vor allem, wenns von MS ihmselbst kommen soll - die müsstens eiglich können.
    In einem Projekt wurde eine Liste mit einem Benutzerdefiniertem Key-Typ erstellt (Test-Uniy -Klasse siehe unten).

    In der Klasse mit dem Benutzerdefiniertem Key-Typ wurden Equals in allen möglichen Varianten angelegt. Dennoch werden mehrfache identische Key-Einträge nicht erkannt.

    Zum Testen muss im Visual Studio ein VB.net-Untity-TestProjekt erstellt werden und in der Test-Klasse der nachstehende Code eingesetzt werden.

    Vielleicht findet ja jemand eine Lösung?

    Solange keine doppelten Key-Einträge mit benutzerdefinierten Key-Typen erkannt werden, muss als Key immer ein "String"-als Typ herhalten, in dem anschließend die Zusammengesetzte Information von HostID und "Name" verglichen werden.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Text
    2. Imports Microsoft.VisualStudio.TestTools.UnitTesting
    3. ''' <summary>
    4. ''' Test: Dictionary HostInfosKey
    5. ''' </summary>
    6. <TestClass()> Public Class HostInfosKey
    7. Class TestKey
    8. Public HostID As Double
    9. Public Name As String
    10. Public ReadOnly Property HostIDName As String
    11. Get
    12. Return Me.ToString()
    13. End Get
    14. End Property
    15. Public Overloads Function Equals(objA As Object, objB As Object)
    16. If objA IsNot Nothing AndAlso objB IsNot Nothing Then
    17. Return Equals(valA:=CType(objA, TestKey), valB:=CType(objB, TestKey))
    18. Else
    19. Return False
    20. End If
    21. End Function
    22. Public Overloads Function Equals(Obj As Object) As Boolean
    23. If Obj IsNot Nothing Then
    24. If Obj.GetType.Equals(Me.GetType) Then
    25. Return Equals(value:=CType(Obj, TestKey))
    26. End If
    27. End If
    28. Return False
    29. End Function
    30. Public Overloads Function Equals(valA As TestKey, ValB As TestKey) As Boolean
    31. Return valA.Equals(ValB)
    32. End Function
    33. Public Overloads Function Equals(value As TestKey) As Boolean
    34. Return HostID.Equals(value.HostID) And Name.Equals(value.Name)
    35. End Function
    36. Public Overloads Function GetHashCode() As Integer
    37. Return Me.ToString.GetHashCode
    38. End Function
    39. Public Shadows Function [ToString]() As String
    40. Return HostID.ToString & "/" & Name
    41. End Function
    42. End Class
    43. <TestCategory("Scanning HostInfos Concurent-Dictionary")>
    44. <TestMethod()> Public Sub Initialize()
    45. Dim nameHostProperty = "HostName"
    46. Dim myHosts As New Concurrent.ConcurrentDictionary(Of TestKey, Object)
    47. Assert.IsTrue(myHosts.TryAdd(key:=New TestKey With {.HostID = 0, .Name = nameHostProperty}, value:="systemA"))
    48. Assert.IsTrue(myHosts.TryAdd(key:=New TestKey With {.HostID = 1, .Name = nameHostProperty}, value:="systemB"))
    49. ' HostID 0 nochmals einfügen, darf nicht erfolgreich sein.
    50. Assert.IsTrue(myHosts.TryAdd(key:=New TestKey With {.HostID = 0, .Name = nameHostProperty}, value:="systemC"), "")
    51. Dim myItems = From hosts In myHosts Select hosts Order By hosts.Key.HostIDName
    52. Dim lastID As String = String.Empty
    53. For Each item In myItems
    54. If item.Key.HostIDName = lastID Then
    55. Assert.Fail("Mehrfache HostIDName-Einträge", "HostIDName: " & item.Key.HostIDName & " ist mehrfach vorhanden")
    56. Else
    57. lastID = item.Key.HostIDName
    58. End If
    59. Next
    60. End Sub
    61. End Class
    @BigBen2003 Überlege Dir mal, was genau da passiert:
    Es wird nachgesehen, ob zwei Instanzen denselben physischen Speicher belegen (Equals()) Dort musst Du eingreifen und den Inhalt der Instanzen überprüfen.
    Also etwa so:

    VB.NET-Quellcode

    1. Public Overloads Function Equals(objA As Object, objB As Object)
    2. If objA Is Nothing OrElse objB Is Nothing Then
    3. Return False
    4. End If
    5. Dim a = CType(objA, TestKey)
    6. Dim b = CType(objB, TestKey)
    7. Return a.HostID = b.HostID AndAlso a.Name = b.Name
    8. End Function
    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!
    @ErfinderDesRades: Bei Reference-Types erzeugt CType exakt den selben Code wie DirectCast, also 0 performance unterschied. Bei ValueTypes siehts interessantererweise anders aus, wenn man von object->primitive hat, dann wird dabei Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger aufgerufen(beim cast zu integer), wobei ich davon nur die .Net Core version und mono version überprüfen konnte. DirectCast geht nur, wenn es bereits genau derselbe Typ ist... Es gibt also kein VB.Net äquivalent zu "(int)floatVal", denn CInt macht dasselbe wie CType und ruft die komische compilerservices funktion auf und DirectCast geht bei soetwas gar nicht.
    Bei CType von object->custom valuetype passiert etwas ganz interessantes(conversion a->b. Er erzeugt einen Code(in IL), der inetwa dem hier entspricht:

    C#-Quellcode

    1. object a = 1.2f;
    2. MyStruct b = default(MyStruct)
    3. if (a != null)
    4. {
    5. b = (MyStruct)a;
    6. }

    Natürlich schlägt der Code hier fehl(InvalidCast) ohne implizite Konvertierung von float zu MyStruct, soll aber ja nur veranschaulichen was im Hintergrund passiert. Aber da DirectCast bei "Nothing" fehlschlagen würde ist hier CType tatsächlich die bessere Wahl und ziemlich performant. Selbst schreiben könnte trotzallem noch etwas performanter sein, aber müsste man überprüfen, da die erzeugten Codes doch ziemlich unterschiedlich sind...

    Viel mehr als über die Performance von CType würde ich mir bei equals jedoch darüber beschweren, dass z.B. bei einem vergleich von (1).Equals(1.2F) true rauskommen würde, da 1.2f über CType zu 1 wird.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    am meisten beschwere ich mich darüber, dass der von Rod gezeigte Code eine Exception verursacht, wenn mit einem inkompatiblem Typ verglichen wird.
    Mit TryCast wär das nicht passiert.

    Weiters beschwerte ich mich über das üflüssige CBool() und über das And - was seit Einführung von AndAlso nur noch als BitOperator verwendet werden sollte.
    Wie gesagt: Wenn das von MS ist, haben die da aber ordentlich daneben gegriffen.
    Das mit der exception ist wahr. Hatte mich auch irwie verlesen und interpretiert "wenigstens DirectCast hätte man verwenden können" hast aber gar nicht^^
    Und And fast nur als bit Operation. Es gibt Ausnahmen, wenn in jedem Fall auch der sekundäre operand ausgewertet werden soll...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Kannst du dafür bitte ein konkretes Beispiel nennen?
    Denn ich kann weder CType operator für base/derivate class machen, noch für object. Und für alle anderen Fälle, die mir ein gefallen sind erzeugt DirectCast gar keinen Code, weil man ja nicht von beliebigem Typ a zu beliebigem Typ b konvertieren kann...Ansonsten wenn es sich um eine sub/superclass handelt, erzeugt er bei up/downcast wieder denselben code..
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    VB.NET-Quellcode

    1. Public Sub String_CType()
    2. Dim s As String = {"a"c, "s"c, "d"c, "f"c} 'widening CType-Operator (implizit)
    3. Dim chars = CType("asdf", Char()) 'narrowing CType-Operator (explizit aufzurufen)
    4. 'chars = DirectCast("asdf", Char()) 'not valid
    5. 'chars = TryCast("asdf", Char()) 'not valid
    6. Dim o As Object = "asdf"
    7. chars = TryCast(o, Char()) ' valid, -> Nothing
    8. End Sub