1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Threading;
12 namespace System.Runtime.InteropServices.WindowsRuntime
14 // An event registration token table stores mappings from delegates to event tokens, in order to support
15 // sourcing WinRT style events from managed code.
16 public sealed class EventRegistrationTokenTable<T> where T : class
18 // Note this dictionary is also used as the synchronization object for this table
19 private Dictionary<EventRegistrationToken, T> m_tokens = new Dictionary<EventRegistrationToken, T>();
21 // Cached multicast delegate which will invoke all of the currently registered delegates. This
22 // will be accessed frequently in common coding paterns, so we don't want to calculate it repeatedly.
23 private volatile T m_invokeList;
25 public EventRegistrationTokenTable()
27 // T must be a delegate type, but we cannot constrain on being a delegate. Therefore, we'll do a
28 // static check at construction time
29 if (!typeof(Delegate).IsAssignableFrom(typeof(T)))
31 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_EventTokenTableRequiresDelegate, typeof (T)));
35 // The InvocationList property provides access to a delegate which will invoke every registered event handler
36 // in this table. If the property is set, the new value will replace any existing token registrations.
37 public T InvocationList
48 // The value being set replaces any of the existing values
54 AddEventHandlerNoLock(value);
60 public EventRegistrationToken AddEventHandler(T handler)
62 // Windows Runtime allows null handlers. Assign those a token value of 0 for easy identity
65 return new EventRegistrationToken(0);
70 return AddEventHandlerNoLock(handler);
74 private EventRegistrationToken AddEventHandlerNoLock(T handler)
76 Debug.Assert(handler != null);
78 // Get a registration token, making sure that we haven't already used the value. This should be quite
79 // rare, but in the case it does happen, just keep trying until we find one that's unused.
80 EventRegistrationToken token = GetPreferredToken(handler);
81 while (m_tokens.ContainsKey(token))
83 token = new EventRegistrationToken(token.Value + 1);
85 m_tokens[token] = handler;
87 // Update the current invocation list to include the newly added delegate
88 Delegate invokeList = (Delegate)(object)m_invokeList;
89 invokeList = MulticastDelegate.Combine(invokeList, (Delegate)(object)handler);
90 m_invokeList = (T)(object)invokeList;
95 // Get the delegate associated with an event registration token if it exists. Additionally,
96 // remove the registration from the table at the same time. If the token is not registered,
97 // Extract returns null and does not modify the table.
98 // [System.Runtime.CompilerServices.FriendAccessAllowed]
99 internal T ExtractHandler(EventRegistrationToken token)
104 if (m_tokens.TryGetValue(token, out handler))
106 RemoveEventHandlerNoLock(token);
113 // Generate a token that may be used for a particular event handler. We will frequently be called
114 // upon to look up a token value given only a delegate to start from. Therefore, we want to make
115 // an initial token value that is easily determined using only the delegate instance itself. Although
116 // in the common case this token value will be used to uniquely identify the handler, it is not
117 // the only possible token that can represent the handler.
119 // This means that both:
120 // * if there is a handler assigned to the generated initial token value, it is not necessarily
122 // * if there is no handler assigned to the generated initial token value, the handler may still
123 // be registered under a different token
125 // Effectively the only reasonable thing to do with this value is either to:
126 // 1. Use it as a good starting point for generating a token for handler
127 // 2. Use it as a guess to quickly see if the handler was really assigned this token value
128 private static EventRegistrationToken GetPreferredToken(T handler)
130 Debug.Assert(handler != null);
132 // We want to generate a token value that has the following properties:
133 // 1. is quickly obtained from the handler instance
134 // 2. uses bits in the upper 32 bits of the 64 bit value, in order to avoid bugs where code
135 // may assume the value is realy just 32 bits
136 // 3. uses bits in the bottom 32 bits of the 64 bit value, in order to ensure that code doesn't
137 // take a dependency on them always being 0.
139 // The simple algorithm chosen here is to simply assign the upper 32 bits the metadata token of the
140 // event handler type, and the lower 32 bits the hash code of the handler instance itself. Using the
141 // metadata token for the upper 32 bits gives us at least a small chance of being able to identify a
142 // totally corrupted token if we ever come across one in a minidump or other scenario.
144 // The hash code of a unicast delegate is not tied to the method being invoked, so in the case
145 // of a unicast delegate, the hash code of the target method is used instead of the full delegate
148 // While calculating this initial value will be somewhat more expensive than just using a counter
149 // for events that have few registrations, it will also gives us a shot at preventing unregistration
150 // from becoming an O(N) operation.
152 // We should feel free to change this algorithm as other requirements / optimizations become
153 // available. This implementation is sufficiently random that code cannot simply guess the value to
154 // take a dependency upon it. (Simply applying the hash-value algorithm directly won't work in the
155 // case of collisions, where we'll use a different token value).
157 uint handlerHashCode = 0;
158 Delegate[] invocationList = ((Delegate)(object)handler).GetInvocationList();
159 if (invocationList.Length == 1)
161 handlerHashCode = (uint)invocationList[0].Method.GetHashCode();
165 handlerHashCode = (uint)handler.GetHashCode();
168 ulong tokenValue = ((ulong)(uint)typeof(T).MetadataToken << 32) | handlerHashCode;
169 return new EventRegistrationToken(tokenValue);
172 public void RemoveEventHandler(EventRegistrationToken token)
174 // The 0 token is assigned to null handlers, so there's nothing to do
175 if (token.Value == 0)
182 RemoveEventHandlerNoLock(token);
186 public void RemoveEventHandler(T handler)
188 // To match the Windows Runtime behaivor when adding a null handler, removing one is a no-op
196 // Fast path - if the delegate is stored with its preferred token, then there's no need to do
197 // a full search of the table for it. Note that even if we find something stored using the
198 // preferred token value, it's possible we have a collision and another delegate was using that
199 // value. Therefore we need to make sure we really have the handler we want before taking the
201 EventRegistrationToken preferredToken = GetPreferredToken(handler);
203 if (m_tokens.TryGetValue(preferredToken, out registeredHandler))
205 if (registeredHandler == handler)
207 RemoveEventHandlerNoLock(preferredToken);
212 // Slow path - we didn't find the delegate with its preferred token, so we need to fall
213 // back to a search of the table
214 foreach (KeyValuePair<EventRegistrationToken, T> registration in m_tokens)
216 if (registration.Value == (T)(object)handler)
218 RemoveEventHandlerNoLock(registration.Key);
220 // If a delegate has been added multiple times to handle an event, then it
221 // needs to be removed the same number of times to stop handling the event.
222 // Stop after the first one we find.
227 // Note that falling off the end of the loop is not an error, as removing a registration
228 // for a handler that is not currently registered is simply a no-op
232 private void RemoveEventHandlerNoLock(EventRegistrationToken token)
235 if (m_tokens.TryGetValue(token, out handler))
237 m_tokens.Remove(token);
239 // Update the current invocation list to remove the delegate
240 Delegate invokeList = (Delegate)(object)m_invokeList;
241 invokeList = MulticastDelegate.Remove(invokeList, (Delegate)(object)handler);
242 m_invokeList = (T)(object)invokeList;
246 public static EventRegistrationTokenTable<T> GetOrCreateEventRegistrationTokenTable(ref EventRegistrationTokenTable<T> refEventTable)
248 if (refEventTable == null)
250 Interlocked.CompareExchange(ref refEventTable, new EventRegistrationTokenTable<T>(), null);
252 return refEventTable;