Delete FriendAccessAllowedAttribute and associated dead code (#15101)
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Runtime / InteropServices / WindowsRuntime / EventRegistrationTokenTable.cs
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.
4
5 //
6
7 using System;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Threading;
11
12 namespace System.Runtime.InteropServices.WindowsRuntime
13 {
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
17     {
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>();
20
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;
24
25         public EventRegistrationTokenTable()
26         {
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)))
30             {
31                 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_EventTokenTableRequiresDelegate, typeof (T)));
32             }
33         }
34
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
38         {
39             get
40             {
41                 return m_invokeList;
42             }
43
44             set
45             {
46                 lock (m_tokens)
47                 {
48                     // The value being set replaces any of the existing values
49                     m_tokens.Clear();
50                     m_invokeList = null;
51
52                     if (value != null)
53                     {
54                         AddEventHandlerNoLock(value);
55                     }
56                 }
57             }
58         }
59
60         public EventRegistrationToken AddEventHandler(T handler)
61         {
62             // Windows Runtime allows null handlers.  Assign those a token value of 0 for easy identity
63             if (handler == null)
64             {
65                 return new EventRegistrationToken(0);
66             }
67
68             lock (m_tokens)
69             {
70                 return AddEventHandlerNoLock(handler);
71             }
72         }
73
74         private EventRegistrationToken AddEventHandlerNoLock(T handler)
75         {
76             Debug.Assert(handler != null);
77
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))
82             {
83                 token = new EventRegistrationToken(token.Value + 1);
84             }
85             m_tokens[token] = handler;
86
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;
91
92             return token;
93         }
94
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)
100         {
101             T handler = null;
102             lock (m_tokens)
103             {
104                 if (m_tokens.TryGetValue(token, out handler))
105                 {
106                     RemoveEventHandlerNoLock(token);
107                 }
108             }
109
110             return handler;
111         }
112
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.
118         //
119         // This means that both:
120         //  * if there is a handler assigned to the generated initial token value, it is not necessarily
121         //    this handler.
122         //  * if there is no handler assigned to the generated initial token value, the handler may still
123         //    be registered under a different token
124         //
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)
129         {
130             Debug.Assert(handler != null);
131
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.
138             //
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.
143             //
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
146             // hash code.
147             //
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.
151             //
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).
156
157             uint handlerHashCode = 0;
158             Delegate[] invocationList = ((Delegate)(object)handler).GetInvocationList();
159             if (invocationList.Length == 1)
160             {
161                 handlerHashCode = (uint)invocationList[0].Method.GetHashCode();
162             }
163             else
164             {
165                 handlerHashCode = (uint)handler.GetHashCode();
166             }
167
168             ulong tokenValue = ((ulong)(uint)typeof(T).MetadataToken << 32) | handlerHashCode;
169             return new EventRegistrationToken(tokenValue);
170         }
171
172         public void RemoveEventHandler(EventRegistrationToken token)
173         {
174             // The 0 token is assigned to null handlers, so there's nothing to do
175             if (token.Value == 0)
176             {
177                 return;
178             }
179
180             lock (m_tokens)
181             {
182                 RemoveEventHandlerNoLock(token);
183             }
184         }
185
186         public void RemoveEventHandler(T handler)
187         {
188             // To match the Windows Runtime behaivor when adding a null handler, removing one is a no-op
189             if (handler == null)
190             {
191                 return;
192             }
193
194             lock (m_tokens)
195             {
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
200                 // fast path.
201                 EventRegistrationToken preferredToken = GetPreferredToken(handler);
202                 T registeredHandler;
203                 if (m_tokens.TryGetValue(preferredToken, out registeredHandler))
204                 {
205                     if (registeredHandler == handler)
206                     {
207                         RemoveEventHandlerNoLock(preferredToken);
208                         return;
209                     }
210                 }
211
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)
215                 {
216                     if (registration.Value == (T)(object)handler)
217                     {
218                         RemoveEventHandlerNoLock(registration.Key);
219
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.
223                         return;
224                     }
225                 }
226
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
229             }
230         }
231
232         private void RemoveEventHandlerNoLock(EventRegistrationToken token)
233         {
234             T handler;
235             if (m_tokens.TryGetValue(token, out handler))
236             {
237                 m_tokens.Remove(token);
238
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;
243             }
244         }
245
246         public static EventRegistrationTokenTable<T> GetOrCreateEventRegistrationTokenTable(ref EventRegistrationTokenTable<T> refEventTable)
247         {
248             if (refEventTable == null)
249             {
250                 Interlocked.CompareExchange(ref refEventTable, new EventRegistrationTokenTable<T>(), null);
251             }
252             return refEventTable;
253         }
254     }
255 }