Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/Core/src/WeakEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,23 @@ void RemoveEventHandler(string eventName, object? handlerTarget, MemberInfo meth
if (!_eventHandlers.TryGetValue(eventName, out List<Subscription>? subscriptions))
return;

for (int n = subscriptions.Count; n > 0; n--)
for (int n = subscriptions.Count - 1; n >= 0; n--)
{
Subscription current = subscriptions[n - 1];
Subscription current = subscriptions[n];

if (current.Subscriber?.Target != handlerTarget || current.Handler.Name != methodInfo.Name)
if (current.Subscriber != null && !current.Subscriber.IsAlive)
{
// If not alive, remove and continue
subscriptions.RemoveAt(n);
continue;
}

subscriptions.Remove(current);
break;
if (current.Subscriber?.Target == handlerTarget && current.Handler.Name == methodInfo.Name)
{
// Found the match, we can break
subscriptions.RemoveAt(n);
break;
}
}
}

Expand Down
45 changes: 44 additions & 1 deletion src/Core/tests/UnitTests/WeakEventManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#nullable enable
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Xunit;

namespace Microsoft.Maui.UnitTests
Expand Down Expand Up @@ -65,6 +68,19 @@ void OnTestEvent()
{
_weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(TestEvent));
}

public int EventHandlerCount
{
get
{
// Access private members:
// Dictionary<string, List<Subscription>> eventHandlers = WeakEventManager._eventHandlers;
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var eventHandlers = _weakEventManager.GetType().GetField("_eventHandlers", flags)?.GetValue(_weakEventManager) as IDictionary;
Assert.NotNull(eventHandlers);
return eventHandlers!.Values.Cast<IList>().Sum(l => l.Count);
}
}
}

internal class TestSubscriber
Expand All @@ -74,6 +90,11 @@ public void Subscribe(TestEventSource source)
source.TestEvent += SourceOnTestEvent;
}

public void Unsubscribe(TestEventSource source)
{
source.TestEvent -= SourceOnTestEvent;
}

void SourceOnTestEvent(object? sender, EventArgs eventArgs)
{
throw new Exception("Fail");
Expand Down Expand Up @@ -230,7 +251,7 @@ public void StaticHandlerShouldRun()
}

[Fact]
public void VerifySubscriberCanBeCollected()
public void VerifySubscriberCanBeCollected_FireEvent()
{
WeakReference? wr = null;
var source = new TestEventSource();
Expand All @@ -250,6 +271,28 @@ public void VerifySubscriberCanBeCollected()
// The handler for this calls Assert.Fail, so if the subscriber has not been collected
// the handler will be called and the test will fail
source.FireTestEvent();
Assert.Equal(0, source.EventHandlerCount);
}

[Fact]
public void VerifySubscriberCanBeCollected_Unsubscribe()
{
WeakReference? wr = null;
var source = new TestEventSource();
new Action(() =>
{
var ts = new TestSubscriber();
wr = new WeakReference(ts);
ts.Subscribe(source);
})();

GC.Collect();
GC.WaitForPendingFinalizers();
new TestSubscriber().Unsubscribe(source);

Assert.NotNull(wr);
Assert.False(wr?.IsAlive);
Assert.Equal(0, source.EventHandlerCount);
}

[Fact]
Expand Down