Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

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!

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top