Skip to content

Commit fff3b75

Browse files
authored
Added SubscriberCount property to WeakEvent. Add and adjust tests. README.md and PACKAGE.md updated. (#1)
1 parent 14d9f79 commit fff3b75

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public record MyEventData(string Message);
7777
## API
7878

7979
### `WeakEvent`
80+
* `SubscriberCount`\
81+
Number of alive subscribers currently registered to the event.
8082
* `Subscribe(Action handler)`\
8183
`Subscribe(Func<Task> handler)`\
8284
`Subscribe(Func<CancellationToken, Task> handler)`\
@@ -89,6 +91,8 @@ public record MyEventData(string Message);
8991
Raises the event by invoking all live subscribers. Dead subscribers (whose targets have been garbage-collected) are removed.
9092

9193
### `WeakEvent<TEvent>`
94+
* `SubscriberCount`\
95+
Number of alive subscribers currently registered to the event.
9296
* `Subscribe(Action<TEvent> handler)`\
9397
`Subscribe(Func<TEvent, Task> handler)`\
9498
`Subscribe(Func<TEvent, CancellationToken, Task> handler)`\

src/WeakEvent.Tests/WeakEventGenericTests.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,14 @@ public async Task DeadHandler_IsNotInvoked_AfterGarbageCollection()
116116
var weakEvent = new WeakEvent<string>();
117117
var callCount = 0;
118118

119-
await CreateSubscriberAndInvoke(weakEvent, () => callCount++);
119+
static async Task createSubscriberAndInvoke(WeakEvent<string> weakEvent, Action onEvent)
120+
{
121+
var subscriber = new GenericSubscriber(onEvent);
122+
weakEvent.Subscribe(subscriber.Handler);
123+
await weakEvent.PublishAsync("Test");
124+
// The subscriber goes out of scope after this method, allowing it to be GC’d.
125+
}
126+
await createSubscriberAndInvoke(weakEvent, () => callCount++);
120127

121128
// Force garbage collection to reclaim the subscriber instance.
122129
GC.Collect();
@@ -129,12 +136,35 @@ public async Task DeadHandler_IsNotInvoked_AfterGarbageCollection()
129136
Assert.Equal(1, callCount);
130137
}
131138

132-
private static async Task CreateSubscriberAndInvoke(WeakEvent<string> weakEvent, Action onEvent)
139+
[Fact]
140+
public void MultipleHandlers_CorrectCount()
133141
{
134-
var subscriber = new GenericSubscriber(onEvent);
135-
weakEvent.Subscribe(subscriber.Handler);
136-
await weakEvent.PublishAsync("Test");
137-
// The subscriber goes out of scope after this method, allowing it to be GC’d.
142+
// Stage 1 - No subscribers yet
143+
var weakEvent = new WeakEvent<string>();
144+
Assert.Equal(0, weakEvent.SubscriberCount);
145+
146+
// Stage 2 - Add local handler
147+
void syncHandler(string _)
148+
{ }
149+
weakEvent.Subscribe(syncHandler);
150+
151+
// Stage 3 - Add a garbage-collected handler
152+
void createSubscriberAndAssert()
153+
{
154+
var subscriber = new GenericSubscriber(() => { });
155+
weakEvent.Subscribe(subscriber.Handler);
156+
Assert.Equal(2, weakEvent.SubscriberCount);
157+
}
158+
createSubscriberAndAssert();
159+
160+
// Stage 4 - Force garbage collection to reclaim the garbage-collectable subscriber instance.
161+
GC.Collect();
162+
GC.WaitForPendingFinalizers();
163+
Assert.Equal(1, weakEvent.SubscriberCount);
164+
165+
// Stage 5 - Remove local handler
166+
weakEvent.Unsubscribe(syncHandler);
167+
Assert.Equal(0, weakEvent.SubscriberCount);
138168
}
139169

140170
private class GenericSubscriber(Action onEvent)

src/WeakEvent.Tests/WeakEventNonGenericTests.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,14 @@ public async Task DeadHandler_IsNotInvoked_AfterGarbageCollection()
114114
var weakEvent = new WeakEvent();
115115
var callCount = 0;
116116

