File: System\ComponentModel\ReflectPropertyDescriptor.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.Globalization;
using System.Reflection;
 
namespace System.ComponentModel
{
    /// <summary>
    /// ReflectPropertyDescriptor defines a property. Properties are the main way that a user can
    /// set up the state of a component.
    /// The ReflectPropertyDescriptor class takes a component class that the property lives on,
    /// a property name, the type of the property, and various attributes for the
    /// property.
    /// For a property named XXX of type YYY, the associated component class is
    /// required to implement two methods of the following
    /// form:
    ///
    /// <code>
    /// public YYY GetXXX();
    /// public void SetXXX(YYY value);
    /// </code>
    /// The component class can optionally implement two additional methods of
    /// the following form:
    /// <code>
    /// public boolean ShouldSerializeXXX();
    /// public void ResetXXX();
    /// </code>
    /// These methods deal with a property's default value. The ShouldSerializeXXX()
    /// method returns true if the current value of the XXX property is different
    /// than it's default value, so that it should be persisted out. The ResetXXX()
    /// method resets the XXX property to its default value. If the ReflectPropertyDescriptor
    /// includes the default value of the property (using the DefaultValueAttribute),
    /// the ShouldSerializeXXX() and ResetXXX() methods are ignored.
    /// If the ReflectPropertyDescriptor includes a reference to an editor
    /// then that value editor will be used to
    /// edit the property. Otherwise, a system-provided editor will be used.
    /// Various attributes can be passed to the ReflectPropertyDescriptor, as are described in
    /// Attribute.
    /// ReflectPropertyDescriptors can be obtained by a user programmatically through the
    /// ComponentManager.
    /// </summary>
    internal sealed class ReflectPropertyDescriptor : PropertyDescriptor
    {
        private const string ComponentClassIsProtected = "_componentClass is already a protected through [DynamicallyAccessedModifiers(All)] or is a registered type.";
 
        private static readonly object s_noValue = new object();
 
        private static readonly int s_bitDefaultValueQueried = InterlockedBitVector32.CreateMask();
        private static readonly int s_bitGetQueried = InterlockedBitVector32.CreateMask(s_bitDefaultValueQueried);
        private static readonly int s_bitSetQueried = InterlockedBitVector32.CreateMask(s_bitGetQueried);
        private static readonly int s_bitShouldSerializeQueried = InterlockedBitVector32.CreateMask(s_bitSetQueried);
        private static readonly int s_bitResetQueried = InterlockedBitVector32.CreateMask(s_bitShouldSerializeQueried);
        private static readonly int s_bitChangedQueried = InterlockedBitVector32.CreateMask(s_bitResetQueried);
        private static readonly int s_bitIPropChangedQueried = InterlockedBitVector32.CreateMask(s_bitChangedQueried);
        private static readonly int s_bitReadOnlyChecked = InterlockedBitVector32.CreateMask(s_bitIPropChangedQueried);
        private static readonly int s_bitAmbientValueQueried = InterlockedBitVector32.CreateMask(s_bitReadOnlyChecked);
        private static readonly int s_bitSetOnDemand = InterlockedBitVector32.CreateMask(s_bitAmbientValueQueried);
 
        private InterlockedBitVector32 _state;             // Contains the state bits for this property descriptor.
        private readonly Type _componentClass;             // used to determine if we should all on us or on the designer
        private readonly Type _type;                       // the data type of the property
        private object? _defaultValue;               // the default value of the property (or noValue)
        private object? _ambientValue;               // the ambient value of the property (or noValue)
        private PropertyInfo? _propInfo;                   // the property info
        private MethodInfo? _getMethod;                  // the property get method
        private MethodInfo? _setMethod;                  // the property set method
        private MethodInfo? _shouldSerializeMethod;      // the should serialize method
        private MethodInfo? _resetMethod;                // the reset property method
        private EventDescriptor? _realChangedEvent;           // <propertyname>Changed event handler on object
        private EventDescriptor? _realIPropChangedEvent;      // INotifyPropertyChanged.PropertyChanged event handler on object
        private readonly Type? _receiverType;               // Only set if we are an extender
 
        /// <summary>
        /// The main constructor for ReflectPropertyDescriptors.
        /// </summary>
        public ReflectPropertyDescriptor(
            Type componentClass,
            string name,
            Type type,
            Attribute[]? attributes)
            : base(name, attributes)
        {
            Debug.WriteLine($"Creating ReflectPropertyDescriptor for {componentClass?.FullName}.{name}");
 
            try
            {
                if (type == null)
                {
                    Debug.WriteLine($"type == null, name == {name}");
                    throw new ArgumentException(SR.Format(SR.ErrorInvalidPropertyType, name));
                }
                if (componentClass == null)
                {
                    Debug.WriteLine($"componentClass == null, name == {name}");
                    throw new ArgumentException(SR.Format(SR.InvalidNullArgument, nameof(componentClass)));
                }
                _type = type;
                _componentClass = componentClass;
            }
            catch (Exception t)
            {
                Debug.Fail($"Property '{name}' on component {componentClass?.FullName} failed to init.");
                Debug.Fail(t.ToString());
                throw;
            }
        }
 
