Support WeakReference based Auto-implemented Event
- Dominant language
- No language data
- Stars
- 328
- Forks
- 71
- PR merge metrics
- No merged PRs in 30d
Description
If you have tried to let objects to subscribe a `Event` that is defined in a `Module`, you might have noticed that those objects will not be collected by the `GC` before event handlers are removed. The following codes show the difference of whether `RemoveHandler` have called.
```vb
Module GamepadWatcher
Public Event GamepadConnected(sender As Object, e As GamepadEventArgs)
Public Event GamepadRemoved(sender As Object, e As GamepadEventArgs)
End Module
Friend Class GamepadEventArgs
Inherits EventArgs
Public Sub New(deviceId As Integer)
Me.DeviceId = deviceId
End Sub
Public ReadOnly Property DeviceId As Integer
End Class
Module PlayerManager
Public ReadOnly Property Players As New List(Of Player)
End Module
Friend Class Player
Sub New()
AddHandler GamepadConnected, AddressOf OnGamepadConnected
AddHandler GamepadRemoved, AddressOf OnGamepadRemoved
End Sub
Private Sub OnGamepadRemoved(sender As Object, e As GamepadEventArgs)
End Sub
Private Sub OnGamepadConnected(sender As Object, e As GamepadEventArgs)
End Sub
Public Sub RemoveGamepadHandlers()
RemoveHandler GamepadConnected, AddressOf OnGamepadConnected
RemoveHandler GamepadRemoved, AddressOf OnGamepadRemoved
End Sub
End Class
Module Program
Sub Main()
Console.WriteLine("Avoid memory leak")
MemoryLeak(False)
Console.WriteLine("Memory leak")
MemoryLeak(True)
End Sub
Private Sub MemoryLeak(leak As Boolean)
Console.WriteLine("Memory before add:" & GC.GetTotalMemory(True))
For i = 0 To 10_000
Players.Add(New Player())
Next
Console.WriteLine("Memory before clear:" & GC.GetTotalMemory(True))
If Not leak Then
For Each p In Players
p.RemoveGamepadHandlers()
Next
End If
Players.Clear()
GC.Collect()
GC.WaitForPendingFinalizers()
Console.WriteLine("Memory after clear:" & GC.GetTotalMemory(True))
End Sub
End Module
```
Outputs on my PC:
```
Avoid memory leak
Memory before add:30816
Memory before clear:988292
Memory after clear:96984
Memory leak
Memory before add:96972
Memory before clear:988292
Memory after clear:988208
```
So, I'd like to suggest `WeakReference` based auto-implemented event.
```vb
Module GamepadWatcher
' New custom attribute: Microsoft.VisualBasic.CompilerServices.AutoRemoveAttribute
' If an auto-event is marked with AutoRemove, the generated AddHandler will weakly reference to event handlers. In the generated RaiseEvent, if a event handler is not alive, the corresponding WeakReference(Of TDelegate) will be removed from the invocation list.
Public Event GamepadConnected(sender As Object, e As GamepadEventArgs)
Public Event GamepadRemoved(sender As Object, e As GamepadEventArgs)
End Module
```
For example, the `Event GamepadRemoved` is marked with `AutoRemove`. Therefore, this event will be replaced at compile-time with a custom event and a delegate:
```vb
Public Delegate Sub GamepadRemovedEventHandler(sender As Object, args As GamepadEventArgs)
Private ReadOnly _GamepadRemoved As New GamepadRemovedWeakEvent
Public Custom Event GamepadRemoved As GamepadRemovedEventHandler
AddHandler(value As GamepadRemovedEventHandler)
_GamepadRemoved.AddHandler(value)
End AddHandler
RemoveHandler(value As GamepadRemovedEventHandler)
_GamepadRemoved.RemoveHandler(value)
End RemoveHandler
RaiseEvent(sender As Object, args As GamepadEventArgs)
_GamepadRemoved.RaiseEvent(sender, args)
End RaiseEvent
End Event
```
And a embedded weak event implementation:
```vb
Friend Class GamepadRemovedWeakEvent
Private ReadOnly _observers As New List(Of (Observer As WeakReference, Handler As WeakReference(Of GamepadRemovedEventHandler)))
Private ReadOnly _strongHandlers As New List(Of GamepadRemovedEventHandler)
Public Sub [RemoveHandler](eventHandler As GamepadRemovedEventHandler)
If eventHandler Is Nothing Then
Throw New ArgumentNullException(NameOf(eventHandler))
End If
Dim observer = eventHandler.Target
If observer Is Nothing Then
_strongHandlers.Remove(eventHandler)
Else
For i = _observers.Count - 1 To 0 Step -1
Dim item = _observers(i)
If item.Observer.IsAlive Then
If item.Observer.Target Is observer Then
Dim handler As GamepadRemovedEventHandler = Nothing
If item.Handler.TryGetTarget(handler) Then
If handler Is eventHandler Then
_observers.RemoveAt(i)
End If
Else
_observers.RemoveAt(i)
End If
End If
Else
_observers.RemoveAt(i)
End If
Next
End If
End Sub
Public Sub [AddHandler](eventHandler As GamepadRemovedEventHandler)
If eventHandler Is Nothing Then
Throw New ArgumentNullException(NameOf(eventHandler))
End If
Dim observer = eventHandler.Target
If observer Is Nothing Then
_strongHandlers.Add(eventHandler)
Else
_observers.Add((New WeakReference(observer), New WeakReference(Of GamepadRemovedEventHandler)(eventHandler)))
End If
End Sub
Public Sub [RaiseEvent](sender As Object, e As GamepadEventArgs)
For i = _observers.Count - 1 To 0 Step -1
Dim item = _observers(i)
If item.Observer.IsAlive Then
Dim handler As GamepadRemovedEventHandler = Nothing
If item.Handler.TryGetTarget(handler) Then
handler(sender, e)
Else
_observers.RemoveAt(i)
End If
Else
_observers.RemoveAt(i)
End If
Next
For i = 0 To _strongHandlers.Count - 1
_strongHandlers(i)(sender, e)
Next
End Sub
Public ReadOnly Property ObserverCount As Integer
Get
Return _observers.Count + _strongHandlers.Count
End Get
End Property
End Class
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.