117-
await CreateSubscriberAndInvoke(weakEvent, () => callCount++);
117+
static async Task createSubscriberAndInvoke(WeakEvent weakEvent, Action onEvent)
118+
{
119+
var subscriber = new NonGenericSubscriber(onEvent);
120+
weakEvent.Subscribe(subscriber.Handler);
121+
await weakEvent.PublishAsync();
122+
// The subscriber goes out of scope after this method, allowing it to be GC’d.
123+
}
124+
await createSubscriberAndInvoke(weakEvent, () => callCount++);
118125

119126
// Force garbage collection to reclaim the subscriber instance.
120127
GC.Collect();
@@ -127,12 +134,36 @@ public async Task DeadHandler_IsNotInvoked_AfterGarbageCollection()
127134
Assert.Equal(1, callCount);
128135
}
129136

130-
private static async Task CreateSubscriberAndInvoke(WeakEvent weakEvent, Action onEvent)
137+
[Fact]
138+
public void MultipleHandlers_CorrectCount()
131139
{
132-
var subscriber = new NonGenericSubscriber(onEvent);
133-
weakEvent.Subscribe(subscriber.Handler);
134-
await weakEvent.PublishAsync();
135-
// The subscriber goes out of scope after this method, allowing it to be GC’d.
140+
// Stage 1 - No subscribers yet
141+
var weakEvent = new WeakEvent();
142+
Assert.Equal(0, weakEvent.SubscriberCount);
143+
144+
// Stage 2 - Add local handler
145+
void syncHandler()
146+
{ }
147+
weakEvent.Subscribe(syncHandler);
148+
Assert.Equal(1, weakEvent.SubscriberCount);
149+
150+
// Stage 3 - Add a garbage-collected handler
151+
void createSubscriberAndAssert()
152+
{
153+
var subscriber = new NonGenericSubscriber(() => { });
154+
weakEvent.Subscribe(subscriber.Handler);
155+
Assert.Equal(2, weakEvent.SubscriberCount);
156+
}
157+
createSubscriberAndAssert();
158+
159+
// Stage 4 - Force garbage collection to reclaim the garbage-collectable subscriber instance.
160+
GC.Collect();
161+
GC.WaitForPendingFinalizers();
162+
Assert.Equal(1, weakEvent.SubscriberCount);
163+
164+
// Stage 5 - Remove local handler
165+
weakEvent.Unsubscribe(syncHandler);
166+
Assert.Equal(0, weakEvent.SubscriberCount);
136167
}
137168

138169
private class NonGenericSubscriber(Action onEvent)

src/WeakEvent/PACKAGE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public record MyEventData(string Message);
6868
## API Overview
6969

7070
### `WeakEvent`
71+
* `SubscriberCount`\
72+
Number of alive subscribers currently registered to the event.
7173
* `Subscribe(Action handler)`\
7274
`Subscribe(Func<Task> handler)`\
7375
`Subscribe(Func<CancellationToken, Task> handler)`\
@@ -80,6 +82,8 @@ public record MyEventData(string Message);
8082
Raises the event by invoking all live subscribers. Dead subscribers (whose targets have been garbage-collected) are removed.
8183

8284
### `WeakEvent<TEvent>`
85+
* `SubscriberCount`\
86+
Number of alive subscribers currently registered to the event.
8387
* `Subscribe(Action<TEvent> handler)`\
8488
`Subscribe(Func<TEvent, Task> handler)`\
8589
`Subscribe(Func<TEvent, CancellationToken, Task> handler)`\

src/WeakEvent/WeakEvent.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ public abstract class WeakEventBase
150150

151151
private readonly SemaphoreSlim _lock = new(1, 1);
152152

153+
/// <summary>
154+
/// Number of alive subscribers currently registered to the event.
155+
/// </summary>
156+
public int SubscriberCount => _handlers.Count(x => x.IsAlive);
157+
153158
/// <summary>
154159
/// Subscribes the specified delegate handler to the event.
155160
/// </summary>

0 commit comments

Comments
 (0)