在设置属性时确定调用者 - 或者设置属性时保持静默

14

在给定标准视图模型的情况下,当属性发生更改时,有没有办法确定更改的发起者?换句话说,在以下视图模型中,我希望“PropertyChanged”事件的“sender”参数是实际调用Prop1 setter的对象:

public class ViewModel : INotifyPropertyChanged
{
    public double Prop1
    {
        get { return _prop1; }
        set
        {
            if (_prop1 == value)
                return;
            _prop1 = value;

            // here, can I determine the sender?
            RaisePropertyChanged(propertyName: "Prop1", sender: this);
        }
    }
    private double _prop1;

    // TODO implement INotifyPropertyChanged
}

另外,是否可以将 CallerMemberNameAttribute 应用于属性 setter 方法?


2
PropertyChanged事件的“sender”参数必须始终为“this”。否则,你将会破坏INotifyPropertyChanged的使用。请注意,PropertyChangedEventArgs仅包含属性名称,而不包括所属实例。该属性所属的实例必须是发送方。如果不传递“this”,尝试按名称访问属性的代码将引发异常。 - shf301
@shf301 我可以理解 sender 应该是拥有事件的实例... 但这并不是必须的,对吗?或者你是在说 WPF/Silverlight 绑定使用 sender 参数来检索更新后的值? - McGarnagle
消费NotifyPropertyChanged事件的对象如何知道哪个对象引发了该事件?如果一个对象在两个不同的对象上注册NotifyPropertyChanged,会怎么样? - shf301
@shf301 这是一个有趣的观点,我进行了测试——似乎如果sender是其他对象,WPF绑定仍然有效。 可能订阅对象/绑定保持对源对象的引用,而不依赖于sender - McGarnagle
“但这并不是必须的,不是吗?”除非您了解类的每个订阅者,否则应该假设某个地方可能会依赖于按照文档实现INotifyPropertyChanged。我并不是期望您的想法会出问题,但如果出现问题,责任完全在您身上,并且有可能在尚不存在的.NET 5、第三方控件、属性转发器、许多其他帮助程序类上出现问题,所有这些都连接到PropertyChanged - user743382
你考虑过使用命令吗?在命令中,你可以传递参数。你正在使用哪个MVVM框架?我以前使用过MVVM Light,并且能够做类似的事情。 - Eric Scherrer
4个回答

20

如果我理解正确,您正在询问setter的调用者。这意味着,在到达setter本身之前在调用堆栈中的上一个方法调用(它也是一种方法)。

使用StackTrace.GetFrames方法来实现此目的。例如(摘自http://www.csharp-examples.net/reflection-callstack/):

using System.Diagnostics;

[STAThread]
public static void Main()
{
  StackTrace stackTrace = new StackTrace();           // get call stack
  StackFrame[] stackFrames = stackTrace.GetFrames();  // get method calls (frames)

  // write call stack method names
  foreach (StackFrame stackFrame in stackFrames)
  {
    Console.WriteLine(stackFrame.GetMethod().Name);   // write method name
  }
}

输出:

Main
nExecuteAssembly
ExecuteAssembly
RunUsersAssembly
ThreadStart_Context
Run
ThreadStart

基本上,您要求的是stackFrames [1] .GetMethod().Name


1
太好了,这就是我要的。使用 stackFrames[1].GetMethod().Type 可以获取调用 setter 的类的类型。谢谢! - McGarnagle
2
请注意,在经过优化的代码和没有调试信息的发布版本中,StackTrace对象相当无用。检查您的发布版本,可能没有相关信息。 - CAD bloke

5

对于您的问题,我的第一种方法是从PropertyEventArgs中派生出一个新类。这个新类将有一个成员,例如PropertyChangeOrigin,除了PropertyName之外。当您调用RaisePropertyChanged时,您提供一个新类的实例,并从CallerMemberName属性中获取PropertyChangeOrigin的信息。现在,当您订阅事件时,订阅者可以尝试将eventargs转换为您的新类,并在转换成功后使用此信息。


1

这是我在实现视图模型时常用的INotifyPropertyChanged中间层:

public class NotifyOnPropertyChanged : INotifyPropertyChanged
{
    private IDictionary<string, PropertyChangedEventArgs> _arguments;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void OnPropertyChanged([CallerMemberName] string property = "")
    {
        if(_arguments == null)
        {
            _arguments = new Dictionary<string, PropertyChangedEventArgs>();
        }

        if(!_arguments.ContainsKey(property))
        {
            _arguments.Add(property, new PropertyChangedEventArgs(property));
        }

        PropertyChanged(this, _arguments[property]);
    }
}

这里有两件事情。它使用[CallerMemberName]属性来设置属性名称。这使得使用语法如下:

public string Words
{
    set
    {
        if(value != _words)
        {
            _words = value;
            OnPropertyChanged( );
        }
    }
}

除此之外,它将PropertyChangedEventArgs对象存储在字典中,以便对于经常设置的属性不会创建大量对象。我相信这解决了你的问题。祝好运!

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接