/Microsoft.Scripting/Actions/EventTracker.cs
C# | 311 lines | 212 code | 48 blank | 51 comment | 45 complexity | 2c63b7fdf973cd1b796aaba5348305e4 MD5 | raw file
- /* ****************************************************************************
- *
- * Copyright (c) Microsoft Corporation.
- *
- * This source code is subject to terms and conditions of the Microsoft Public License. A
- * copy of the license can be found in the License.html file at the root of this distribution. If
- * you cannot locate the Microsoft Public License, please send an email to
- * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
- * by the terms of the Microsoft Public License.
- *
- * You must not remove this notice, or any other, from this software.
- *
- *
- * ***************************************************************************/
-
- #if CODEPLEX_40
- using System;
- #else
- using System; using Microsoft;
- #endif
- using System.Diagnostics;
- #if CODEPLEX_40
- using System.Linq.Expressions;
- #else
- using Microsoft.Linq.Expressions;
- #endif
- using System.Reflection;
- using Microsoft.Contracts;
- using System.Collections.Generic;
- using Microsoft.Scripting.Utils;
- using Microsoft.Scripting.Runtime;
- using System.Runtime.InteropServices;
- using Microsoft.Scripting.Generation;
- using System.Threading;
-
- namespace Microsoft.Scripting.Actions {
- public class EventTracker : MemberTracker {
- // For each instance of the class that declares the event there is a list of pairs in a table
- // (like if we added an instance field for each instance event into its declaring class).
- // We use _staticTarget for a static event (it is bound to its declaring type).
- // Each pair in the list holds on the stub handler that was added to the event delegate chain and the callable
- // object that is passed to +=/-= operators.
- private WeakDictionary<object, NormalHandlerList> _handlerLists;
- private static readonly object _staticTarget = new object();
-
- private readonly EventInfo _eventInfo;
- private MethodInfo _addMethod;
- private MethodInfo _removeMethod;
-
- internal EventTracker(EventInfo eventInfo) {
- Assert.NotNull(eventInfo);
- _eventInfo = eventInfo;
- }
-
- public override Type DeclaringType {
- get { return _eventInfo.DeclaringType; }
- }
-
- public override TrackerTypes MemberType {
- get { return TrackerTypes.Event; }
- }
-
- public override string Name {
- get { return _eventInfo.Name; }
- }
-
- public EventInfo Event {
- get {
- return _eventInfo;
- }
- }
-
- public MethodInfo GetCallableAddMethod() {
- if (_addMethod == null) {
- _addMethod = CompilerHelpers.TryGetCallableMethod(_eventInfo.GetAddMethod(true));
- }
- return _addMethod;
- }
-
- public MethodInfo GetCallableRemoveMethod() {
- if (_removeMethod == null) {
- _removeMethod = CompilerHelpers.TryGetCallableMethod(_eventInfo.GetRemoveMethod(true));
- }
- return _removeMethod;
- }
-
- /// <summary>
- /// Doesn't need to check PrivateBinding setting: no method that is part of the event is public the entire event is private.
- /// If the code has already a reference to the event tracker instance for a private event its "static-ness" is not influenced
- /// by private-binding setting.
- /// </summary>
- public bool IsStatic {
- get {
- MethodInfo mi = Event.GetAddMethod(false) ??
- Event.GetRemoveMethod(false) ??
- Event.GetRaiseMethod(false) ??
- Event.GetAddMethod(true) ??
- Event.GetRemoveMethod(true) ??
- Event.GetRaiseMethod(true);
-
- MethodInfo m;
- Debug.Assert(
- ((m = Event.GetAddMethod(true)) == null || m.IsStatic == mi.IsStatic) &&
- ((m = Event.GetRaiseMethod(true)) == null || m.IsStatic == mi.IsStatic) &&
- ((m = Event.GetRaiseMethod(true)) == null || m.IsStatic == mi.IsStatic),
- "Methods are either all static or all instance."
- );
-
- return mi.IsStatic;
- }
- }
-
- protected internal override Expression GetBoundValue(Expression context, ActionBinder binder, Type type, Expression instance) {
- return binder.ReturnMemberTracker(type, new BoundMemberTracker(this, instance));
- }
-
- public override MemberTracker BindToInstance(Expression instance) {
- if (IsStatic) {
- return this;
- }
-
- return new BoundMemberTracker(this, instance);
- }
-
- [Confined]
- public override string ToString() {
- return _eventInfo.ToString();
- }
-
- public void AddHandler(object target, object handler, LanguageContext language) {
- ContractUtils.RequiresNotNull(handler, "handler");
- ContractUtils.RequiresNotNull(language, "language");
-
- Delegate delegateHandler;
- HandlerList stubs;
-
- // we can add event directly (signature does match):
- if (_eventInfo.EventHandlerType.IsAssignableFrom(handler.GetType())) {
- delegateHandler = (Delegate)handler;
- stubs = null;
- } else {
- // create signature converting stub:
- delegateHandler = BinderOps.GetDelegate(language, handler, _eventInfo.EventHandlerType);
- stubs = GetHandlerList(target);
- }
-
- GetCallableAddMethod().Invoke(target, new object[] { delegateHandler });
-
- if (stubs != null) {
- // remember the stub so that we could search for it on removal:
- stubs.AddHandler(handler, delegateHandler);
- }
- }
-
- public void RemoveHandler(object target, object handler, IEqualityComparer<object> objectComparer) {
- ContractUtils.RequiresNotNull(handler, "handler");
- ContractUtils.RequiresNotNull(objectComparer, "objectComparer");
-
- Delegate delegateHandler;
- if (_eventInfo.EventHandlerType.IsAssignableFrom(handler.GetType())) {
- delegateHandler = (Delegate)handler;
- } else {
- delegateHandler = GetHandlerList(target).RemoveHandler(handler, objectComparer);
- }
-
- if (delegateHandler != null) {
- GetCallableRemoveMethod().Invoke(target, new object[] { delegateHandler });
- }
- }
-
- #region Private Implementation Details
-
- private HandlerList GetHandlerList(object instance) {
- #if !SILVERLIGHT
- if (instance != null && IsComObject(instance)) {
- return GetComHandlerList(instance);
- }
- #endif
-
- if (_handlerLists == null) {
- Interlocked.CompareExchange(ref _handlerLists, new WeakDictionary<object, NormalHandlerList>(), null);
- }
-
- if (instance == null) {
- // targetting a static method, we'll use a random object
- // as our place holder here...
- instance = _staticTarget;
- }
-
- lock (_handlerLists) {
- NormalHandlerList result;
- if (_handlerLists.TryGetValue(instance, out result)) {
- return result;
- }
-
- result = new NormalHandlerList();
- _handlerLists[instance] = result;
- return result;
- }
- }
-
- #if !SILVERLIGHT
- private static readonly Type ComObjectType = typeof(object).Assembly.GetType("System.__ComObject");
-
- internal static bool IsComObject(object obj) {
- // we can't use System.Runtime.InteropServices.Marshal.IsComObject(obj) since it doesn't work in partial trust
- return obj != null && ComObjectType.IsAssignableFrom(obj.GetType());
- }
-
- /// <summary>
- /// Gets the stub list for a COM Object. For COM objects we store the stub list
- /// directly on the object using the Marshal APIs. This allows us to not have
- /// any circular references to deal with via weak references which are challenging
- /// in the face of COM.
- /// </summary>
- private HandlerList GetComHandlerList(object instance) {
- HandlerList hl = (HandlerList)Marshal.GetComObjectData(instance, this);
- if (hl == null) {
- lock (_staticTarget) {
- hl = (HandlerList)Marshal.GetComObjectData(instance, this);
- if (hl == null) {
- hl = new ComHandlerList();
- if (!Marshal.SetComObjectData(instance, this, hl)) {
- throw new COMException("Failed to set COM Object Data");
- }
- }
- }
- }
-
- return hl;
- }
- #endif
-
- /// <summary>
- /// Holds on a list of delegates hooked to the event.
- /// We need the list because we cannot enumerate the delegates hooked to CLR event and we need to do so in
- /// handler removal (we need to do custom delegate comparison there). If BCL enables the enumeration we could remove this.
- /// </summary>
- private abstract class HandlerList {
- public abstract void AddHandler(object callableObject, Delegate handler);
- public abstract Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer);
- }
-
- #if !SILVERLIGHT
- private sealed class ComHandlerList : HandlerList {
- /// <summary>
- /// Storage for the handlers - a key value pair of the callable object and the delegate handler.
- /// </summary>
- private readonly CopyOnWriteList<KeyValuePair<object, object>> _handlers = new CopyOnWriteList<KeyValuePair<object, object>>();
-
- public override void AddHandler(object callableObject, Delegate handler) {
- Assert.NotNull(handler);
- _handlers.Add(new KeyValuePair<object, object>(callableObject, handler));
- }
-
- public override Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer) {
- List<KeyValuePair<object, object>> copyOfHandlers = _handlers.GetCopyForRead();
- for (int i = copyOfHandlers.Count - 1; i >= 0; i--) {
- object key = copyOfHandlers[i].Key;
- object value = copyOfHandlers[i].Value;
-
- if (comparer.Equals(key, callableObject)) {
- Delegate handler = (Delegate)value;
- _handlers.RemoveAt(i);
- return handler;
- }
- }
-
- return null;
- }
- }
- #endif
-
- private sealed class NormalHandlerList : HandlerList {
- /// <summary>
- /// Storage for the handlers - a key value pair of the callable object and the delegate handler.
- ///
- /// The delegate handler is closed over the callable object. Therefore as long as the object is alive the
- /// delegate will stay alive and so will the callable object. That means it's fine to have a weak reference
- /// to both of these objects.
- /// </summary>
- private readonly CopyOnWriteList<KeyValuePair<WeakReference, WeakReference>> _handlers = new CopyOnWriteList<KeyValuePair<WeakReference, WeakReference>>();
-
- public NormalHandlerList() {
- }
-
- public override void AddHandler(object callableObject, Delegate handler) {
- Assert.NotNull(handler);
- _handlers.Add(new KeyValuePair<WeakReference, WeakReference>(new WeakReference(callableObject), new WeakReference(handler)));
- }
-
- public override Delegate RemoveHandler(object callableObject, IEqualityComparer<object> comparer) {
- List<KeyValuePair<WeakReference, WeakReference>> copyOfHandlers = _handlers.GetCopyForRead();
- for (int i = copyOfHandlers.Count - 1; i >= 0; i--) {
- object key = copyOfHandlers[i].Key.Target;
- object value = copyOfHandlers[i].Value.Target;
-
- if (key != null && value != null && comparer.Equals(key, callableObject)) {
- Delegate handler = (Delegate)value;
- _handlers.RemoveAt(i);
- return handler;
- }
- }
-
- return null;
- }
- }
-
- #endregion
- }
- }