0

I'm trying to fire PropertyChanged via reflection and I'm having some issues.

The following code works:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string m_test = string.Empty;

    public string Test
    {
        get
        {
            return m_test;
        }
        set
        {
            m_test = value;
            Notify();
        }
    }

    protected void Notify([CallerMemberName] string name = null)
    {
        var handler = PropertyChanged;
        if (handler != null && name != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

public class ViewModel : ViewModelBase
{
    private string m_test2 = string.Empty;
    public string Test2
    {
        get
        {
            return m_test2;
        }
        set
        {
            m_test2 = value;
            Notify();
        }
    }
}

However, I have added an extension method to INotifyPropertyChanged that would raise it via reflection.

Instead of Notify() I could call this.Notify() instead, which is defined like so:

    /// <summary>
    /// Invoke sender's PropertyChanged event via Reflection
    /// </summary>
    /// <param name="sender">sender of the event</param>
    /// <param name="prop">The Property name that has changed</param>
    public static void NotifyPropertyChanged(this INotifyPropertyChanged sender, [CallerMemberName] string prop = null)
    {
        var senderType = sender.GetType();
        var methodInfo = senderType.GetField("PropertyChanged", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        if (methodInfo != null)
        {
            var delegates = (MulticastDelegate)methodInfo.GetValue(sender);
            if (delegates != null)
            {
                foreach (var handler in delegates.GetInvocationList())
                {
                    handler.Method.Invoke(handler.Target, new object[] { sender, new PropertyChangedEventArgs(prop) });
                }
            }
        }
    }

Unfortunately, GetField returns null for ViewModel in the example above.

Is there a way to reflect the parent's event?

I'm thinking of iterating over the base classes, but I'm hoping for a better/easier way.

Arthur
  • 75
  • 1
  • 8
  • Maybe this answer will help you: http://stackoverflow.com/a/586156/1466456 Note the Raise extension method. – Jaanus Varus Jun 06 '15 at 21:29
  • You're doing it wrong. This is not an event that makes sense to raise manually from outside your class, unless your class has bugs that cause the event not to be raised when it should be. In that case, if it's your class, just fix those bugs. –  Jun 06 '15 at 21:34
  • 1
    @hvd I'm not raising outside of the class, I just didn't want to write a `Notify` method on every class that implements `INotifyPropertyChanged`. – Arthur Jun 06 '15 at 21:43
  • You're defining a ViewModelBase class so any of your VIewModels that inherit from it will already have that method and you won't need to write it again. If you have classes that you don't want to inherit from ViewModelBase but also want to implement INotifyPropertyChanged then create a NotifyPropertyBase class. Put the Notify() method there and then have ViewModelBase and whatever else you have inherit from NotifyPropertyBase. – Wearwolf Jun 06 '15 at 22:13
  • Even putting this in a T4 template that generates the same `protected void Notify(string)` implementation for each relevant base class (using partial classes) would be better than this approach with reflection. It's not reliable anyway: your static method's API promises it supports all implementations of `INotifyPropertyChanged`, but it won't actually work for all those implementations, just your own. And even then, that needs a *big* comment in your base classes that the `PropertyChanged` cannot be refactored due to unverifiable uses through reflection. –  Jun 07 '15 at 00:02
  • I understand. I'm modifying the base class instead. – Arthur Jun 07 '15 at 14:20

2 Answers2

2

I think you are going about this the wrong way.

You are breaking the framework's encapsulation of events only being raised by the declaring (owning) instance. By using a publicly available extension method that anyone can call, you are opening a can of worms.

A better solution would be to use a protected method in a base class, as was done in your "the following code works" example.

But if you are really determined on doing it, it can obviously be done.

The extension method below can be used if you want to break the normal protections and encapsulation around events.

public static class ProperyChangedEventExtensions
{
    public static void RaisePropertyChanged<T, P>(this T sender, Expression<Func<T, P>> propertyExpression) where T : INotifyPropertyChanged
    {
        Raise(typeof(T), sender, (propertyExpression.Body as MemberExpression).Member.Name);
    }

    public static void RaisePropertyChanged(this INotifyPropertyChanged sender, [CallerMemberName] string prop = null)
    {
        Raise(sender.GetType(), sender, prop);
    }

    private static void Raise(Type targetType, INotifyPropertyChanged sender, string propName)
    {
        var evtPropType = targetType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
        var evtPropVal = (PropertyChangedEventHandler)evtPropType.GetValue(sender);
        evtPropVal(sender, new PropertyChangedEventArgs(propName));
    }
}

Usage example (including hopefully some cases that will make you reconsider this approach):

class MyViewModel : INotifyPropertyChanged
{
    // The compiler will complain about this:
    // Warning 3 The event 'MyNamespace.MyViewModel.PropertyChanged' is never used
    public event PropertyChangedEventHandler PropertyChanged;

    private string _myProp;
    public string MyProp
    {
        get { return _myProp; }
        set
        {
            _myProp = value;
            this.RaisePropertyChanged();
        }
    }

    public readonly int MyImmutableValue;
}

// ...

var vm = new MyViewModel();
vm.PropertyChanged += (sender, evt) => Console.WriteLine("Prop changed {0}", evt.PropertyName);
vm.MyProp = "abc";
vm.RaisePropertyChanged(x => x.MyProp);
vm.RaisePropertyChanged("MyProp");
vm.RaisePropertyChanged("Un Oh. Do we have a problem");
vm.RaisePropertyChanged(x => x.MyImmutableValue);
vm.RaisePropertyChanged("MyImmutableValue");
Alex
  • 13,024
  • 33
  • 62
0
/// <summary>
/// Invoke sender's PropertyChanged event via Reflection???
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="prop">The Property name that has changed</param>
public static void NotifyPropertyChanged(this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, [CallerMemberName] string prop = null)
{
    handler(sender, new PropertyChangedEventArgs(prop));
}

use it like this?

class MyViewModel : INotifyPropertyChanged
{
    // The compiler will complain about this:
    // Warning 3 The event 'MyNamespace.MyViewModel.PropertyChanged' is never used
    public event PropertyChangedEventHandler PropertyChanged;

    private string _myProp;
    public string MyProp
    {
        get { return _myProp; }
        set
        {
            _myProp = value;
            this.Notify(this.PropertyChanged);
        }
    }
}
Tony THONG
  • 21
  • 2