views:

35

answers:

1

This question will probably take a while to explain, and I'll need to provide background...

This is just something I'm playing about with and isn't for production, but at the moment I have some code which looks like this:

var myDataModel = new DataModel();

myDataModel.PropertyChanged += myDataModel_PropertyChanged;

myDataModel.ChangeProperty(t => t.TestValue, 2);

So, rather than using myDataModel.TestValue = 2 directly, I'm using a ChangeProperty extension method so that I can handle all of the change events and do anything I want to in one place. My extension method looks like this (and yes, I know it's hacky):

public static class NotifyPropertyChangedExtensions
{
    public static void ChangeProperty<T, U>(
                                      this T instance, 
                                      Expression<Func<T, U>> propertyToChange, 
                                      U newValue)
    {
        var member = propertyToChange.Body as MemberExpression;

        if (member != null)
        {
            if (!propertyToChange.Compile().Invoke(instance).Equals(newValue))
            {
                var setProperty = instance.GetType().GetProperty(
                                      member.Member.Name, 
                                        BindingFlags.SetProperty | 
                                        BindingFlags.Public | 
                                        BindingFlags.Instance);

                if (setProperty != null)
                {
                    // actually set the property
                    setProperty.SetValue(instance, newValue, null);

                    // raise the property changed event
                    if (typeof(INotifyPropertyChanged).IsAssignableFrom(
                                                                 typeof(T)))
                    {
                        var delegatesToCall = 
                             instance.GetType().GetField("PropertyChanged", 
                                       BindingFlags.Instance | 
                                       BindingFlags.NonPublic)
                                  .GetValue(instance) as MulticastDelegate;


                        if (delegatesToCall != null)
                        {
                            var eventArgs = new PropertyChangedEventArgs(
                                                           setProperty.Name);
                            foreach (var @delegate in 
                                         delegatesToCall.GetInvocationList())
                            {
                                @delegate.Method.Invoke(
                                       @delegate.Target, 
                                       new object[] { instance, eventArgs });
                            }
                        }

                    }
                }
            }
        }
        else
        {
            throw new ArgumentException(
                    string.Format(
                           "Cannot determine the property to change {0}", 
                           propertyToChange));
        }
    }
}

With this architecture, my data model is quite clean:

public class DataModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public int TestValue { get; set; }
} 

That is, I can use auto-properties, and don't need to worry about raising events etc.

Now, what I actually want to do is something closer to this:

var dataModel = new DataModel();

myDataModel.PropertyChanged += myDataModel_PropertyChanged;

myDataModel.TestValue.Set(2); // this is what I want...

So, I'm thinking I'll basically need an extension method - but I can only see how to send the property itself (the TestValue in this case), and the new value. So then I wondered if it's possible, given a property, to find out the instance of the class it belongs to?

+1  A: 
  1. Don't do this. It breaks encapsulation.

  2. No. In myDataModel.TestValue.Set(2); the extension method will always be called on the value returned by the property. There is no way to get the class, instance or property that returned the value.

  3. You could do something like this:

    var t = new DataModel();
    ((Expression<Func<int>>)(() => t.Foo)).Set(100);
    

    with

    static class Extensions
    {
        public static void Set<T>(this Expression<Func<T>> expression, T value)
        { ... }
    }
    

    but this is ugly, almost unreadable, unclear, inefficient, and error prone.

  4. You're looking for Aspect Oriented Programming (AOP).

    Have a look at PostSharp or LinFu.

  5. There is no really clean solution to implementing INotifyPropertyChanged yet. If typing all the property setters is too much work or too error prone, I'd generate them with a T4 template.

dtb