问题
(with C# 3.0 and VS 2008).
Doing MVVM WPF stuff you often write properties like this:
public bool MyProperty {
get{return _myProperty;}
set{
if(_myProperty == value)return;
_myProperty = value;
RaisePropertyChanged("MyProperty");
}
}
Doing TDD I often end up writing tests such as:
[Test]
public void MyPropertyRaisesPropertyChangedWhenChanged(){
var mySUT = CreateSUT();
bool eventRaised = false;
string propName = "";
mySUT.PropertyChanged +=
(s,e)=>{eventRaised = true;propName = e.PropertyName;};
Assert.That(mySUT.MyProperty,Is.False(),"mySUT.MyProperty");
mySUT.MyProperty = true;
Assert.That(eventRaised,"eventRaised");
Assert.That(propName, Is.EqualTo("MyProperty"),"propName");
// could check not raised when set same...
}
I experimented with a method like this:
public class MyTestMethods{
public static PropertyChangedEventHandler MakePropertyChangedHandler(
bool eventWasRaised, string propertyName){
return (s,e)=>{eventWasRaised = true; propertyName = e.PropertyName};
}
}
So that I could write my test:
[Test]
public void MyPropertyRaisesPropertyChangedWhenChanged(){
var mySUT = CreateSUT();
bool eventRaised = false;
string propName = "";
mySUT.PropertyChanged +=
MyTestMethods.MakePropertyChangedHandler(eventRaised,propName);
// etc...
}
But VS2008 told me that eventRaised would always be false.
I thought maybe changing MakePropertyChangedHandler to use ref parameters would work
public static PropertyChangedEventHandler MakePropertyChangedHandler(
ref bool eventWasRaised, ref string propertyName){
return // lambda...
}
but VisualStudio tells me 'Cannot use ref or out parameter 'x' inside an anonymous method body'.
Can anyone tell me if it's possible to write a working method like MakePropertyChangedHandler and if not, how come?
回答1:
It's not possible to give ref to a lambda, because proper life cycle management can't be ensured. When the compiler encounters a closure (lambda using outer scope variable), it
- wraps all captured local variables in an anonymous object,
- creates instance of this object instead of allocating the variables on the stack,
- makes the lambda code a method of this object and
- returns a delegate to this object and method
(details might be a bit different, but that's the principle). This way the captured variables exist as long as the delegate does.
However when compiling the function higher up the stack, the compiler does not know this and so it allocates variables on the stack. That's faster, but the variables only exist until the function returns. Since the closure can live longer than that (in your case it won't, but the compiler can't know), the closure can't refer to the stack variable.
What you can do is create an object with reference semantics (which lives as long as it's refered to) and give it to the closure. So if you create:
class BoolHolder {
public bool value;
};
pass BoolHolder to the lambda and in the lambda do
boolHolder.value = true;
than you'll see the change outside.
来源:https://stackoverflow.com/questions/4817352/question-about-passing-parameters-to-methods-that-return-lambdas