Skip to content

Commit 021e1ad

Browse files
authored
Avoid boxing in UniqueEventHelper (#6510)
Use a Dictionary instead of Hashtable in UniqueEventHelper in order to avoid boxing the ref count integer on every add/remove. And as long as this was being updated, addressed the TODO to combine the identical types into a helper base type that provided most of the functionality shared instead of being duplicated on both the generic and non-generic handler implementation. Also reduced the allocation in InvokeEvents, just allocating an array for the handlers rather than allocating a complete copy of the dictionary, a keys object, and an enumerator object.
1 parent 1f15ae0 commit 021e1ad

File tree

1 file changed

+52
-146
lines changed

1 file changed

+52
-146
lines changed

src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/UniqueEventHelper.cs

Lines changed: 52 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
6-
using System.Collections;
5+
using System.Collections.Generic;
76
using System.Diagnostics;
87

98
namespace System.Windows.Media
@@ -17,62 +16,19 @@ namespace System.Windows.Media
1716
/// handlers that throw exceptions will crash the app, making the developer
1817
/// aware of the problem.
1918
/// </summary>
20-
internal class UniqueEventHelper<TEventArgs>
21-
where TEventArgs : EventArgs
19+
internal sealed class UniqueEventHelper<TEventArgs> : UniqueEventHelperBase<EventHandler<TEventArgs>>
20+
where TEventArgs : EventArgs
2221
{
23-
/// <summary>
24-
/// Add the handler to the list of handlers associated with this event.
25-
/// If the handler has already been added, we simply increment the ref
26-
/// count. That way if this same handler has already been added, it
27-
/// won't be invoked multiple times when the event is raised.
28-
/// </summary>
29-
internal void AddEvent(EventHandler<TEventArgs> handler)
30-
{
31-
if (handler == null)
32-
{
33-
throw new System.ArgumentNullException("handler");
34-
}
35-
36-
EnsureEventTable();
37-
38-
if (_htDelegates[handler] == null)
39-
{
40-
_htDelegates.Add(handler, 1);
41-
}
42-
else
43-
{
44-
int refCount = (int)_htDelegates[handler] + 1;
45-
_htDelegates[handler] = refCount;
46-
}
47-
}
48-
49-
/// <summary>
50-
/// Removed the handler from the list of handlers associated with this
51-
/// event. If the handler has been added multiple times (more times than
52-
/// it has been removed), we simply decrement its ref count.
53-
/// </summary>
54-
internal void RemoveEvent(EventHandler<TEventArgs> handler)
22+
/// <summary>Clones the event helper</summary>
23+
internal UniqueEventHelper<TEventArgs> Clone()
5524
{
56-
if (handler == null)
25+
var ueh = new UniqueEventHelper<TEventArgs>();
26+
if (_delegates != null)
5727
{
58-
throw new System.ArgumentNullException("handler");
28+
ueh._delegates = new Dictionary<EventHandler<TEventArgs>, int>(_delegates);
5929
}
6030

61-
EnsureEventTable();
62-
63-
if (_htDelegates[handler] != null)
64-
{
65-
int refCount = (int)_htDelegates[handler];
66-
67-
if (refCount == 1)
68-
{
69-
_htDelegates.Remove(handler);
70-
}
71-
else
72-
{
73-
_htDelegates[handler] = refCount - 1;
74-
}
75-
}
31+
return ueh;
7632
}
7733

7834
/// <summary>
@@ -83,77 +39,68 @@ internal void RemoveEvent(EventHandler<TEventArgs> handler)
8339
/// <param name="args">The args object to be sent by the delegate</param>
8440
internal void InvokeEvents(object sender, TEventArgs args)
8541
{
86-
Debug.Assert((sender != null), "Sender is null");
87-
88-
if (_htDelegates != null)
42+
Debug.Assert(sender != null, "Sender is null");
43+
foreach (EventHandler<TEventArgs> handler in CopyHandlers())
8944
{
90-
Hashtable htDelegates = (Hashtable)_htDelegates.Clone();
91-
foreach (EventHandler<TEventArgs> handler in htDelegates.Keys)
92-
{
93-
Debug.Assert((handler != null), "Event handler is null");
94-
handler(sender, args);
95-
}
45+
Debug.Assert(handler != null, "Event handler is null");
46+
handler(sender, args);
9647
}
9748
}
49+
}
9850

99-
/// <summary>
100-
/// Clones the event helper
101-
/// </summary>
102-
internal UniqueEventHelper<TEventArgs> Clone()
51+
internal sealed class UniqueEventHelper : UniqueEventHelperBase<EventHandler>
52+
{
53+
/// <summary>Clones the event helper</summary>
54+
internal UniqueEventHelper Clone()
10355
{
104-
UniqueEventHelper<TEventArgs> ueh = new UniqueEventHelper<TEventArgs>();
105-
if (_htDelegates != null)
56+
var ueh = new UniqueEventHelper();
57+
if (_delegates != null)
10658
{
107-
ueh._htDelegates = (Hashtable)_htDelegates.Clone();
59+
ueh._delegates = new Dictionary<EventHandler, int>(_delegates);
10860
}
61+
10962
return ueh;
11063
}
11164

11265
/// <summary>
113-
/// Ensures Hashtable is created so that event handlers can be added/removed
66+
/// Enumerates all the keys in the hashtable, which must be EventHandlers,
67+
/// and invokes them.
11468
/// </summary>
115-
private void EnsureEventTable()
69+
/// <param name="sender">The sender for the callback.</param>
70+
/// <param name="args">The args object to be sent by the delegate</param>
71+
internal void InvokeEvents(object sender, EventArgs args)
11672
{
117-
if (_htDelegates == null)
73+
Debug.Assert(sender != null, "Sender is null");
74+
foreach (EventHandler handler in CopyHandlers())
11875
{
119-
_htDelegates = new Hashtable();
76+
Debug.Assert(handler != null, "Event handler is null");
77+
handler(sender, args);
12078
}
12179
}
122-
123-
private Hashtable _htDelegates;
12480
}
12581

126-
127-
128-
// (if possible) figure out a way so that the
129-
// previous class can be used in place of this one. This class is needed
130-
// in addition to the above generic one because EventHandler cannot be
131-
// cast to its generic counterpart
132-
internal class UniqueEventHelper
82+
internal abstract class UniqueEventHelperBase<TEventHandler> where TEventHandler : Delegate
13383
{
84+
protected Dictionary<TEventHandler, int> _delegates;
85+
13486
/// <summary>
13587
/// Add the handler to the list of handlers associated with this event.
13688
/// If the handler has already been added, we simply increment the ref
13789
/// count. That way if this same handler has already been added, it
13890
/// won't be invoked multiple times when the event is raised.
13991
/// </summary>
140-
internal void AddEvent(EventHandler handler)
92+
internal void AddEvent(TEventHandler handler)
14193
{
142-
if (handler == null)
143-
{
144-
throw new System.ArgumentNullException("handler");
145-
}
146-
147-
EnsureEventTable();
94+
ArgumentNullException.ThrowIfNull(handler);
14895

149-
if (_htDelegates[handler] == null)
96+
if (_delegates is Dictionary<TEventHandler, int> delegates)
15097
{
151-
_htDelegates.Add(handler, 1);
98+
delegates.TryGetValue(handler, out int refCount);
99+
delegates[handler] = refCount + 1;
152100
}
153101
else
154102
{
155-
int refCount = (int)_htDelegates[handler] + 1;
156-
_htDelegates[handler] = refCount;
103+
_delegates = new Dictionary<TEventHandler, int>() { { handler, 1 } };
157104
}
158105
}
159106

@@ -162,75 +109,34 @@ internal void AddEvent(EventHandler handler)
162109
/// event. If the handler has been added multiple times (more times than
163110
/// it has been removed), we simply decrement its ref count.
164111
/// </summary>
165-
internal void RemoveEvent(EventHandler handler)
112+
internal void RemoveEvent(TEventHandler handler)
166113
{
167-
if (handler == null)
168-
{
169-
throw new System.ArgumentNullException("handler");
170-
}
171-
172-
EnsureEventTable();
114+
ArgumentNullException.ThrowIfNull(handler);
173115

174-
if (_htDelegates[handler] != null)
116+
if (_delegates is Dictionary<TEventHandler, int> delegates &&
117+
delegates.TryGetValue(handler, out int refCount))
175118
{
176-
int refCount = (int)_htDelegates[handler];
177-
178119
if (refCount == 1)
179120
{
180-
_htDelegates.Remove(handler);
121+
delegates.Remove(handler);
181122
}
182123
else
183124
{
184-
_htDelegates[handler] = refCount - 1;
125+
delegates[handler] = refCount - 1;
185126
}
186127
}
187128
}
188129

189-
/// <summary>
190-
/// Enumerates all the keys in the hashtable, which must be EventHandlers,
191-
/// and invokes them.
192-
/// </summary>
193-
/// <param name="sender">The sender for the callback.</param>
194-
/// <param name="args">The args object to be sent by the delegate</param>
195-
internal void InvokeEvents(object sender, EventArgs args)
130+
protected TEventHandler[] CopyHandlers()
196131
{
197-
Debug.Assert((sender != null), "Sender is null");
198-
199-
if (_htDelegates != null)
132+
if (_delegates is { Count: > 0 } delegates)
200133
{
201-
Hashtable htDelegates = (Hashtable)_htDelegates.Clone();
202-
foreach (EventHandler handler in htDelegates.Keys)
203-
{
204-
Debug.Assert((handler != null), "Event handler is null");
205-
handler(sender, args);
206-
}
134+
var handlers = new TEventHandler[delegates.Count];
135+
delegates.Keys.CopyTo(handlers, 0);
136+
return handlers;
207137
}
208-
}
209138

210-
/// <summary>
211-
/// Clones the event helper
212-
/// </summary>
213-
internal UniqueEventHelper Clone()
214-
{
215-
UniqueEventHelper ueh = new UniqueEventHelper();
216-
if (_htDelegates != null)
217-
{
218-
ueh._htDelegates = (Hashtable)_htDelegates.Clone();
219-
}
220-
return ueh;
221-
}
222-
223-
/// <summary>
224-
/// Ensures Hashtable is created so that event handlers can be added/removed
225-
/// </summary>
226-
private void EnsureEventTable()
227-
{
228-
if (_htDelegates == null)
229-
{
230-
_htDelegates = new Hashtable();
231-
}
139+
return Array.Empty<TEventHandler>();
232140
}
233-
234-
private Hashtable _htDelegates;
235141
}
236142
}

0 commit comments

Comments
 (0)