EventHandler empfängt Eventfeuerung nicht zuverlässig

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

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

    EventHandler empfängt Eventfeuerung nicht zuverlässig

    Hallo zusammen.

    Ich versuche mir gerade in WinForms eine Vereinfachung der INotifyPropertyChanged-Implementierung zu basteln, scheitere jedoch am zuverlässigen Abfangen des Events.
    Dazu habe ich eine Testklasse, die eine generische und eine nichtgenerische Property hat. Beides sind nichtprimitive Klassentypen, beide haben ein abfeuerbereites Event, beide können angewiesen werden, ihr Event zu feuern.
    Die Oberklasse stellt EventHandler bereit, um die Events der Properties zu Testzwecken abzufangen.

    Lasse ich den Code ab Zeile#8 laufen, werden die Events der Properties gefeuert und von den EventHandlern abgefangen. Damit ist klar, dass die Events an sich funktionieren, die EventHandler ebenfalls.
    Wird jedoch Z#15 ausgeführt, sagt mir der Debugger zwar, dass der Property-Setter ab Z#64 aufgerufen wird und auch Z#67 und Z#71-74 aufgerufen werden. Aber der zuständige EventHandler in Z#33 wird nicht aufgerufen, verpasst also seinen Einsatz. Was habe ich übersehen?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class FrmMain
    2. Dim TestInstance As Test = Nothing
    3. Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. TestInstance = New Test With {.GenericClassInstance = 123}
    5. End Sub
    6. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    7. TestInstance.Initialize()
    8. TestInstance.RaiseTestEventOfNonGenericClassInstance()
    9. TestInstance.RaiseTestEventOfGenericClassInstance()
    10. End Sub
    11. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    12. TestInstance.GenericClassInstance = 456
    13. End Sub
    14. End Class
    15. Public Class Test
    16. Property GenericClassInstance As New GenericClass(Of Integer)
    17. Property NonGenericClassInstance As New NonGenericClass
    18. Public Sub Initialize()
    19. AddHandler GenericClassInstance.TestEvent, AddressOf GenericClassInstanceTestEventHandler
    20. AddHandler NonGenericClassInstance.TestEvent, AddressOf NonGenericClassInstanceTestEventHandler
    21. End Sub
    22. Private Sub NonGenericClassInstanceTestEventHandler(sender As Object, e As EventArgs)
    23. MessageBox.Show("nongeneric event fired")
    24. End Sub
    25. Private Sub GenericClassInstanceTestEventHandler(sender As Object, e As EventArgs)
    26. MessageBox.Show("generic event fired")
    27. End Sub
    28. Public Sub RaiseTestEventOfNonGenericClassInstance()
    29. NonGenericClassInstance.RaiseTestEvent()
    30. End Sub
    31. Public Sub RaiseTestEventOfGenericClassInstance()
    32. GenericClassInstance.RaiseTestEvent()
    33. End Sub
    34. End Class
    35. Public Class NonGenericClass
    36. Public Event TestEvent As EventHandler
    37. Public Sub RaiseTestEvent()
    38. MessageBox.Show("nongeneric event firing")
    39. RaiseEvent TestEvent(Me, New EventArgs)
    40. End Sub
    41. End Class
    42. Public Class GenericClass(Of T)
    43. Public Event TestEvent As EventHandler
    44. Private _Value As T
    45. Public Property Value As T
    46. Get
    47. Return _Value
    48. End Get
    49. Set
    50. If _Value.Equals(Value) Then Return
    51. _Value = Value
    52. RaiseTestEvent()
    53. End Set
    54. End Property
    55. Public Sub RaiseTestEvent()
    56. MessageBox.Show("generic event firing")
    57. RaiseEvent TestEvent(Me, New EventArgs)
    58. End Sub
    59. Public Shared Widening Operator CType(Value As GenericClass(Of T)) As T
    60. Return Value.Value
    61. End Operator
    62. Public Shared Widening Operator CType(Value As T) As GenericClass(Of T)
    63. Return New GenericClass(Of T) With {.Value = Value}
    64. End Operator
    65. Public Overrides Function ToString() As String
    66. Return Value.ToString
    67. End Function
    68. End Class



    ##########

    Führt man das Ganze mit einer nongenerischen Property aus, geht es. Hier ein einfaches Beispiel dafür:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class FrmMain
    2. Dim EasyTest As EasyTest
    3. Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. EasyTest = New EasyTest With {.TestProperty = 123}
    5. AddHandler EasyTest.TestEvent, AddressOf TestEventHandler
    6. End Sub
    7. Private Sub TestEventHandler()
    8. MessageBox.Show("got it")
    9. End Sub
    10. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    11. EasyTest.TestProperty = 222
    12. End Sub
    13. End Class
    14. Public Class EasyTest
    15. Private _TestProperty As Integer
    16. Public Event TestEvent()
    17. Public Property TestProperty As Integer
    18. Get
    19. Return _TestProperty
    20. End Get
    21. Set
    22. _TestProperty = Value
    23. RaiseTestEvent()
    24. End Set
    25. End Property
    26. Public Sub RaiseTestEvent()
    27. RaiseEvent TestEvent()
    28. End Sub
    29. End Class


    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „VaporiZed“ ()

    Ok, Problem eingegrenzt. Der überladene CType-Operator ist schuld, weil er eine neue Instanz erstellt. Lässt man den weg und manipuliert den Value-Wert der Property direkt, wird auch das Event abgefangen, da ja noch mit der richtigen Instanz gearbeitet wird. Muss ich nur noch rausfinden, wie ich das umgehen kann, sodass ich nicht den Umweg über den PropertyValue gehen muss, sondern über die Property selbst.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @VaporiZed Ich hab mal die Button-Handler anders bestückt und die MessageBoxen in die Console schreibnen lassen:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim TestInstance As Test = Nothing
    3. Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. TestInstance = New Test With {.GenericClassInstance = 123}
    5. TestInstance.Initialize()
    6. End Sub
    7. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    8. TestInstance.RaiseTestEventOfNonGenericClassInstance()
    9. 'TestInstance.NonGenericClassInstance = New NonGenericClass()
    10. 'TestInstance.NonGenericClassInstance.RaiseTestEvent()
    11. End Sub
    12. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    13. 'TestInstance.GenericClassInstance = 456
    14. TestInstance.RaiseTestEventOfGenericClassInstance()
    15. End Sub
    16. End Class
    ==>

    Ausgabe schrieb:

    Quellcode

    1. nongeneric event firing
    2. nongeneric event fired
    3. generic event firing
    4. generic event fired
    Und:
    Statt New EventArgs machst Du EventArgs.Empty.
    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!
    Nun, das hilft mir jetzt nicht weiter. Wie gesagt, das explizite Feuern der Events funktioniert bei mir. Nur eben über den Property-Setter-Aufruf nicht, bei Dir (die noch auskommentierte) Zeile#13. Aber wie geschrieben. Liegt eben daran, das CType ja ne neue GenericClass-Instanz erzeugt und daher der Bezug für den EventHandler verloren geht. Ich muss daher diesen Teil überarbeiten, damit ich weiterkomme.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Jo genau da liegt das Problem. Du erzeugst eine neue Instanz, die andere wird vom GC in die ewigen Jagdgründe befördert und alle EventHandler sind futsch. Leider ist C# beim Operator Überladen nicht sehr frei. In C++ könnstest du einfach den Assignment Operator überladen (=) und fertig.
    @VaporiZed Das Shared ist mir durch die Lappen gegangen. :whistling:
    Wenn Du Dir eine Shared Variable this mit dem Inhalt Me anlegst, kannst Du im Operator darauf zugreifen.
    Problematisch wird es dann bei mehreren Instanzen der generischen Klasse, da müsste ne Shared List(Of xxx) oder ein Dictionary(Of xxx, yyy) her, um die richtige Instanz rauszufischen.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class GenericClass(Of T)
    2. Public Event TestEvent As EventHandler
    3. Private _Value As T
    4. Private Shared this As GenericClass(Of T)
    5. Public Sub New()
    6. this = Me
    7. End Sub
    8. Public Property Value As T
    9. Get
    10. Return _Value
    11. End Get
    12. Set(value As T)
    13. If _Value.Equals(value) Then Return
    14. _Value = value
    15. RaiseTestEvent()
    16. End Set
    17. End Property
    18. Public Sub RaiseTestEvent()
    19. Console.WriteLine("generic event firing")
    20. RaiseEvent TestEvent(Me, EventArgs.Empty)
    21. End Sub
    22. Public Shared Widening Operator CType(Value As GenericClass(Of T)) As T
    23. Return Value.Value
    24. End Operator
    25. Public Shared Widening Operator CType(Value As T) As GenericClass(Of T)
    26. this.Value = Value
    27. 'Return New GenericClass(Of T) With {.Value = Value}
    28. Return this
    29. End Operator
    30. Public Overrides Function ToString() As String
    31. Return Value.ToString
    32. End Function
    33. End Class

    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!
    Ja, das habe ich inzwischen auch ausprobiert, doch das nächste Problem kommt gleich danach. Das ist aber eigentlich schon das Thema für den nächsten Thread: Ich kann den Property-Ersatz nicht erfassen. Daran knoble ich jetzt. Dafür mach ich aber ein eigenes Thema auf, da die Event-/EventHandler-Geschichte ja geklärt ist.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    data wird and denn Eventhandler geschickt, das geschieht durch denn e Parameter
    aber der e Parameter kann nicht von Typ EventArgs sein

    versuche eine Klasse mit Inherits

    VB.NET-Quellcode

    1. Public Class Class1
    2. Inherits EventArgs
    3. Private ReadOnly _xValue As String
    4. Public ReadOnly Property pValue() As String
    5. Get
    6. Return Me._xValue
    7. End Get
    8. End Property
    9. Public Sub New(ByVal pValue As String)
    10. Me._xValue = pValue
    11. End Sub
    12. End Class
    Leider versteh ich gerade nicht, wo Du gedanklich bist oder welches Problem jetzt gelöst wird. Da EventArgs die Basisklasse ist, kann Deine abgeleitete Klasse da auch immer verwendet werden, das ist richtig (Stichwort LSP). Aber wo kann ich nun nicht die EventArgs an sich verwenden? Ich will ja in e gar nix mitschicken. Dass das Problem seit Post#2 gelöst ist, hast Du gesehen?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    VaporiZed schrieb:

    Ich versuche mir gerade in WinForms eine Vereinfachung der INotifyPropertyChanged-Implementierung zu basteln, scheitere jedoch am zuverlässigen Abfangen des Events.
    Warum?
    Vor allem: INotifyPropertyChanged ist ein extrem wichtiger Standard, der massenhaft und allüberall seine Arbeit tut.
    Wenn du da jetzt das Rad neu erfindest, dann sitzst du ganz schön auf der Insel, und die Welt um dich herum funktioniert anders.

    Aber vielleicht verstehe ich dich auch falsch. Du sagst: "Ich will eine INotifyPropertyChanged-Implementation vereinfachen" - das wäre ja durchaus löblich (was immer darunter konkret zu verstehen ist - und wenn es denn möglich wäre).
    Aber in deim Code sehe ich nirgends Implements INotifyPropertyChanged - also implementierst du offsichtlich iwas anneres, aber nicht INotifyPropertyChanged - und damit eine Insel-Lösung.
    Dann bitte bei Wie erfasst man den Ersatz einer (Property)Instanz? reinschauen. Hier ging es um ein sehr spezifisches Problem, was mit der INotifyPropertyChanged-Geschichte noch direkt explizit nichts zu tun hat. Es ging hier um eine Vorbereitung. Daher habe ich das Ganze mit der Schnittstellenimplementierung rausgelassen, da es für das Problem noch nicht relevant ist.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.