PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Microsoft.Scripting/Actions/EventTracker.cs

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