PageRenderTime 60ms CodeModel.GetById 46ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/Runtime/Microsoft.Dynamic/Actions/EventTracker.cs

http://github.com/IronLanguages/main
C# | 306 lines | 206 code | 49 blank | 51 comment | 43 complexity | 7008f2e0e1b11d298ab6b09c7cd50146 MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Apache License, Version 2.0, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Apache License, Version 2.0.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16using System;
 17using System.Collections.Generic;
 18using System.Diagnostics;
 19using System.Dynamic;
 20using System.Reflection;
 21using System.Runtime.InteropServices;
 22using System.Threading;
 23
 24using Microsoft.Scripting.Actions.Calls;
 25using Microsoft.Scripting.Generation;
 26using Microsoft.Scripting.Runtime;
 27using Microsoft.Scripting.Utils;
 28
 29namespace Microsoft.Scripting.Actions {
 30    public class EventTracker : MemberTracker {
 31        // For each instance of the class that declares the event there is a list of pairs in a table 
 32        // (like if we added an instance field for each instance event into its declaring class). 
 33        // We use _staticTarget for a static event (it is bound to its declaring type).
 34        // Each pair in the list holds on the stub handler that was added to the event delegate chain and the callable 
 35        // object that is passed to +=/-= operators. 
 36        private WeakDictionary<object, NormalHandlerList> _handlerLists;
 37        private static readonly object _staticTarget = new object();
 38
 39        private readonly EventInfo _eventInfo;
 40        private MethodInfo _addMethod;
 41        private MethodInfo _removeMethod;
 42
 43        internal EventTracker(EventInfo eventInfo) {
 44            Assert.NotNull(eventInfo);
 45            _eventInfo = eventInfo;
 46        }
 47
 48        public override Type DeclaringType {
 49            get { return _eventInfo.DeclaringType; }
 50        }
 51
 52        public override TrackerTypes MemberType {
 53            get { return TrackerTypes.Event; }
 54        }
 55
 56        public override string Name {
 57            get { return _eventInfo.Name; }
 58        }
 59
 60        public EventInfo Event {
 61            get {
 62                return _eventInfo;
 63            }
 64        }
 65
 66        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
 67        public MethodInfo GetCallableAddMethod() {
 68            if (_addMethod == null) {
 69                _addMethod = _eventInfo.GetAddMethod(true);
 70            }
 71            return _addMethod;
 72        }
 73
 74        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
 75        public MethodInfo GetCallableRemoveMethod() {
 76            if (_removeMethod == null) {
 77                _removeMethod = _eventInfo.GetRemoveMethod(true);
 78            }
 79            return _removeMethod;
 80        }
 81
 82        /// <summary>
 83        /// Doesn't need to check PrivateBinding setting: no method that is part of the event is public the entire event is private. 
 84        /// If the code has already a reference to the event tracker instance for a private event its "static-ness" is not influenced 
 85        /// by private-binding setting.
 86        /// </summary>
 87        public bool IsStatic {
 88            get {
 89                MethodInfo mi = Event.GetAddMethod(false) ??
 90                    Event.GetRemoveMethod(false) ??
 91                    Event.GetRaiseMethod(false) ??
 92                    Event.GetAddMethod(true) ??
 93                    Event.GetRemoveMethod(true) ??
 94                    Event.GetRaiseMethod(true);
 95
 96                MethodInfo m;
 97                Debug.Assert(
 98                    ((m = Event.GetAddMethod(true)) == null || m.IsStatic == mi.IsStatic) &&
 99                    ((m = Event.GetRaiseMethod(true)) == null || m.IsStatic == mi.IsStatic) &&
100                    ((m = Event.GetRaiseMethod(true)) == null || m.IsStatic == mi.IsStatic),
101                    "Methods are either all static or all instance."
102                );
103                
104                return mi.IsStatic;
105            }
106        }
107
108        protected internal override DynamicMetaObject GetBoundValue(OverloadResolverFactory resolverFactory, ActionBinder binder, Type type, DynamicMetaObject instance) {
109            return binder.ReturnMemberTracker(type, new BoundMemberTracker(this, instance));
110        }
111
112        public override MemberTracker BindToInstance(DynamicMetaObject instance) {
113            if (IsStatic) {
114                return this;
115            }
116
117            return new BoundMemberTracker(this, instance);
118        }
119
120        public override string ToString() {
121            return _eventInfo.ToString();
122        }
123
124        public void AddHandler(object target, object handler, DynamicDelegateCreator delegateCreator) {
125            ContractUtils.RequiresNotNull(handler, "handler");
126            ContractUtils.RequiresNotNull(delegateCreator, "delegateCreator");
127
128            Delegate delegateHandler;
129            HandlerList stubs;
130
131            // we can add event directly (signature does match):
132            if (_eventInfo.EventHandlerType.IsAssignableFrom(handler.GetType())) {
133                delegateHandler = (Delegate)handler;
134                stubs = null;
135            } else {
136                // create signature converting stub:
137                delegateHandler = delegateCreator.GetDelegate(handler, _eventInfo.EventHandlerType);
138                stubs = GetHandlerList(target);
139            }
140
141            var add = GetCallableAddMethod();
142
143            // TODO (tomat): this used to use event.ReflectedType, is it still correct?
144            if (target != null) {
145                add = CompilerHelpers.TryGetCallableMethod(target.GetType(), add);
146            }
147
148            add.Invoke(target, new object[] { delegateHandler });
149
150            if (stubs != null) {
151                // remember the stub so that we could search for it on removal:
152                stubs.AddHandler(handler, delegateHandler);
153            }
154        }
155
156        public void RemoveHandler(object target, object handler, IEqualityComparer<object> objectComparer) {
157            ContractUtils.RequiresNotNull(handler, "handler");
158            ContractUtils.RequiresNotNull(objectComparer, "objectComparer");
159
160            Delegate delegateHandler;
161            if (_eventInfo.EventHandlerType.IsAssignableFrom(handler.GetType())) {
162                delegateHandler = (Delegate)handler;
163            } else {
164                delegateHandler = GetHandlerList(target).RemoveHandler(handler, objectComparer);
165            }
166
167            if (delegateHandler != null) {
168                GetCallableRemoveMethod().Invoke(target, new object[] { delegateHandler });
169            }
170        }
171
172        #region Private Implementation Details
173
174        private HandlerList GetHandlerList(object instance) {
175#if FEATURE_COM
176            if (TypeUtils.IsComObject(instance)) {
177                return GetComHandlerList(instance);
178            }
179#endif
180
181            if (_handlerLists == null) {
182                Interlocked.CompareExchange(ref _handlerLists, new WeakDictionary<object, NormalHandlerList>(), null);
183            }
184
185            if (instance == null) {
186                // targetting a static method, we'll use a random object
187                // as our place holder here...
188                instance = _staticTarget;
189            }
190
191            lock (_handlerLists) {
192                NormalHandlerList result;
193                if (_handlerLists.TryGetValue(instance, out result)) {
194                    return result;
195                }
196
197                result = new NormalHandlerList();
198                _handlerLists[instance] = result;
199                return result;
200            }
201        }
202
203#if FEATURE_COM
204        /// <summary>
205        /// Gets the stub list for a COM Object.  For COM objects we store the stub list
206        /// directly on the object using the Marshal APIs.  This allows us to not have
207        /// any circular references to deal with via weak references which are challenging
208        /// in the face of COM.
209        /// </summary>
210        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
211        private HandlerList GetComHandlerList(object instance) {
212            HandlerList hl = (HandlerList)Marshal.GetComObjectData(instance, this);
213            if (hl == null) {
214                lock (_staticTarget) {
215                    hl = (HandlerList)Marshal.GetComObjectData(instance, this);
216                    if (hl == null) {
217                        hl = new ComHandlerList();
218                        if (!Marshal.SetComObjectData(instance, this, hl)) {
219                            throw new COMException("Failed to set COM Object Data");
220                        }
221                    }
222                }
223            }
224
225            return hl;
226        }
227#endif
228
229        /// <summary>
230        /// Holds on a list of delegates hooked to the event. 
231        /// We need the list because we cannot enumerate the delegates hooked to CLR event and we need to do so in 
232        /// handler removal (we need to do custom delegate comparison there). If BCL enables the enumeration we could remove this.
233        /// </summary>
234        private abstract class HandlerList {
235            public abstract void AddHandler(object callableObject, Delegate handler);
236            public abstract Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer);
237        }
238
239#if !SILVERLIGHT
240        private sealed class ComHandlerList : HandlerList {
241            /// <summary>
242            /// Storage for the handlers - a key value pair of the callable object and the delegate handler.
243            /// </summary>
244            private readonly CopyOnWriteList<KeyValuePair<object, object>> _handlers = new CopyOnWriteList<KeyValuePair<object, object>>();
245
246            public override void AddHandler(object callableObject, Delegate handler) {
247                Assert.NotNull(handler);
248                _handlers.Add(new KeyValuePair<object, object>(callableObject, handler));
249            }
250
251            public override Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer) {
252                List<KeyValuePair<object, object>> copyOfHandlers = _handlers.GetCopyForRead();
253                for (int i = copyOfHandlers.Count - 1; i >= 0; i--) {
254                    object key = copyOfHandlers[i].Key;
255                    object value = copyOfHandlers[i].Value;
256
257                    if (comparer.Equals(key, callableObject)) {
258                        Delegate handler = (Delegate)value;
259                        _handlers.RemoveAt(i);
260                        return handler;
261                    }
262                }
263
264                return null;
265            }
266        }
267#endif
268
269        private sealed class NormalHandlerList : HandlerList {
270            /// <summary>
271            /// Storage for the handlers - a key value pair of the callable object and the delegate handler.
272            /// 
273            /// The delegate handler is closed over the callable object.  Therefore as long as the object is alive the
274            /// delegate will stay alive and so will the callable object.  That means it's fine to have a weak reference
275            /// to both of these objects.
276            /// </summary>
277            private readonly CopyOnWriteList<KeyValuePair<WeakReference, WeakReference>> _handlers = new CopyOnWriteList<KeyValuePair<WeakReference, WeakReference>>();
278
279            public NormalHandlerList() {
280            }
281
282            public override void AddHandler(object callableObject, Delegate handler) {
283                Assert.NotNull(handler);
284                _handlers.Add(new KeyValuePair<WeakReference, WeakReference>(new WeakReference(callableObject), new WeakReference(handler)));
285            }
286
287            public override Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer) {
288                List<KeyValuePair<WeakReference, WeakReference>> copyOfHandlers = _handlers.GetCopyForRead();
289                for (int i = copyOfHandlers.Count - 1; i >= 0; i--) {
290                    object key = copyOfHandlers[i].Key.Target;
291                    object value = copyOfHandlers[i].Value.Target;
292
293                    if (key != null && value != null && comparer.Equals(key, callableObject)) {
294                        Delegate handler = (Delegate)value;
295                        _handlers.RemoveAt(i);
296                        return handler;
297                    }
298                }
299
300                return null;
301            }
302        }
303
304        #endregion
305    }
306}