Why is this Animatable property being set again?

2019-06-07 15:31发布

问题:

Follow up to this question.

Apparently, for some reason after having explicitly set the Parent.Child property ( either inside the constructor or explicitly outside of the constructor ), when I set the Child.Trigger property of the Parent.Child object, the Parent.Child object is being set yet again. This can be observed by breaking on the _OnChildChanged method defined within the static constructor. On the second instance of it being called, you can see that e.OldValue is not null, and that it is the same as e.NewValue.

WHY is the Child property of Parent being set yet again when setting the Trigger property?

In compliance with the requisite Minimal, Complete and Verifiable Example:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Animation;

namespace MCVE {
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class Program {
        [STAThread]
        public static int Main( ) {
            Parent p = new Parent( );
            p.Child.Trigger = new object( );
            return 0;
        }
    }

    public abstract class Base : Animatable {
        public static readonly DependencyProperty TriggerProperty;
        static Base( ) =>
            TriggerProperty = DependencyProperty.Register(
                "Trigger", typeof( object ), typeof( Base) );
        public object Trigger {
            get => this.GetValue( TriggerProperty );
            set => this.SetValue( TriggerProperty, value );
        }
    }
    public class Parent : Base {
        public static readonly DependencyProperty ChildProperty;

        static Parent( ) {
            ChildProperty = DependencyProperty.Register(
                "Child", typeof( Child ), typeof( Parent ),
                new PropertyMetadata( null as Child, _OnChildChanged ) );

            void _OnChildChanged(
                DependencyObject sender,
                DependencyPropertyChangedEventArgs e ) =>
                Console.WriteLine( "Child Changed!" );
        }

        public Parent( ) : base( ) =>
            this.Child = new Child( );


        public Child Child {
            get => this.GetValue( ChildProperty ) as Child;
            set => this.SetValue( ChildProperty, value );
        }

        protected override Freezable CreateInstanceCore( ) => new Parent( );
    }
    public class Child : Base {
        public Child( ) : base( ) { }
        protected override Freezable CreateInstanceCore( ) => new Child( );
    }
}

To reproduce:

  1. Create WPF Project. Target .Net 4.7.2.
  2. Select App.xaml
  3. Under Properties, change Build Action to Page
  4. Paste code into App.xaml.cs. Overwrite EVERYTHING.

回答1:

I took a look on implementation of the Animatable class. It is inherited from freezable class which is inherited from DependencyObject class.

Inside freezable, it overwrote OnPropertyChanged event from the DependencyObject and invoke all handlers which is response to a changing dependency property of type Freezable.

That mean when any dependency value in Child class has changed, OnPropertyChanged event in Freezable class will be called. And the FireChanged() has called also. Inside FireChange() method, it will call GetChangeHandlersAndInvalidateSubProperties to check all changed from sub class, and then the _OnChildChanged will be invoked whenever any its dependency property has changed.

You can refer source code of Freezable class here


This behavior is also documented in Freezable Objects Overview, section Creating Your Own Freezable Class (emphasis mine):

A class that derives from Freezable gains the following features.

  • Special states: a read-only (frozen) and a writable state.

  • Thread safety: a frozen Freezable can be shared across threads.

  • Detailed change notification: Unlike other DependencyObjects, Freezable objects provide change notifications when sub-property values change.

  • Easy cloning: the Freezable class has already implemented several methods that produce deep clones.