通过反射消防INotifyPropertyChanged.PropertyChanged

时间:2015-06-06 21:22:50

标签: c# inheritance reflection inotifypropertychanged

我正在尝试通过反射触发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添加了一个扩展方法,可以通过反射来提升它。

而不是Notify()我可以改为调用this.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在上面的示例中为ViewModel返回null。

有没有办法反映父母的事件?

我正在考虑迭代基类,但我希望有更好/更简单的方法。

2 个答案:

答案 0 :(得分:1)

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

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

更好的解决方案是在基类中使用受保护的方法,就像在“以下代码工作”示例中所做的那样。

但如果你真的决定这样做,显然可以做到。

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

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

答案 1 :(得分: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));
}

像这样使用它?

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