File: System\ComponentModel\ReflectEventDescriptor.cs
Web Access
Project: src\src\libraries\System.ComponentModel.TypeConverter\src\System.ComponentModel.TypeConverter.csproj (System.ComponentModel.TypeConverter)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
 
namespace System.ComponentModel
{
    /// <summary>
    /// ReflectEventDescriptor defines an event. Events are the main way that a user can get
    /// run-time notifications from a component.
    /// The ReflectEventDescriptor class takes a component class that the event lives on,
    /// the event name, the type of the event handling delegate, and various
    /// attributes for the event.
    /// Every event has a structure through which it passes it's information. The base
    /// structure, Event, is empty, and there is a default instance, Event.EMPTY, which
    /// is usually passed. When addOnXXX is invoked, it needs a pointer to a method
    /// that takes a source object (the object that fired the event) and a structure
    /// particular to that event. It also needs a pointer to the instance of the
    /// object the method should be invoked on. These two things are what composes a
    /// delegate. An event handler is
    /// a delegate, and the compiler recognizes a special delegate syntax that makes
    /// using delegates easy.
    /// For example, to listen to the click event on a button in class Foo, the
    /// following code will suffice:
    ///
    /// <code>
    /// class Foo {
    /// Button button1 = new Button();
    /// void button1_click(Object sender, Event e) {
    /// // do something on button1 click.
    /// }
    /// public Foo() {
    /// button1.addOnClick(button1_click);
    /// }
    /// }
    /// </code>
    /// For an event named XXX, a YYYEvent structure, and a YYYEventHandler delegate,
    /// a component writer is required to implement two methods of the following
    /// form:
    /// <code>
    /// public void addOnXXX(YYYEventHandler handler);
    /// public void removeOnXXX(YYYEventHandler handler);
    /// </code>
    /// YYYEventHandler should be an event handler declared as
    /// <code>
    /// public multicast delegate void YYYEventHandler(Object sender, YYYEvent e);
    /// </code>
    /// Note that this event was declared as a multicast delegate. This allows multiple
    /// listeners on an event. This is not a requirement.
    /// Various attributes can be passed to the ReflectEventDescriptor, as are described in
    /// Attribute.
    /// ReflectEventDescriptors can be obtained by a user programmatically through the
    /// ComponentManager.
    /// </summary>
    internal sealed class ReflectEventDescriptor : EventDescriptor
    {
        private Type? _type;           // the delegate type for the event
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
        private readonly Type _componentClass; // the class of the component this info is for
 
        private MethodInfo? _addMethod;     // the method to use when adding an event
        private MethodInfo? _removeMethod;  // the method to use when removing an event
        private EventInfo? _realEvent;      // actual event info... may be null
        private bool _filledMethods;   // did we already call FillMethods() once?
 
        /// <summary>
        /// This is the main constructor for an ReflectEventDescriptor.
        /// </summary>
        public ReflectEventDescriptor(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentClass,
            string name,
            Type type,
            Attribute[] attributes)
            : base(name, attributes)
        {
            if (componentClass == null)
            {
                throw new ArgumentException(SR.Format(SR.InvalidNullArgument, nameof(componentClass)));
            }
            if (type == null || !(typeof(Delegate)).IsAssignableFrom(type))
            {
                throw new ArgumentException(SR.Format(SR.ErrorInvalidEventType, name));
            }
            Debug.Assert(type.IsSubclassOf(typeof(Delegate)), $"Not a valid ReflectEvent: {componentClass.FullName}. {name} {type.FullName}");
            _componentClass = componentClass;
            _type = type;
        }
 
