问题
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