        /// <summary>
        /// A constructor for ReflectPropertyDescriptors.
        /// </summary>
        public ReflectPropertyDescriptor(
            Type componentClass,
            string name,
            Type type,
            PropertyInfo propInfo,
            MethodInfo getMethod,
            MethodInfo? setMethod,
            Attribute[]? attrs)
            : this(componentClass, name, type, attrs)
        {
            _propInfo = propInfo;
            _getMethod = getMethod;
            _setMethod = setMethod;
            if (getMethod != null && propInfo != null && setMethod == null)
                _state.DangerousSet(s_bitGetQueried | s_bitSetOnDemand, true);
            else
                _state.DangerousSet(s_bitGetQueried | s_bitSetQueried, true);
        }
 
        /// <summary>
        /// A constructor for ReflectPropertyDescriptors that creates an extender property.
        /// </summary>
        [RequiresUnreferencedCode(PropertyDescriptorPropertyTypeMessage)]
        public ReflectPropertyDescriptor(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentClass,
            string name,
            Type type,
            Type receiverType,
            MethodInfo getMethod,
            MethodInfo? setMethod,
            Attribute[]? attrs)
            : this(componentClass, name, type, attrs)
        {
            _receiverType = receiverType;
            _getMethod = getMethod;
            _setMethod = setMethod;
            _state.DangerousSet(s_bitGetQueried | s_bitSetQueried, true);
        }
 
