How can I fix the DependencyPropertyDescriptor AddValueChanged Memory Leak on AttachedBehavior?

后端 未结 2 1749
独厮守ぢ
独厮守ぢ 2020-12-16 00:18

I know I need to call RemoveValueChanged, but I have not been able to find a reliable place to call this. I\'m learning that there probably isn\'t one.

I looks like

相关标签:
2条回答
  • 2020-12-16 00:43

    AddValueChanged of dependency property descriptor results in memory leak as you already know. So, as described here, you can create custom class PropertyChangeNotifier to listen to any dependency property changes.

    Complete implementation can be found here - PropertyDescriptor AddValueChanged Alternative.


    Quote from the link:

    This class takes advantage of the fact that bindings use weak references to manage associations so the class will not root the object who property changes it is watching. It also uses a WeakReference to maintain a reference to the object whose property it is watching without rooting that object. In this way, you can maintain a collection of these objects so that you can unhook the property change later without worrying about that collection rooting the object whose values you are watching.

    Also for sake of completeness of answer I am posting complete code here to avoid any rot issue in future.

    public sealed class PropertyChangeNotifier : DependencyObject, IDisposable
    {
        #region Member Variables
    
        private readonly WeakReference _propertySource;
    
        #endregion // Member Variables
    
        #region Constructor
        public PropertyChangeNotifier(DependencyObject propertySource, string path)
            : this(propertySource, new PropertyPath(path))
        {
        }
        public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property)
            : this(propertySource, new PropertyPath(property))
        {
        }
        public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property)
        {
            if (null == propertySource)
                throw new ArgumentNullException("propertySource");
            if (null == property)
                throw new ArgumentNullException("property");
            _propertySource = new WeakReference(propertySource);
            Binding binding = new Binding
            {
                Path = property, 
                Mode = BindingMode.OneWay, 
                Source = propertySource
            };
            BindingOperations.SetBinding(this, ValueProperty, binding);
        }
        #endregion // Constructor
    
        #region PropertySource
        public DependencyObject PropertySource
        {
            get
            {
                try
                {
                    // note, it is possible that accessing the target property
                    // will result in an exception so i’ve wrapped this check
                    // in a try catch
                    return _propertySource.IsAlive
                    ? _propertySource.Target as DependencyObject
                    : null;
                }
                catch
                {
                    return null;
                }
            }
        }
        #endregion // PropertySource
    
        #region Value
        /// <summary>
        /// Identifies the <see cref="Value"/> dependency property
        /// </summary>
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
        typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, OnPropertyChanged));
    
        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PropertyChangeNotifier notifier = (PropertyChangeNotifier)d;
            if (null != notifier.ValueChanged)
                notifier.ValueChanged(notifier, EventArgs.Empty);
        }
    
        /// <summary>
        /// Returns/sets the value of the property
        /// </summary>
        /// <seealso cref="ValueProperty"/>
        [Description("Returns/sets the value of the property")]
        [Category("Behavior")]
        [Bindable(true)]
        public object Value
        {
            get
            {
                return GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }
        #endregion //Value
    
        #region Events
        public event EventHandler ValueChanged;
        #endregion // Events
    
        #region IDisposable Members
    
        public void Dispose()
        {
            BindingOperations.ClearBinding(this, ValueProperty);
        }
    
        #endregion
    }
    
    0 讨论(0)
  • 2020-12-16 00:45

    A more lightweight solution for FrameworkElements and FrameworkContentElements is to subscribe to the Unloaded event and remove the handler. This requires a non-anonymous delegate (UpdateAdorner in that case) though:

    focusProp.AddValueChanged(m_TextBox, UpdateAdorner);
    m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);
    
    0 讨论(0)
提交回复
热议问题