【问题标题】:Fire INotifyPropertyChanged.PropertyChanged via Reflection通过反射触发 INotifyPropertyChanged.PropertyChanged
【发布时间】:2015-06-06 21:22:50
【问题描述】:

我正在尝试通过反射触发 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 在上面的示例中为 ViewModel 返回 null。

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

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

【问题讨论】:

  • 也许这个答案会对你有所帮助:stackoverflow.com/a/586156/1466456 注意 Raise 扩展方法。
  • 你做错了。这不是从课堂外部手动引发的有意义的事件,除非你的课堂存在导致事件在应该引发的时候不引发的错误。在这种情况下,如果是你的班级,只需修复这些错误。
  • @hvd 我不是在课堂之外提出的,我只是不想在每个实现INotifyPropertyChanged 的类上编写Notify 方法。
  • 您正在定义一个 ViewModelBase 类,因此从它继承的任何 VIewModel 都将具有该方法,您无需再次编写它。如果您有不想从 ViewModelBase 继承但又想实现 INotifyPropertyChanged 的​​类,则创建一个 NotifyPropertyBase 类。将 Notify() 方法放在那里,然后拥有 ViewModelBase 以及您从 NotifyPropertyBase 继承的任何其他内容。
  • 即使将它放在一个 T4 模板中,为每个相关的基类(使用部分类)生成相同的 protected void Notify(string) 实现也会比这种反射方法更好。无论如何它都不可靠:您的静态方法的 API 承诺它支持 INotifyPropertyChanged 的所有实现,但它实际上并不适用于所有这些实现,只适用于您自己的实现。即便如此,也需要在您的基类中添加一个 big 注释,即 PropertyChanged 由于无法通过反射进行验证而无法重构。

标签: c# inheritance reflection inotifypropertychanged


【解决方案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");

【讨论】:

    【解决方案2】:
    /// <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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-05
      • 2021-03-23
      • 1970-01-01
      • 2018-03-26
      • 1970-01-01
      相关资源
      最近更新 更多