Determining the caller inside a setter — or setting properties, silently

旧城冷巷雨未停 提交于 2019-12-18 12:26:39

问题


Given a standard view model implementation, when a property changes, is there any way to determine the originator of the change? In other words, in the following view model, I would like the "sender" argument of the "PropertyChanged" event to be the actual object that called the 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
}

Alternatively, is it possible to apply CallerMemberNameAttribute to a property setter?


回答1:


If I understood correctly, you're asking about the caller of the setter. That means, the previous method call in the call stack before getting to the setter itself (which is a method too).

Use StackTrace.GetFrames method for this. For example (taken from 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
  }
}

The output:

Main
nExecuteAssembly
ExecuteAssembly
RunUsersAssembly
ThreadStart_Context
Run
ThreadStart

Basically, what you're asking for would be stackFrames[1].GetMethod().Name.




回答2:


My first approach to your problem would be to derive from PropertyEventArgs. The new class would have a member called, for instance PropertyChangeOrigin in addition to PropertyName. When you invoke the RaisePropertyChanged, you supply an instance of the new class with the PropertyChangeOrigin set from the information gleaned from the CallerMemberName attribute. Now, when you subscribe to the event, the subscriber could try casting the eventargs to your new class and use the information if the cast is successful.




回答3:


Have you looked at the CallerMemberName msdn page ?

I'm using it to avoid doing RaisePropertyChanged("some_name_here"); by having this in a base class, and then all properties just call RaisePropertyChanged();.

I believe it could be used in the setter as well ...

Edit:

As per the comment question, here's a bit of code that will do it (silly, but still, will do what was asked:)

public int some_int
{
    get { return _someInt; }
    set
    {
        _someInt = value;
        var v = check_sender();
        Console.Out.WriteLine("in the setter of {0}", v);
    }
}
private int _someInt;

private string check_sender([CallerMemberName]string property = "")
{
    return property;
}

Then if in the setter I'll write:

some_int = 7; // obviously, any number will cause it to fire

You'll get in the output (note the middle line, added the rest so you'll see it's some console output):

'WPF MVVM for SO.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework-SystemXmlLinq\v4.0_4.0.0.0__b77a5c561934e089\PresentationFramework-SystemXmlLinq.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
in the setter of some_int
'WPF MVVM for SO.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework.Aero\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.Aero.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.



回答4:


This is what I always use as a middle-ground between INotifyPropertyChanged and my View Models:

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

Two things here. It uses the [CallerMemberName] attribute to set the property name. This makes the usage syntax as follows:

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

Beyond that, it stores the PropertyChangedEventArgs object in a dictionary so it's not created a ton of times for properties that are frequently set. I believe this addresses your problem. Good luck!




回答5:


Whenever I have had to pass in extra information down into a VM I have a great success with using commands:

Commands, RelayCommands and EventToCommand



来源:https://stackoverflow.com/questions/19737351/determining-the-caller-inside-a-setter-or-setting-properties-silently

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!