diff options
Diffstat (limited to 'src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationTokenTable.cs')
-rw-r--r-- | src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationTokenTable.cs | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationTokenTable.cs b/src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationTokenTable.cs new file mode 100644 index 0000000000..91b123bee7 --- /dev/null +++ b/src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationTokenTable.cs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Threading; + +namespace System.Runtime.InteropServices.WindowsRuntime +{ + // An event registration token table stores mappings from delegates to event tokens, in order to support + // sourcing WinRT style events from managed code. + public sealed class EventRegistrationTokenTable<T> where T : class + { + // Note this dictionary is also used as the synchronization object for this table + private Dictionary<EventRegistrationToken, T> m_tokens = new Dictionary<EventRegistrationToken, T>(); + + // Cached multicast delegate which will invoke all of the currently registered delegates. This + // will be accessed frequently in common coding paterns, so we don't want to calculate it repeatedly. + private volatile T m_invokeList; + + public EventRegistrationTokenTable() + { + // T must be a delegate type, but we cannot constrain on being a delegate. Therefore, we'll do a + // static check at construction time + if (!typeof(Delegate).IsAssignableFrom(typeof(T))) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EventTokenTableRequiresDelegate", typeof(T))); + } + } + + // The InvocationList property provides access to a delegate which will invoke every registered event handler + // in this table. If the property is set, the new value will replace any existing token registrations. + public T InvocationList + { + get + { + return m_invokeList; + } + + set + { + lock (m_tokens) + { + // The value being set replaces any of the existing values + m_tokens.Clear(); + m_invokeList = null; + + if (value != null) + { + AddEventHandlerNoLock(value); + } + } + } + } + + public EventRegistrationToken AddEventHandler(T handler) + { + // Windows Runtime allows null handlers. Assign those a token value of 0 for easy identity + if (handler == null) + { + return new EventRegistrationToken(0); + } + + lock (m_tokens) + { + return AddEventHandlerNoLock(handler); + } + } + + private EventRegistrationToken AddEventHandlerNoLock(T handler) + { + Contract.Requires(handler != null); + + // Get a registration token, making sure that we haven't already used the value. This should be quite + // rare, but in the case it does happen, just keep trying until we find one that's unused. + EventRegistrationToken token = GetPreferredToken(handler); + while (m_tokens.ContainsKey(token)) + { + token = new EventRegistrationToken(token.Value + 1); + } + m_tokens[token] = handler; + + // Update the current invocation list to include the newly added delegate + Delegate invokeList = (Delegate)(object)m_invokeList; + invokeList = MulticastDelegate.Combine(invokeList, (Delegate)(object)handler); + m_invokeList = (T)(object)invokeList; + + return token; + } + + // Get the delegate associated with an event registration token if it exists. Additionally, + // remove the registration from the table at the same time. If the token is not registered, + // Extract returns null and does not modify the table. + [System.Runtime.CompilerServices.FriendAccessAllowed] + internal T ExtractHandler(EventRegistrationToken token) + { + T handler = null; + lock (m_tokens) + { + if (m_tokens.TryGetValue(token, out handler)) + { + RemoveEventHandlerNoLock(token); + } + } + + return handler; + } + + // Generate a token that may be used for a particular event handler. We will frequently be called + // upon to look up a token value given only a delegate to start from. Therefore, we want to make + // an initial token value that is easily determined using only the delegate instance itself. Although + // in the common case this token value will be used to uniquely identify the handler, it is not + // the only possible token that can represent the handler. + // + // This means that both: + // * if there is a handler assigned to the generated initial token value, it is not necessarily + // this handler. + // * if there is no handler assigned to the generated initial token value, the handler may still + // be registered under a different token + // + // Effectively the only reasonable thing to do with this value is either to: + // 1. Use it as a good starting point for generating a token for handler + // 2. Use it as a guess to quickly see if the handler was really assigned this token value + private static EventRegistrationToken GetPreferredToken(T handler) + { + Contract.Requires(handler != null); + + // We want to generate a token value that has the following properties: + // 1. is quickly obtained from the handler instance + // 2. uses bits in the upper 32 bits of the 64 bit value, in order to avoid bugs where code + // may assume the value is realy just 32 bits + // 3. uses bits in the bottom 32 bits of the 64 bit value, in order to ensure that code doesn't + // take a dependency on them always being 0. + // + // The simple algorithm chosen here is to simply assign the upper 32 bits the metadata token of the + // event handler type, and the lower 32 bits the hash code of the handler instance itself. Using the + // metadata token for the upper 32 bits gives us at least a small chance of being able to identify a + // totally corrupted token if we ever come across one in a minidump or other scenario. + // + // The hash code of a unicast delegate is not tied to the method being invoked, so in the case + // of a unicast delegate, the hash code of the target method is used instead of the full delegate + // hash code. + // + // While calculating this initial value will be somewhat more expensive than just using a counter + // for events that have few registrations, it will also gives us a shot at preventing unregistration + // from becoming an O(N) operation. + // + // We should feel free to change this algorithm as other requirements / optimizations become + // available. This implementation is sufficiently random that code cannot simply guess the value to + // take a dependency upon it. (Simply applying the hash-value algorithm directly won't work in the + // case of collisions, where we'll use a different token value). + + uint handlerHashCode = 0; + Delegate[] invocationList = ((Delegate)(object)handler).GetInvocationList(); + if (invocationList.Length == 1) + { + handlerHashCode = (uint)invocationList[0].Method.GetHashCode(); + } + else + { + handlerHashCode = (uint)handler.GetHashCode(); + } + + ulong tokenValue = ((ulong)(uint)typeof(T).MetadataToken << 32) | handlerHashCode; + return new EventRegistrationToken(tokenValue); + } + + public void RemoveEventHandler(EventRegistrationToken token) + { + // The 0 token is assigned to null handlers, so there's nothing to do + if (token.Value == 0) + { + return; + } + + lock (m_tokens) + { + RemoveEventHandlerNoLock(token); + } + } + + public void RemoveEventHandler(T handler) + { + // To match the Windows Runtime behaivor when adding a null handler, removing one is a no-op + if (handler == null) + { + return; + } + + lock (m_tokens) + { + // Fast path - if the delegate is stored with its preferred token, then there's no need to do + // a full search of the table for it. Note that even if we find something stored using the + // preferred token value, it's possible we have a collision and another delegate was using that + // value. Therefore we need to make sure we really have the handler we want before taking the + // fast path. + EventRegistrationToken preferredToken = GetPreferredToken(handler); + T registeredHandler; + if (m_tokens.TryGetValue(preferredToken, out registeredHandler)) + { + if (registeredHandler == handler) + { + RemoveEventHandlerNoLock(preferredToken); + return; + } + } + + // Slow path - we didn't find the delegate with its preferred token, so we need to fall + // back to a search of the table + foreach (KeyValuePair<EventRegistrationToken, T> registration in m_tokens) + { + if (registration.Value == (T)(object)handler) + { + RemoveEventHandlerNoLock(registration.Key); + + // If a delegate has been added multiple times to handle an event, then it + // needs to be removed the same number of times to stop handling the event. + // Stop after the first one we find. + return; + } + } + + // Note that falling off the end of the loop is not an error, as removing a registration + // for a handler that is not currently registered is simply a no-op + } + } + + private void RemoveEventHandlerNoLock(EventRegistrationToken token) + { + T handler; + if (m_tokens.TryGetValue(token, out handler)) + { + m_tokens.Remove(token); + + // Update the current invocation list to remove the delegate + Delegate invokeList = (Delegate)(object)m_invokeList; + invokeList = MulticastDelegate.Remove(invokeList, (Delegate)(object)handler); + m_invokeList = (T)(object)invokeList; + } + } + + public static EventRegistrationTokenTable<T> GetOrCreateEventRegistrationTokenTable(ref EventRegistrationTokenTable<T> refEventTable) + { + if (refEventTable == null) + { + Interlocked.CompareExchange(ref refEventTable, new EventRegistrationTokenTable<T>(), null); + } + return refEventTable; + } + } +} |