        public ReflectEventDescriptor(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentClass,
            EventInfo eventInfo)
            : base(eventInfo.Name, Array.Empty<Attribute>())
        {
            Debug.Assert(eventInfo.ReflectedType!.IsAssignableFrom(componentClass), "eventInfo.ReflectedType is used below, but only componentClass is annotated with DynamicallyAccessedMembers. Ensure ReflectedType is in componentClass's hierarchy.");
 
            _componentClass = componentClass ?? throw new ArgumentException(SR.Format(SR.InvalidNullArgument, nameof(componentClass)));
            _realEvent = eventInfo;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
            Justification = "componentClass is annotated as preserve All members, so it can call ReflectEventDescriptor ctor.")]
        internal static ReflectEventDescriptor CreateWithRegisteredType(Type componentClass, EventInfo eventInfo) => new ReflectEventDescriptor(componentClass, eventInfo);
 
        /// <summary>
        /// This constructor takes an existing ReflectEventDescriptor and modifies it by merging in the
        /// passed-in attributes.
        /// </summary>
        public ReflectEventDescriptor(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentType,
            EventDescriptor oldReflectEventDescriptor,
            Attribute[] attributes)
            : base(oldReflectEventDescriptor, attributes)
        {
            _componentClass = componentType;
            _type = oldReflectEventDescriptor.EventType;
 
            if (oldReflectEventDescriptor is ReflectEventDescriptor desc)
            {
                _addMethod = desc._addMethod;
                _removeMethod = desc._removeMethod;
                _filledMethods = true;
            }
        }
 
        /// <summary>
        /// Retrieves the type of the component this EventDescriptor is bound to.
        /// </summary>
        public override Type ComponentType => _componentClass;
 
        /// <summary>
        /// Retrieves the type of the delegate for this event.
        /// </summary>
        public override Type EventType
        {
            get
            {
                FillMethods();
                return _type!;
            }
        }
 
        /// <summary>
        /// Indicates whether the delegate type for this event is a multicast delegate.
        /// </summary>
        public override bool IsMulticast => (typeof(MulticastDelegate)).IsAssignableFrom(EventType);
 
        /// <summary>
        /// This adds the delegate value as a listener to when this event is fired
        /// by the component, invoking the addOnXXX method.
        /// </summary>
        public override void AddEventHandler(object component, Delegate value)
        {
            FillMethods();
 
            if (component != null)
            {
                ISite? site = GetSite(component);
                IComponentChangeService? changeService = null;
 
                // Announce that we are about to change this component
                if (site != null)
                {
                    changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                }
 
                if (changeService != null)
                {
                    try
                    {
                        changeService.OnComponentChanging(component, this);
                    }
                    catch (CheckoutException coEx)
                    {
                        if (coEx == CheckoutException.Canceled)
                        {
                            return;
                        }
                        throw;
                    }
                    changeService.OnComponentChanging(component, this);
                }
 
                bool shadowed = false;
 
                if (site != null && site.DesignMode)
                {
                    // Events are final, so just check the class
                    if (EventType != value.GetType())
                    {
                        throw new ArgumentException(SR.Format(SR.ErrorInvalidEventHandler, Name));
                    }
                    IDictionaryService? dict = (IDictionaryService?)site.GetService(typeof(IDictionaryService));
                    if (dict != null)
                    {
                        Delegate? eventdesc = (Delegate?)dict.GetValue(this);
                        eventdesc = Delegate.Combine(eventdesc, value);
                        dict.SetValue(this, eventdesc);
                        shadowed = true;
                    }
                }
 
                if (!shadowed)
                {
                    _addMethod!.Invoke(component, new[] { value });
                }
 
                // Now notify the change service that the change was successful.
                changeService?.OnComponentChanged(component, this, null, value);
            }
        }
 
