.Net lambda表达式 – 这个参数来自哪里?
我是一个lambda新手,所以如果我在描述中缺少重要信息,请告诉我。 我会尽可能简单地保持这个例子。
我正在查看其他人的代码,他们有一个inheritance自另一个的类。 这里首先是派生类,以及我无法理解的lambda表达式:
class SampleViewModel : ViewModelBase { private ICustomerStorage storage = ModelFactory.Create(); public ICustomer CurrentCustomer { get { return (ICustomer)GetValue(CurrentCustomerProperty); } set { SetValue(CurrentCustomerProperty, value); } } private int quantitySaved; public int QuantitySaved { get { return quantitySaved; } set { if (quantitySaved != value) { quantitySaved = value; NotifyPropertyChanged(p => QuantitySaved); //where does 'p' come from? } } } public static readonly DependencyProperty CurrentCustomerProperty; static SampleViewModel() { CurrentCustomerProperty = DependencyProperty.Register("CurrentCustomer", typeof(ICustomer), typeof(SampleViewModel), new UIPropertyMetadata(ModelFactory.Create())); } //more method definitions follow..
请注意上面对NotifyPropertyChanged(p => QuantitySaved)
位的调用。 我不明白“p”来自哪里。
这是基类:
public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged, IXtremeMvvmViewModel { public event PropertyChangedEventHandler PropertyChanged; protected virtual void NotifyPropertyChanged(Expression<Func> property) { MvvmHelper.NotifyPropertyChanged(property, PropertyChanged); } }
那里有很多与我确定的问题没有密切关系,但我想在包容性方面犯错误。
问题是,我不明白’p’参数来自哪里,以及编译器如何知道(显然?)从空中填充ViewModelBase的类型值?
为了好玩,我将代码从’p’更改为’this’,因为SampleViewModelinheritance自ViewModelBase,但我遇到了一系列编译错误,其中第一个Invalid expression term '=>'
这让我有点困惑因为我认为那会有用。
谁能解释这里发生了什么?
lambda p => QuantitySaved
是Expression
类型的Expression
。 由于NotifyPropertyChanged
方法正在寻找
的表达式,因此它适合。
因此编译器能够推断出p
是ViewModelBase
。 p
并非“来自”任何地方,它基本上都在这里宣布。 它是lambda的参数。 当有人使用您方法的property
参数时,它将被填充。 例如,如果将lambda放入一个名为lambda
的单独变量中,则可以使用lambda(this)
调用它,并返回QuantitySaved
值。
你不能在lambda中使用它的原因是因为它需要一个参数名,而this
不是一个有效的名字。 关键是你可以在任何ViewModelBase
实例上调用它,而不仅仅是创建lambda的实例。
‘p’来自
NotifyPropertyChanged(p => QuantitySaved);
lambda被传递给一个名为NotifyPropertyChanged
的方法。 该方法有一个重载。 它具有forms参数类型Expression
。 也就是说,forms参数期望得到一个带有ViewModelBase的lambda并返回一个T.
p
是lambda采用的参数。
编译器能够推断出代码的作者忽略了明确地说明lambda参数的类型。 作者也可以写:
NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);
他们想要明确它吗?
编译器如何知道从空中填充ViewModelBase的类型值?
编译器检查NotifyPropertyChanged
所有可能重载,这些重载可能在该参数位置采用lambda。 它从NotifyPropertyChanged
方法的forms参数类型中的 委托类型推断出lambda的forms参数类型 。 一个例子可能有帮助。 假设我们有:
void M(Func f) {} void M(Func f) {}
和一个电话
M(x=>x.Length);
编译器必须推断lambda参数x的类型。 有什么可能性? M有两个重载。两个都在M的forms参数中占用一个委托,该委托对应于在调用中传递的第一个参数。 在第一个函数中,函数从int到double,因此x可以是int类型。 在第二个中,M的forms参数是从string到int的函数,因此x可以是string。
编译器现在必须确定哪一个是正确的。 为了使第一个正确,lambda的主体必须返回一个双精度。 但是如果x是int,则x上没有返回double的属性Length。 所以x不能是int。 x可以是字符串吗? 是。 如果x是字符串,则x上有一个属性Length,它返回一个int。
因此编译器推断出x是字符串。
这些扣除可能会非常复杂。 一个稍微复杂的例子:
void M(A a1, Func, B> a2, Func a3) {} ... M(123, x=>x.Count.ToString(), y=>y.Length);
类型推断必须推断类型A,B,C,因此推断x和y的类型。 编译器首先推断出A必须是int,因为a1是123.然后它推断出x必须是来自该事实的List
。 然后推断出B必须是字符串,因此y是字符串,因此C是y.Length
的类型,它是int。
相信我,从那里变得更加复杂。
如果这个主题让你感兴趣,我已经写了一些文章,并拍摄了一些关于编译器执行的各种类型推断主题的video。 看到
http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/
了解所有细节。
为了好玩,我将代码从’p’更改为’this’,因为SampleViewModelinheritance自ViewModelBase,但我遇到了一系列编译器错误,其中第一个声明了无效的表达式术语’=>’这让我感到很困惑我认为那会有用。
lambda运算符唯一可接受的左侧是lambda参数列表; “this”永远不是合法的lambda参数列表。 编译器期望“this”后面跟着“.SomeMethod()”或者其他东西; 编译器假定“this”永远不会跟“=>”。 当你违反这个假设时,就会发生坏事。
p
只是一个虚拟名称,它是任何方法中的参数名称。 如果你愿意,可以将它命名为x
或Fred
。
请记住,lambda表达式只是非常非常特殊的匿名方法。
在常规方法中,您有参数,并且它们具有名称:
public double GetQuantitysaved(ViewModelBase p) { return QuantitySaved; }
在匿名方法中,您有参数,并且它们具有名称:
delegate(ViewModelBase p) { return QuantitySaved; }
在lambda表达式中,您有参数,它们具有名称:
p => QuantitySaved
这里的p
在所有三个版本中扮演相同的角色。 您可以随意命名。 它只是方法参数的名称。
在最后一种情况下,编译器做了很多工作来弄清楚p
表示ViewModelBase
类型的参数,这样p => QuantitySaved
就可以扮演
Expression> property
为了好玩,我将代码从
p
更改this
,因为SampleViewModel
inheritance自ViewModelBase
,但我遇到了一系列编译器错误,第一个Invalid expression term '=>'
这让我感到困惑,因为我认为会工作。
好吧, this
不是一个有效的参数名,因为它是一个保留的关键字。 最好将p => QuantitySaved
视为
delegate(ViewModelBase p) { return QuantitySaved; }
直到你对这个想法感到满意。 在这种情况下, this
永远不能替换为p
因为它不是有效的参数名称。
理解这个的简单方法是替换这个:
p => QuantitySaved // lambda
有了这个:
delegate (ViewModelBase p) { return QuantitySaved; } // anonymous delegate
这实际上是一样的。 p
是匿名委托的第一个参数的参数名称。 您可以为其指定任何适合参数名称的名称( this
是一个关键字,您不能将其用作参数名称)
在这个特定的例子中,这个p
变量是冗余的,你也可以使用无参数委托。
从NotifyPropertyChanged签名:
void NotifyPropertyChanged(Expression> property)
该方法需要一个表达式,该表达式接受ViewModelBase
类型的输入并返回类型为T
的实例。
p参数是ViewModelBase的一个实例。