This project is read-only.

For ReactiveObjects, make sure ALL objects in your expressions implement INotifyPropertyChanged

Jul 7, 2010 at 4:35 PM

Make sure that all objects in your "OnChanged()" expression implement INotifyPropertyChanged.

While it may seem obvious, it threw me for a loop. When using a ReactiveObject that registers dependencies, make sure that the objects in the "OnChanged(...)" expression all implement INotifyPropertyChanged. If not, your expression tree won't call the callback that you define in the "Call(...)" portion of the dependency.

In my case, my expression included a set of objects that all implemented INPC, but one of the objects in the expression was being accessed via an interface. The interface did not implement INotifyPropertyChanged, even though the concrete class did. As a result, the code in ContinuousLinq.ExpressionPropertyAnalyzer.ApplyTypeFilter() removed that node from the subscription. The net result was that my callback wasn't called.

It might be nice to have the option to see these issues rendered in the Debug window, or even issue a Debug ASSERT so that we can easily catch these scenarios. This one took me a while to track down by stepping through the CLINQ codebase.

 

Example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ContinuousLinq.Reactive;
using System.ComponentModel;
using System.Diagnostics;

namespace ClinqTest
{
    class Program
    {

        static void Main(string[] args)
        {
            var a = new A();

            Console.WriteLine();
            Console.WriteLine("Changing value - INTERFACE based expression.");
            
            // This line should result in a Console.Writeline(...), but won't,
            // unless you make IInterfaceB implement INotifyPropertyChanged.
            a.PropBInterface.PropC.PropOfC = "New Value (via Interface expression)";

            Console.WriteLine("Changed value - INTERFACE based expression.");

            Console.WriteLine();
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }


        interface IInterfaceB
            // FIX: Uncomment the following line to fix it.
            // : INotifyPropertyChanged    
        {
            C PropC { get; set; }
        }

        
        class A : ReactiveObject, INotifyPropertyChanged
        {
            public new event PropertyChangedEventHandler PropertyChanged;

            static A()
            {
                var dependsOn = Register<A>();
                dependsOn
                    .Call(me => 
                        Console.WriteLine(
                            "C.PropOfC Changed (Interface). Value = '{0}'.",
                            new string[] { me.PropBInterface.PropC.PropOfC })
                        )
                    .OnChanged(me => me.PropBInterface.PropC.PropOfC);

            }

            public A()
                : base()
            {
                PropBInterface = new B();
            }

            private B _B;
            public IInterfaceB PropBInterface
            {
                get { return _B; }
                set { _B = (B)value; OnPropertyChanged("PropBInterface"); }
            }

            internal void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged == null) return;
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


        class B : IInterfaceB, INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public B()
            {
                PropC = new C();
            }

            private C _C;
            public C PropC
            {
                get { return _C; }
                set { _C = value; OnPropertyChanged("PropC"); }
            }

            internal void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged == null) return;
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


        class C : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            private string _PropOfC;
            public string PropOfC
            {
                get { return _PropOfC; }
                set { _PropOfC = value; OnPropertyChanged("PropOfC"); }
            }

            internal void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged == null) return;
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


    }
}

Jul 7, 2010 at 5:47 PM

Here is a sample tweak that helped me check if any of my types were missing INotifyPropertyChanged.

Note:
This is just an idea, and has not been fully tested or reviewed for correctness. It worked for me during my dev phase.

File: Expressions\ExpressionPropertyAnalyzer.cs

        private static bool DoesTypeImplementINotifyPropertyChanged(Type type)
        {
            var ret = typeof(INotifyPropertyChanged).IsAssignableFrom(type);

            if (!ret)
                System.Diagnostics.Debug.WriteLine("CLINQ: Type does not implement INotifyPropertyChanged and will be removed from the expression evaluation. Type = {0}, Assembly={1}.", type.FullName, type.Assembly.FullName);

            return ret;
        }