        /// <summary>
        /// Adds in custom attributes found on either the AddOn or RemoveOn method...
        /// </summary>
        protected override void FillAttributes(IList attributes)
        {
            // The order that we fill in attributes is critical. The list of attributes will be
            // filtered so that matching attributes at the end of the list replace earlier matches
            // (last one in wins). Therefore, the two categories of attributes we add must be
            // added as follows:
            //
            // 1. Attributes of the event, from base class to most derived. This way
            //     derived class attributes replace base class attributes.
            //
            // 2. Attributes from our base MemberDescriptor. While this seems opposite of what
            //     we want, MemberDescriptor only has attributes if someone passed in a new
            //     set in the constructor. Therefore, these attributes always
            //     supersede existing values.
 
            FillMethods();
            Debug.Assert(_componentClass != null, "Must have a component class for FilterAttributes");
            if (_realEvent != null)
            {
                FillEventInfoAttribute(_realEvent, attributes);
            }
            else
            {
                Debug.Assert(_removeMethod != null, $"Null remove method for {Name}");
                FillSingleMethodAttribute(_removeMethod, attributes);
 
                Debug.Assert(_addMethod != null, $"Null remove method for {Name}");
                FillSingleMethodAttribute(_addMethod, attributes);
            }
 
            // Include the base attributes. These override all attributes on the actual
            // property, so we want to add them last.
            base.FillAttributes(attributes);
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "currentReflectType is in _componentClass's hierarchy. Since _componentClass is annotated with All, this means currentReflectType is annotated with All as well.")]
        private void FillEventInfoAttribute(EventInfo realEventInfo, IList attributes)
        {
            string eventName = realEventInfo.Name;
            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
            Type currentReflectType = realEventInfo.ReflectedType!;
            Debug.Assert(currentReflectType != null, "currentReflectType cannot be null");
            Debug.Assert(currentReflectType.IsAssignableFrom(_componentClass), "currentReflectType must be in _componentClass's hierarchy");
            int depth = 0;
 
            // First, calculate the depth of the object hierarchy. We do this so we can do a single
            // object create for an array of attributes.
            while (currentReflectType != typeof(object))
            {
                depth++;
                currentReflectType = currentReflectType.BaseType!;
            }
 
            if (depth > 0)
            {
                // Now build up an array in reverse order
                currentReflectType = realEventInfo.ReflectedType!;
                Attribute[][] attributeStack = new Attribute[depth][];
 
                while (currentReflectType != typeof(object))
                {
                    // Fill in our member info so we can get at the custom attributes.
                    MemberInfo? memberInfo = currentReflectType!.GetEvent(eventName, bindingFlags);
 
                    // Get custom attributes for the member info.
                    if (memberInfo != null)
                    {
                        attributeStack[--depth] = ReflectTypeDescriptionProvider.ReflectGetAttributes(memberInfo);
                    }
 
                    // Ready for the next loop iteration.
                    currentReflectType = currentReflectType.BaseType!;
                }
 
                // Now trawl the attribute stack so that we add attributes
                // from base class to most derived.
                foreach (Attribute[] attributeArray in attributeStack)
                {
                    if (attributeArray != null)
                    {
                        foreach (Attribute attr in attributeArray)
                        {
                            attributes.Add(attr);
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// This fills the get and set method fields of the event info. It is shared
        /// by the various constructors.
        /// </summary>
        private void FillMethods()
        {
            if (_filledMethods)
                return;
 
            if (_realEvent != null)
            {
                _addMethod = _realEvent.GetAddMethod();
                _removeMethod = _realEvent.GetRemoveMethod();
 
                EventInfo? defined = null;
 
                if (_addMethod == null || _removeMethod == null)
                {
                    Type? start = _componentClass.BaseType;
                    while (start != null && start != typeof(object))
                    {
                        BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
                        EventInfo test = start.GetEvent(_realEvent.Name, bindingFlags)!;
                        if (test.GetAddMethod() != null)
                        {
                            defined = test;
                            break;
                        }
                    }
                }
 
                if (defined != null)
                {
                    _addMethod = defined.GetAddMethod();
                    _removeMethod = defined.GetRemoveMethod();
                    _type = defined.EventHandlerType;
                }
                else
                {
                    _type = _realEvent.EventHandlerType;
                }
            }
            else
            {
                // first, try to get the eventInfo...
                _realEvent = _componentClass.GetEvent(Name);
                if (_realEvent != null)
                {
                    // if we got one, just recurse and return.
                    FillMethods();
                    return;
                }
 
                Type[] argsType = new Type[] { _type! };
                _addMethod = FindMethod(_componentClass, "AddOn" + Name, argsType, typeof(void));
                _removeMethod = FindMethod(_componentClass, "RemoveOn" + Name, argsType, typeof(void));
                if (_addMethod == null || _removeMethod == null)
                {
                    Debug.Fail($"Missing event accessors for {_componentClass.FullName}. {Name}");
                    throw new ArgumentException(SR.Format(SR.ErrorMissingEventAccessors, Name));
                }
            }
 
            _filledMethods = true;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "currentReflectType is in _componentClass's hierarchy. Since _componentClass is annotated with All, this means currentReflectType is annotated with All as well.")]
        private void FillSingleMethodAttribute(MethodInfo realMethodInfo, IList attributes)
        {
            string methodName = realMethodInfo.Name;
            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
            Type? currentReflectType = realMethodInfo.ReflectedType;
            Debug.Assert(currentReflectType != null, "currentReflectType cannot be null");
            Debug.Assert(currentReflectType.IsAssignableFrom(_componentClass), "currentReflectType must be in _componentClass's hierarchy");
 
            // First, calculate the depth of the object hierarchy. We do this so we can do a single
            // object create for an array of attributes.
            //
            int depth = 0;
            while (currentReflectType != null && currentReflectType != typeof(object))
            {
                depth++;
                currentReflectType = currentReflectType.BaseType;
            }
 
            if (depth > 0)
            {
                // Now build up an array in reverse order
                //
                currentReflectType = realMethodInfo.ReflectedType;
                Attribute[][] attributeStack = new Attribute[depth][];
 
                while (currentReflectType != null && currentReflectType != typeof(object))
                {
                    // Fill in our member info so we can get at the custom attributes.
                    MemberInfo? memberInfo = currentReflectType.GetMethod(methodName, bindingFlags);
 
                    // Get custom attributes for the member info.
                    if (memberInfo != null)
                    {
                        attributeStack[--depth] = ReflectTypeDescriptionProvider.ReflectGetAttributes(memberInfo);
                    }
 
                    // Ready for the next loop iteration.
                    currentReflectType = currentReflectType.BaseType;
                }
 
                // Now trawl the attribute stack so that we add attributes
                // from base class to most derived.
                foreach (Attribute[] attributeArray in attributeStack)
                {
                    if (attributeArray != null)
                    {
                        foreach (Attribute attr in attributeArray)
                        {
                            attributes.Add(attr);
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// This will remove the delegate value from the event chain so that
        /// it no longer gets events from this component.
        /// </summary>
        public override void RemoveEventHandler(object component, Delegate value)
        {
            FillMethods();
 
            if (component != null)
            {
                ISite? site = GetSite(component);
                IComponentChangeService? changeService = null;
 
                // Announce that we are about to change this component
                if (site != null)
                {
                    changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                }
 
                if (changeService != null)
                {
                    try
                    {
                        changeService.OnComponentChanging(component, this);
                    }
                    catch (CheckoutException coEx)
                    {
                        if (coEx == CheckoutException.Canceled)
                        {
                            return;
                        }
                        throw;
                    }
                    changeService.OnComponentChanging(component, this);
                }
 
                bool shadowed = false;
 
                if (site != null && site.DesignMode)
                {
                    IDictionaryService? dict = (IDictionaryService?)site.GetService(typeof(IDictionaryService));
                    if (dict != null)
                    {
                        Delegate? del = (Delegate?)dict.GetValue(this);
                        del = Delegate.Remove(del, value);
                        dict.SetValue(this, del);
                        shadowed = true;
                    }
                }
 
                if (!shadowed)
                {
                    _removeMethod!.Invoke(component, new[] { value });
                }
 
                // Now notify the change service that the change was successful.
                changeService?.OnComponentChanged(component, this, null, value);
            }
        }
    }
}