        /// <summary>
        /// This constructor takes an existing ReflectPropertyDescriptor and modifies it by merging in the
        /// passed-in attributes.
        /// </summary>
        [RequiresUnreferencedCode(PropertyDescriptorPropertyTypeMessage)]
        public ReflectPropertyDescriptor(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentClass,
            PropertyDescriptor oldReflectPropertyDescriptor,
            Attribute[] attributes)
            : base(oldReflectPropertyDescriptor, attributes)
        {
            _componentClass = componentClass;
            _type = oldReflectPropertyDescriptor.PropertyType;
 
            if (componentClass == null)
            {
                throw new ArgumentException(SR.Format(SR.InvalidNullArgument, nameof(componentClass)));
            }
 
            // If the classes are the same, we can potentially optimize the method fetch because
            // the old property descriptor may already have it.
            if (oldReflectPropertyDescriptor is ReflectPropertyDescriptor oldProp)
            {
                if (oldProp.ComponentType == componentClass)
                {
                    _propInfo = oldProp._propInfo;
                    _getMethod = oldProp._getMethod;
                    _setMethod = oldProp._setMethod;
                    _shouldSerializeMethod = oldProp._shouldSerializeMethod;
                    _resetMethod = oldProp._resetMethod;
                    _defaultValue = oldProp._defaultValue;
                    _ambientValue = oldProp._ambientValue;
                    _state = oldProp._state;
                }
 
                // Now we must figure out what to do with our default value. First, check to see
                // if the caller has provided an new default value attribute. If so, use it. Otherwise,
                // just let it be and it will be picked up on demand.
                //
                if (attributes != null)
                {
                    foreach (Attribute a in attributes)
                    {
                        if (a is DefaultValueAttribute dva)
                        {
                            _defaultValue = dva.Value;
                            // Default values for enums are often stored as their underlying integer type:
                            if (_defaultValue != null && PropertyType.IsEnum && PropertyType.GetEnumUnderlyingType() == _defaultValue.GetType())
                            {
                                _defaultValue = Enum.ToObject(PropertyType, _defaultValue);
                            }
 
                            _state.DangerousSet(s_bitDefaultValueQueried, true);
                        }
                        else if (a is AmbientValueAttribute ava)
                        {
                            _ambientValue = ava.Value;
                            _state.DangerousSet(s_bitAmbientValueQueried, true);
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Retrieves the ambient value for this property.
        /// </summary>
        private object AmbientValue
        {
            get
            {
                if (!_state[s_bitAmbientValueQueried])
                {
                    Attribute? a = Attributes[typeof(AmbientValueAttribute)];
                    if (a != null)
                    {
                        _ambientValue = ((AmbientValueAttribute)a).Value;
                    }
                    else
                    {
                        _ambientValue = s_noValue;
                    }
                    _state[s_bitAmbientValueQueried] = true;
                }
                return _ambientValue!;
            }
        }
 
        /// <summary>
        /// The EventDescriptor for the "{propertyname}Changed" event on the component, or null if there isn't one for this property.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        private EventDescriptor ChangedEventValue
        {
            get
            {
                if (!_state[s_bitChangedQueried])
                {
                    _realChangedEvent = TypeDescriptor.GetEvents(_componentClass)[Name + "Changed"];
                    _state[s_bitChangedQueried] = true;
                }
 
                return _realChangedEvent!;
            }
        }
 
        /// <summary>
        /// The EventDescriptor for the INotifyPropertyChanged.PropertyChanged event on the component, or null if there isn't one for this property.
        /// </summary>
        private EventDescriptor IPropChangedEventValue
        {
            get
            {
                if (!_state[s_bitIPropChangedQueried])
                {
                    if (typeof(INotifyPropertyChanged).IsAssignableFrom(ComponentType))
                    {
                        _realIPropChangedEvent = TypeDescriptor.GetEvents(typeof(INotifyPropertyChanged))["PropertyChanged"];
                    }
 
                    _state[s_bitIPropChangedQueried] = true;
                }
 
                return _realIPropChangedEvent!;
            }
        }
 
        /// <summary>
        /// Retrieves the type of the component this PropertyDescriptor is bound to.
        /// </summary>
        public override Type ComponentType => _componentClass;
 
        /// <summary>
        /// Retrieves the default value for this property.
        /// </summary>
        private object DefaultValue
        {
            get
            {
                if (!_state[s_bitDefaultValueQueried])
                {
                    Attribute? a = Attributes[typeof(DefaultValueAttribute)];
                    if (a != null)
                    {
                        // Default values for enums are often stored as their underlying integer type:
                        object? defaultValue = ((DefaultValueAttribute)a).Value;
                        bool storedAsUnderlyingType = defaultValue != null && PropertyType.IsEnum && PropertyType.GetEnumUnderlyingType() == defaultValue.GetType();
                        _defaultValue = storedAsUnderlyingType ?
                            Enum.ToObject(PropertyType, defaultValue!) :
                            defaultValue;
                    }
                    else
                    {
                        _defaultValue = s_noValue;
                    }
                    _state[s_bitDefaultValueQueried] = true;
                }
                return _defaultValue!;
            }
        }
 
        /// <summary>
        /// The GetMethod for this property
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2080:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        private MethodInfo GetMethodValue
        {
            get
            {
                if (!_state[s_bitGetQueried])
                {
                    if (_receiverType == null)
                    {
                        if (_propInfo == null)
                        {
                            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty;
                            _propInfo = _componentClass.GetProperty(Name, bindingFlags, binder: null, PropertyType, Type.EmptyTypes, Array.Empty<ParameterModifier>());
                        }
                        if (_propInfo != null)
                        {
                            _getMethod = _propInfo.GetGetMethod(nonPublic: true);
                        }
                        if (_getMethod == null)
                        {
                            throw new InvalidOperationException(SR.Format(SR.ErrorMissingPropertyAccessors, _componentClass.FullName + "." + Name));
                        }
                    }
                    else
                    {
                        _getMethod = FindMethod(_componentClass, "Get" + Name, new Type[] { _receiverType }, _type);
                        if (_getMethod == null)
                        {
                            throw new ArgumentException(SR.Format(SR.ErrorMissingPropertyAccessors, Name));
                        }
                    }
                    _state[s_bitGetQueried] = true;
                }
                return _getMethod!;
            }
        }
 
        /// <summary>
        /// Determines if this property is an extender property.
        /// </summary>
        private bool IsExtender => (_receiverType != null);
 
        /// <summary>
        /// Indicates whether this property is read only.
        /// </summary>
        public override bool IsReadOnly => SetMethodValue == null || ((ReadOnlyAttribute)Attributes[typeof(ReadOnlyAttribute)]!).IsReadOnly;
 
        /// <summary>
        /// Retrieves the type of the property.
        /// </summary>
        public override Type PropertyType => _type;
 
        /// <summary>
        /// Access to the reset method, if one exists for this property.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        private MethodInfo? ResetMethodValue
        {
            get
            {
                if (!_state[s_bitResetQueried])
                {
                    Type[] args;
 
                    if (_receiverType == null)
                    {
                        args = Type.EmptyTypes;
                    }
                    else
                    {
                        args = new Type[] { _receiverType };
                    }
 
                    _resetMethod = FindMethod(_componentClass, "Reset" + Name, args, typeof(void), /* publicOnly= */ false);
 
                    _state[s_bitResetQueried] = true;
                }
                return _resetMethod;
            }
        }
 
        /// <summary>
        /// Accessor for the set method
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2080:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        private MethodInfo? SetMethodValue
        {
            get
            {
                if (!_state[s_bitSetQueried] && _state[s_bitSetOnDemand])
                {
                    string name = _propInfo!.Name;
 
                    if (_setMethod == null)
                    {
                        for (Type? t = _componentClass.BaseType; t != null && t != typeof(object); t = t.BaseType)
                        {
                            BindingFlags bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
                            PropertyInfo? p = t.GetProperty(name, bindingFlags, binder: null, PropertyType, Type.EmptyTypes, null);
                            if (p != null)
                            {
                                _setMethod = p.GetSetMethod(nonPublic: false);
                                if (_setMethod != null)
                                {
                                    break;
                                }
                            }
                        }
                    }
 
                    _state[s_bitSetQueried] = true;
                }
                if (!_state[s_bitSetQueried])
                {
                    if (_receiverType == null)
                    {
                        if (_propInfo == null)
                        {
                            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty;
                            _propInfo = _componentClass.GetProperty(Name, bindingFlags, binder: null, PropertyType, Type.EmptyTypes, Array.Empty<ParameterModifier>());
                        }
                        if (_propInfo != null)
                        {
                            _setMethod = _propInfo.GetSetMethod(nonPublic: true);
                        }
                    }
                    else
                    {
                        _setMethod = FindMethod(_componentClass, "Set" + Name,
                                               new Type[] { _receiverType, _type }, typeof(void));
                    }
 
                    _state[s_bitSetQueried] = true;
                }
                return _setMethod;
            }
        }
 
        /// <summary>
        /// Accessor for the ShouldSerialize method.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        private MethodInfo? ShouldSerializeMethodValue
        {
            get
            {
                if (!_state[s_bitShouldSerializeQueried])
                {
                    Type[] args;
 
                    if (_receiverType == null)
                    {
                        args = Type.EmptyTypes;
                    }
                    else
                    {
                        args = new Type[] { _receiverType };
                    }
 
                    _shouldSerializeMethod = FindMethod(_componentClass, "ShouldSerialize" + Name, args, typeof(bool), publicOnly: false);
                    _state[s_bitShouldSerializeQueried] = true;
                }
                return _shouldSerializeMethod;
            }
        }
 
        /// <summary>
        /// Allows interested objects to be notified when this property changes.
        /// </summary>
        public override void AddValueChanged(object component, EventHandler handler)
        {
            ArgumentNullException.ThrowIfNull(component);
            ArgumentNullException.ThrowIfNull(handler);
 
            // If there's an event called <propertyname>Changed, hook the caller's handler directly up to that on the component
            EventDescriptor changedEvent = ChangedEventValue;
            if (changedEvent != null && changedEvent.EventType.IsInstanceOfType(handler))
            {
                changedEvent.AddEventHandler(component, handler);
            }
 
            // Otherwise let the base class add the handler to its ValueChanged event for this component
            else
            {
                // Special case: If this will be the FIRST handler added for this component, and the component implements
                // INotifyPropertyChanged, the property descriptor must START listening to the generic PropertyChanged event
                if (GetValueChangedHandler(component) == null)
                {
                    EventDescriptor iPropChangedEvent = IPropChangedEventValue;
                    iPropChangedEvent?.AddEventHandler(component, new PropertyChangedEventHandler(OnINotifyPropertyChanged));
                }
 
                base.AddValueChanged(component, handler);
            }
        }
 
        internal bool ExtenderCanResetValue(IExtenderProvider provider, object component)
        {
            if (DefaultValue != s_noValue)
            {
                return !Equals(ExtenderGetValue(provider, component), _defaultValue);
            }
 
            MethodInfo? reset = ResetMethodValue;
            if (reset != null)
            {
                MethodInfo? shouldSerialize = ShouldSerializeMethodValue;
                if (shouldSerialize != null)
                {
                    try
                    {
                        IExtenderProvider? prov = (IExtenderProvider?)GetInvocationTarget(_componentClass, provider);
                        return (bool)shouldSerialize.Invoke(prov, new object[] { component })!;
                    }
                    catch { }
                }
                return true;
            }
 
            return false;
        }
 
        internal Type? ExtenderGetReceiverType() => _receiverType;
 
        internal Type ExtenderGetType() => PropertyType;
 
        internal object? ExtenderGetValue(IExtenderProvider? provider, object? component)
        {
            if (provider != null)
            {
                IExtenderProvider? prov = (IExtenderProvider?)GetInvocationTarget(_componentClass, provider);
                return GetMethodValue.Invoke(prov, new object?[] { component });
            }
            return null;
        }
 
        internal void ExtenderResetValue(IExtenderProvider provider, object component, PropertyDescriptor notifyDesc)
        {
            if (DefaultValue != s_noValue)
            {
                ExtenderSetValue(provider, component, DefaultValue, notifyDesc);
            }
            else if (AmbientValue != s_noValue)
            {
                ExtenderSetValue(provider, component, AmbientValue, notifyDesc);
            }
            else if (ResetMethodValue != null)
            {
                ISite? site = GetSite(component);
                IComponentChangeService? changeService = null;
                object? oldValue = null;
                object? newValue;
 
                // Announce that we are about to change this component
                if (site != null)
                {
                    changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                }
 
                // Make sure that it is ok to send the onchange events
                if (changeService != null)
                {
                    oldValue = ExtenderGetValue(provider, component);
                    try
                    {
                        changeService.OnComponentChanging(component, notifyDesc);
                    }
                    catch (CheckoutException coEx)
                    {
                        if (coEx == CheckoutException.Canceled)
                        {
                            return;
                        }
                        throw;
                    }
                }
 
                IExtenderProvider? prov = (IExtenderProvider?)GetInvocationTarget(_componentClass, provider);
                if (ResetMethodValue != null)
                {
                    ResetMethodValue.Invoke(prov, new object[] { component });
 
                    // Now notify the change service that the change was successful.
                    if (changeService != null)
                    {
                        newValue = ExtenderGetValue(prov, component);
                        changeService.OnComponentChanged(component, notifyDesc, oldValue, newValue);
                    }
                }
            }
        }
 
        internal void ExtenderSetValue(IExtenderProvider? provider, object? component, object? value, PropertyDescriptor notifyDesc)
        {
            if (provider != null)
            {
                ISite? site = GetSite(component);
                IComponentChangeService? changeService = null;
                object? oldValue = null;
 
                // Announce that we are about to change this component
                if (site != null)
                {
                    changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                }
 
                // Make sure that it is ok to send the onchange events
                if (changeService != null)
                {
                    oldValue = ExtenderGetValue(provider, component);
                    try
                    {
                        changeService.OnComponentChanging(component!, notifyDesc);
                    }
                    catch (CheckoutException coEx)
                    {
                        if (coEx == CheckoutException.Canceled)
                        {
                            return;
                        }
                        throw;
                    }
                }
 
                IExtenderProvider? prov = (IExtenderProvider?)GetInvocationTarget(_componentClass, provider);
 
                if (SetMethodValue != null)
                {
                    SetMethodValue.Invoke(prov, new object?[] { component, value });
 
                    // Now notify the change service that the change was successful.
                    changeService?.OnComponentChanged(component!, notifyDesc, oldValue, value);
                }
            }
        }
 
        internal bool ExtenderShouldSerializeValue(IExtenderProvider provider, object component)
        {
            IExtenderProvider? prov = (IExtenderProvider?)GetInvocationTarget(_componentClass, provider);
 
            if (IsReadOnly)
            {
                if (ShouldSerializeMethodValue != null)
                {
                    try
                    {
                        return (bool)ShouldSerializeMethodValue.Invoke(prov, new object[] { component })!;
                    }
                    catch { }
                }
                return AttributesContainsDesignerVisibilityContent();
            }
            else if (DefaultValue == s_noValue)
            {
                if (ShouldSerializeMethodValue != null)
                {
                    try
                    {
                        return (bool)ShouldSerializeMethodValue.Invoke(prov, new object[] { component })!;
                    }
                    catch { }
                }
                return true;
            }
            return !Equals(DefaultValue, ExtenderGetValue(prov, component));
        }
 
        [DynamicDependency(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields, typeof(DesignerSerializationVisibilityAttribute))]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The DynamicDependency ensures the correct members are preserved.")]
        private bool AttributesContainsDesignerVisibilityContent() => Attributes.Contains(DesignerSerializationVisibilityAttribute.Content);
 
        /// <summary>
        /// Indicates whether reset will change the value of the component. If there
        /// is a DefaultValueAttribute, then this will return true if getValue returns
        /// something different than the default value. If there is a reset method and
        /// a ShouldSerialize method, this will return what ShouldSerialize returns.
        /// If there is just a reset method, this always returns true. If none of these
        /// cases apply, this returns false.
        /// </summary>
        public override bool CanResetValue(object component)
        {
            if (IsExtender || IsReadOnly)
            {
                return false;
            }
 
            if (DefaultValue != s_noValue)
            {
                return !Equals(GetValue(component), DefaultValue);
            }
 
            if (ResetMethodValue != null)
            {
                if (ShouldSerializeMethodValue != null)
                {
                    component = GetInvocationTarget(_componentClass, component)!;
                    try
                    {
                        return (bool)ShouldSerializeMethodValue.Invoke(component, null)!;
                    }
                    catch { }
                }
                return true;
            }
 
            if (AmbientValue != s_noValue)
            {
                return ShouldSerializeValue(component);
            }
 
            return false;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
            Justification = "ReflectPropertyDescriptor ctors are all marked as RequiresUnreferencedCode because PropertyType can't be annotated as 'All'.")]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2080:UnrecognizedReflectionPattern",
            Justification = ComponentClassIsProtected)]
        protected override void FillAttributes(IList attributes)
        {
            Debug.Assert(_componentClass != null, "Must have a component class for FillAttributes");
 
            //
            // 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 four categories of attributes we add must be
            // added as follows:
            //
            // 1. Attributes of the property type. These are the lowest level and should be
            //     overwritten by any newer attributes.
            //
            // 2. Attributes obtained from any SpecificTypeAttribute. These supersede attributes
            //     for the property type.
            //
            // 3. Attributes of the property itself, from base class to most derived. This way
            //     derived class attributes replace base class attributes.
            //
            // 4. 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.
            //
 
 
            // We need to include attributes from the type of the property.
            foreach (Attribute typeAttr in TypeDescriptor.GetAttributes(PropertyType))
            {
                attributes.Add(typeAttr);
            }
 
            // NOTE : Must look at method OR property, to handle the case of Extender properties...
            //
            // Note : Because we are using BindingFlags.DeclaredOnly it is more effcient to re-acquire
            //      : the property info, rather than use the one we have cached. The one we have cached
            //      : may ave come from a base class, meaning we will request custom metadata for this
            //      : class twice.
 
            Type? currentReflectType = _componentClass;
            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 != null && currentReflectType != typeof(object))
            {
                depth++;
                currentReflectType = currentReflectType.BaseType;
            }
 
            // Now build up an array in reverse order
            if (depth > 0)
            {
                currentReflectType = _componentClass;
                Attribute[][] attributeStack = new Attribute[depth][];
 
                while (currentReflectType != null && currentReflectType != typeof(object))
                {
                    MemberInfo? memberInfo;
 
                    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly;
                    // Fill in our member info so we can get at the custom attributes.
                    if (IsExtender)
                    {
                        //receiverType is used to avoid ambitiousness when there are overloads for the get method.
                        memberInfo = currentReflectType.GetMethod("Get" + Name, bindingFlags, binder: null, new Type[] { _receiverType! }, modifiers: null);
                    }
                    else
                    {
                        memberInfo = currentReflectType.GetProperty(Name, bindingFlags, binder: null, PropertyType, Type.EmptyTypes, Array.Empty<ParameterModifier>());
                    }
 
                    // Get custom attributes for the member info.
                    if (memberInfo != null)
                    {
                        attributeStack[--depth] = ReflectTypeDescriptionProvider.ReflectGetAttributes(memberInfo);
                    }
 
                    // Ready for the next loop iteration.
                    currentReflectType = currentReflectType.BaseType;
                }
 
                // Look in the attribute stack for AttributeProviders
                foreach (Attribute[] attributeArray in attributeStack)
                {
                    if (attributeArray != null)
                    {
                        foreach (Attribute attr in attributeArray)
                        {
                            if (attr is AttributeProviderAttribute sta)
                            {
                                Type? specificType = Type.GetType(sta.TypeName!);
 
                                if (specificType != null)
                                {
                                    Attribute[]? stAttrs = null;
 
                                    if (!string.IsNullOrEmpty(sta.PropertyName))
                                    {
                                        MemberInfo[] milist = specificType.GetMember(sta.PropertyName);
                                        if (milist.Length > 0 && milist[0] != null)
                                        {
                                            stAttrs = ReflectTypeDescriptionProvider.ReflectGetAttributes(milist[0]);
                                        }
                                    }
                                    else
                                    {
                                        stAttrs = ReflectTypeDescriptionProvider.ReflectGetAttributes(specificType);
                                    }
                                    if (stAttrs != null)
                                    {
                                        foreach (Attribute stAttr in stAttrs)
                                        {
                                            attributes.Add(stAttr);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
 
                // 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);
                        }
                    }
                }
            }
 
            // Include the base attributes. These override all attributes on the actual
            // property, so we want to add them last.
            base.FillAttributes(attributes);
 
            // Finally, override any form of ReadOnlyAttribute.
            if (SetMethodValue == null)
            {
                attributes.Add(ReadOnlyAttribute.Yes);
            }
        }
 
        /// <summary>
        /// Retrieves the current value of the property on component,
        /// invoking the getXXX method. An exception in the getXXX
        /// method will pass through.
        /// </summary>
        public override object? GetValue(object? component)
        {
            Debug.WriteLine($"[{Name}]: GetValue({component?.GetType().Name ?? "(null)"})");
 
            if (IsExtender)
            {
                Debug.WriteLine($"[{Name}]:   ---> returning: null");
                return null;
            }
 
            Debug.Assert(component != null, "GetValue must be given a component");
 
            if (component != null)
            {
                component = GetInvocationTarget(_componentClass, component)!;
 
                try
                {
                    return GetMethodValue.Invoke(component, null);
                }
                catch (Exception t)
                {
                    string? name = null;
                    IComponent? comp = component as IComponent;
                    ISite? site = comp?.Site;
                    if (site?.Name != null)
                    {
                        name = site.Name;
                    }
 
                    name ??= component.GetType().FullName;
 
                    if (t is TargetInvocationException)
                    {
                        t = t.InnerException!;
                    }
 
                    string message = t.Message ?? t.GetType().Name;
 
                    throw new TargetInvocationException(SR.Format(SR.ErrorPropertyAccessorException, Name, name, message), t);
                }
            }
            Debug.WriteLine("[" + Name + "]:   ---> returning: null");
            return null;
        }
 
        /// <summary>
        /// Handles INotifyPropertyChanged.PropertyChange events from components.
        /// If event pertains to this property, issue a ValueChanged event.
        /// </summary>
        internal void OnINotifyPropertyChanged(object? component, PropertyChangedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.PropertyName) ||
                string.Compare(e.PropertyName, Name, true, CultureInfo.InvariantCulture) == 0)
            {
                OnValueChanged(component, e);
            }
        }
 
        /// <summary>
        /// This should be called by your property descriptor implementation
        /// when the property value has changed.
        /// </summary>
        protected override void OnValueChanged(object? component, EventArgs e)
        {
            if (_state[s_bitChangedQueried] && _realChangedEvent == null)
            {
                base.OnValueChanged(component, e);
            }
        }
 
        /// <summary>
        /// Allows interested objects to be notified when this property changes.
        /// </summary>
        public override void RemoveValueChanged(object? component, EventHandler handler)
        {
            ArgumentNullException.ThrowIfNull(component);
            ArgumentNullException.ThrowIfNull(handler);
 
            // If there's an event called <propertyname>Changed, we hooked the caller's
            // handler directly up to that on the component, so remove it now.
            EventDescriptor changedEvent = ChangedEventValue;
            if (changedEvent != null && changedEvent.EventType.IsInstanceOfType(handler))
            {
                changedEvent.RemoveEventHandler(component, handler);
            }
 
            // Otherwise the base class added the handler to its ValueChanged
            // event for this component, so let the base class remove it now.
            else
            {
                base.RemoveValueChanged(component, handler);
 
                // Special case: If that was the LAST handler removed for this component, and the component implements
                // INotifyPropertyChanged, the property descriptor must STOP listening to the generic PropertyChanged event
                if (GetValueChangedHandler(component) == null)
                {
                    EventDescriptor iPropChangedEvent = IPropChangedEventValue;
                    iPropChangedEvent?.RemoveEventHandler(component, new PropertyChangedEventHandler(OnINotifyPropertyChanged));
                }
            }
        }
 
        /// <summary>
        /// Will reset the default value for this property on the component. If
        /// there was a default value passed in as a DefaultValueAttribute, that
        /// value will be set as the value of the property on the component. If
        /// there was no default value passed in, a ResetXXX method will be looked
        /// for. If one is found, it will be invoked. If one is not found, this
        /// is a nop.
        /// </summary>
        public override void ResetValue(object component)
        {
            object? invokee = GetInvocationTarget(_componentClass, component);
 
            if (DefaultValue != s_noValue)
            {
                SetValue(component, DefaultValue);
            }
            else if (AmbientValue != s_noValue)
            {
                SetValue(component, AmbientValue);
            }
            else if (ResetMethodValue != null)
            {
                ISite? site = GetSite(component);
                IComponentChangeService? changeService = null;
                object? oldValue = null;
                object? newValue;
 
                // Announce that we are about to change this component
                if (site != null)
                {
                    changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                }
 
                // Make sure that it is ok to send the onchange events
                if (changeService != null)
                {
                    // invokee might be a type from mscorlib or system, GetMethodValue might return a NonPublic method
                    oldValue = GetMethodValue.Invoke(invokee, null);
                    try
                    {
                        changeService.OnComponentChanging(component, this);
                    }
                    catch (CheckoutException coEx)
                    {
                        if (coEx == CheckoutException.Canceled)
                        {
                            return;
                        }
                        throw;
                    }
                }
 
                if (ResetMethodValue != null)
                {
                    ResetMethodValue.Invoke(invokee, null);
 
                    // Now notify the change service that the change was successful.
                    if (changeService != null)
                    {
                        newValue = GetMethodValue.Invoke(invokee, null);
                        changeService.OnComponentChanged(component, this, oldValue, newValue);
                    }
                }
            }
        }
 
        /// <summary>
        /// This will set value to be the new value of this property on the
        /// component by invoking the setXXX method on the component. If the
        /// value specified is invalid, the component should throw an exception
        /// which will be passed up. The component designer should design the
        /// property so that getXXX following a setXXX should return the value
        /// passed in if no exception was thrown in the setXXX call.
        /// </summary>
        public override void SetValue(object? component, object? value)
        {
            Debug.WriteLine($"[{Name}]: SetValue({component?.GetType().Name ?? "(null)"}, {value?.GetType().Name ?? "(null)"})");
 
            if (component != null)
            {
                ISite? site = GetSite(component);
                object? oldValue = null;
 
                object? invokee = GetInvocationTarget(_componentClass, component);
 
                if (!IsReadOnly)
                {
                    IComponentChangeService? changeService = null;
 
                    // Announce that we are about to change this component
                    if (site != null)
                    {
                        changeService = (IComponentChangeService?)site.GetService(typeof(IComponentChangeService));
                    }
 
                    // Make sure that it is ok to send the onchange events
                    if (changeService != null)
                    {
                        oldValue = GetMethodValue.Invoke(invokee, null);
                        try
                        {
                            changeService.OnComponentChanging(component, this);
                        }
                        catch (CheckoutException coEx)
                        {
                            if (coEx == CheckoutException.Canceled)
                            {
                                return;
                            }
                            throw;
                        }
                    }
 
                    try
                    {
                        try
                        {
                            SetMethodValue!.Invoke(invokee, new object?[] { value });
                            OnValueChanged(invokee, EventArgs.Empty);
                        }
                        catch (Exception t)
                        {
                            // Give ourselves a chance to unwind properly before rethrowing the exception.
                            //
                            value = oldValue;
 
                            // If there was a problem setting the controls property then we get:
                            // ArgumentException (from properties set method)
                            // ==> Becomes inner exception of TargetInvocationException
                            // ==> caught here
 
                            if (t is TargetInvocationException && t.InnerException != null)
                            {
                                // Propagate the original exception up
                                throw t.InnerException;
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }
                    finally
                    {
                        // Now notify the change service that the change was successful.
                        changeService?.OnComponentChanged(component, this, oldValue, value);
                    }
                }
            }
        }
 
        /// <summary>
        /// Indicates whether the value of this property needs to be persisted. In
        /// other words, it indicates whether the state of the property is distinct
        /// from when the component is first instantiated. If there is a default
        /// value specified in this ReflectPropertyDescriptor, it will be compared against the
        /// property's current value to determine this. If there isn't, the
        /// ShouldSerializeXXX method is looked for and invoked if found. If both
        /// these routes fail, true will be returned.
        ///
        /// If this returns false, a tool should not persist this property's value.
        /// </summary>
        public override bool ShouldSerializeValue(object component)
        {
            component = GetInvocationTarget(_componentClass, component)!;
 
            if (IsReadOnly)
            {
                if (ShouldSerializeMethodValue != null)
                {
                    try
                    {
                        return (bool)ShouldSerializeMethodValue.Invoke(component, null)!;
                    }
                    catch { }
                }
                return AttributesContainsDesignerVisibilityContent();
            }
            else if (DefaultValue == s_noValue)
            {
                if (ShouldSerializeMethodValue != null)
                {
                    try
                    {
                        return (bool)ShouldSerializeMethodValue.Invoke(component, null)!;
                    }
                    catch { }
                }
                return true;
            }
            return !Equals(DefaultValue, GetValue(component));
        }
 
        /// <summary>
        /// Indicates whether value change notifications for this property may originate from outside the property
        /// descriptor, such as from the component itself (value=true), or whether notifications will only originate
        /// from direct calls made to PropertyDescriptor.SetValue (value=false). For example, the component may
        /// implement the INotifyPropertyChanged interface, or may have an explicit '{name}Changed' event for this property.
        /// </summary>
        public override bool SupportsChangeEvents => IPropChangedEventValue != null || ChangedEventValue != null;
    }
}