Search code examples
c#genericsinotifypropertychangeddisambiguation

How can I disambiguate this call to a generic method?


Given the following simple console application which illustrates two ways of notifying on changed properties:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person(){ Name = "Me" };
            person.Age = 20;
            person.Weight = 80.5F;
                    person.RandomProperty = new RandomComplexObject();

            Console.ReadKey();
        }
    }


    public class Person : BaseObject
    {
        public string Name
        {
            get { return _name; }
            set { SetProperty(ref value, ref _name, false); }
        }

        public int Age
        {
            get { return _age; }
            set { SetProperty<int>(ref value, ref _age, true, "Age", "Weight"); }
        }

        public float Weight
        {
            get { return _weight; }
            set { SetProperty(ref value, ref _weight, true, () => Weight, () => Age); }
        }

        public RandomComplexObject RandomProperty
        {
            get { return _rco; }

            //*** the following line has the error:
            //-------------------------------------
            set { SetProperty(ref value, ref _rco, true, () => Name, () => Age, () => Weight); } 
        }

        private float _weight;
        private int _age;
        private string _name;
        private RandomComplexObject _rco;
    }

    public class BaseObject : INotifyPropertyChanged
    {

        protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                var body = propertyExpression.Body as MemberExpression;
                var expression = body.Expression as ConstantExpression;
                handler(expression.Value, new PropertyChangedEventArgs(body.Member.Name));
            }
        }

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

        protected bool SetProperty<T>(ref T newValue, ref T currentValue, bool notify, params string[] notifications)
        {
            if (EqualityComparer<T>.Default.Equals(newValue, currentValue))
                return false;

            currentValue = newValue;
            if (notify)
                foreach (var propertyName in notifications)
                    OnPropertyChanged(propertyName);

            return true;
        }

        protected bool SetProperty<T, TProperty>(ref T newValue, ref T currentValue, bool notify, params Expression<Func<TProperty>>[] notifications)
        {
            if (EqualityComparer<T>.Default.Equals(newValue, currentValue))
                return false;

            currentValue = newValue;
            if (notify)
                foreach (var notification in notifications)
                    OnPropertyChanged(notification);

            return true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

    }

    public class RandomComplexObject{}
}

on the line with the method call SetProperty(ref value, ref _rco, true, () => Name, () => Age, () => Weight); I am experiencing a compilation error:

Cannot convert lambda expression to type 'string' because it is not a delegate type

the error shown directly in the IDE is:

The type arguments for method 'bool ConsoleApplication1.BaseObject.SetProperty(ref T, ref T, bool, params Expression<Func<TProperty>>[])' cannot be inferred from the usage. Try specifying the type arguments explicitly.

How can I disambiguate this call to the SetProperty() method? Is there a syntactically cleaner way to write this?


Solution

  • The following variant at least compiles.

    public RandomComplexObject RandomProperty
    {
        get { return _rco; }
    
        set
        {
            SetProperty(
                ref value,
                ref _rco,
                true,
                () => Name,
                () => Age.ToString(),    //instead of () => Age
                () => Weight.ToString());//instead of () => Weight
        }
    }
    

    The error you're receving, I guess, was in the first place based on the fact that compiler couldn't infer the TProperty for

    protected bool SetProperty<T, TProperty>(
        ref T newValue, 
        ref T currentValue, 
        bool notify, 
        params Expression<Func<TProperty>>[] notifications)
    {
        //...
    }
    

    as it's expecting a variable number of arguments of type Expression<Func<TProperty>> and you passed there lambdas, returning string, int and float. Definitely compiler couldn't determine, which one of them was TProperty.

    In the setter of the Weight property:

    public float Weight
    {
        get { return _weight; }
        set
        {
            SetProperty(ref value, ref _weight, true, () => Weight, () => Age);
        }
    }
    

    having Weight of type float and Age of type int, compiler inferred TProperty was float, as there is an implicit conversion from int to float.