通过反射触发 INotifyPropertyChanged.PropertyChanged

Fire INotifyPropertyChanged.PropertyChanged via Reflection

我正在尝试通过反射触发 PropertyChanged,但我遇到了一些问题。

以下代码有效:

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();
        }
    }
}

但是,我已经向 INotifyPropertyChanged 添加了一个扩展方法,可以通过反射引发它。

我可以调用 this.Notify() 而不是 Notify(),它的定义如下:

    /// <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) });
                }
            }
        }
    }

不幸的是,GetField returns 上例中的 ViewModel 为空。

有没有办法反映 parent 的事件?

我正在考虑遍历基础 类,但我希望采用 better/easier 方式。

我认为你的做法是错误的。

您正在破坏框架对仅由声明(拥有)实例引发的事件的封装。通过使用任何人都可以调用的公开可用的扩展方法,您正在打开一个蠕虫罐头。

更好的解决方案是在基 class 中使用受保护的方法,就像您在 "the following code works" 示例中所做的那样。

但是如果你真的下定决心去做,那显然是可以做到的。

如果你想打破正常的事件保护和封装,可以使用下面的扩展方法。

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));
    }
}

使用示例(希望包括一些会让您重新考虑这种方法的案例):

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");
/// <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));
}

这样用?

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);
        }
    }
}