.Net lambda表达式 - 这个参数来自哪里?

时间:2011-11-30 17:42:51

标签: c# lambda

我是一个lambda新手,所以如果我在描述中缺少重要信息,请告诉我。我会尽可能简单地保持这个例子。

我正在查看别人的代码,他们有一个继承自另一个的类。这里首先是派生类,以及我无法理解的lambda表达式:

    class SampleViewModel : ViewModelBase
{
    private ICustomerStorage storage = ModelFactory<ICustomerStorage>.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<ICustomer>.Create()));
    }
//more method definitions follow..

请注意上面NotifyPropertyChanged(p => QuantitySaved)位的呼叫。我不明白“p”来自哪里。

这是基类:

  public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged, IXtremeMvvmViewModel
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void NotifyPropertyChanged<T>(Expression<Func<ViewModelBase, T>> property)
        {
            MvvmHelper.NotifyPropertyChanged(property, PropertyChanged);
        }
    }

那里有很多与我确定的问题没有密切关系,但我想在包容性方面犯错误。

问题是,我不明白'p'参数的来源,以及编译器如何知道(显然?)从空中填充ViewModelBase的类型值?

为了好玩,我将代码从'p'更改为'this',因为SampleViewModel继承自ViewModelBase,但我遇到了一系列编译错误,其中第一个错误表明Invalid expression term '=>'这让我很困惑因为我认为这会有用。

有谁能解释这里发生了什么?

5 个答案:

答案 0 :(得分:17)

  

'p'来自哪里   NotifyPropertyChanged(p => QuantitySaved);

将lambda传递给名为NotifyPropertyChanged的方法。该方法有一个重载。它具有形式参数类型Expression<Func<ViewModelBase, T>>。也就是说,形式参数需要获取一个带有ViewModelBase的lambda,并为某些T返回一个T.

p是lambda采用的参数。

编译器能够推断出代码的作者忽略了明确地说明lambda参数的类型。作者也可以写:

NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);

他们想要明确一点。

  

编译器如何知道从空中填充ViewModelBase的类型值?

编译器会检查NotifyPropertyChanged的所有可能重载,这些重载可能会在该参数位置中占用lambda。它从委托类型中的委托类型中推断出lambda 的形式参数类型,这些类型位于NotifyPropertyChanged 方法的形式参数类型中。一个例子可能有帮助。假设我们有:

void M(Func<int, double> f) {}
void M(Func<string, int> f) {}

和电话

M(x=>x.Length);

编译器必须推断lambda参数x的类型。有什么可能性? M有两个重载。两个都在M的形式参数中占用一个委托,该委托对应于在调用中传递的第一个参数。在第一个函数中,函数是从int到double,因此x可以是int类型。在第二个中,M的形式参数是从string到int的函数,因此x可以是string。

编译器现在必须确定哪一个是正确的。为了使第一个正确,lambda的主体必须返回一个双精度。但是如果x是int,则x上没有返回double的属性Length。所以x不能是int。 x可以是字符串吗?是。在x上有一个属性Length,如果x是字符串,则返回一个int。

因此编译器推断出x是字符串。

这些扣除可能会使非常复杂复杂化。一个稍微复杂的例子:

void M<A, B, C>(A a1, Func<List<A>, B> a2, Func<B, C> a3) {}
...
M(123, x=>x.Count.ToString(), y=>y.Length);

类型推断必须推断出类型A,B,C,因此必须推断出x和y的类型。编译器首先推断出A必须是int,因为a1是123.然后它推断出x必须是List<int>。然后它推断B必须是字符串,因此y是字符串,因此C是y.Length的类型,它是int。

相信我,从那里变得更加复杂。

如果这个主题让你感兴趣,我已经写了很多文章并拍摄了一些关于编译器执行的各种类型推断主题的视频。见

http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/

了解所有细节。

  

为了好玩,我将代码从'p'更改为'this',因为SampleViewModel继承自ViewModelBase,但我遇到了一系列编译错误,其中第一个错误表达了无效的表达式'=&gt;'这让我感到困惑,因为我认为这样可行。

lambda运算符唯一可接受的左侧是lambda参数列表; “this”永远不是合法的lambda参数列表。编译器期望“this”后面跟着“.SomeMethod()”或者其他东西;编译器假定“this”永远不会跟“=&gt;”。当你违反这个假设时,会发生不好的事情。

答案 1 :(得分:10)

p只是一个虚拟名称,它是任何方法中的参数名称。如果您愿意,可以将其命名为xFred

请记住,lambda表达式只是非常非常特殊的匿名方法。

在常规方法中,您有参数,并且它们具有名称:

public double GetQuantitysaved(ViewModelBase p) {
    return QuantitySaved;
}

在匿名方法中,您有参数,并且它们具有名称:

delegate(ViewModelBase p) { return QuantitySaved; }

在lambda表达式中,您有参数,它们有名称:

p => QuantitySaved

p在所有三个版本中扮演相同的角色。您可以随意命名。它只是方法参数的名称。

在最后一种情况下,编译器做了很多工作来确定p代表ViewModelBase类型的参数,以便p => QuantitySaved可以扮演

的角色
Expression<Func<ViewModelBase, T>> property
  

为了好玩,我将代码从p更改为this,因为SampleViewModel继承自ViewModelBase,但我遇到了一系列编译错误,第一个错误其中陈述Invalid expression term '=>'这让我感到困惑,因为我认为这样可行。

好吧,this不是有效的参数名,因为它是一个保留关键字。最好将p => QuantitySaved视为

delegate(ViewModelBase p) { return QuantitySaved; }

直到你对这个想法感到满意为止。在这种情况下,this永远无法代替p,因为它不是有效的参数名称。

答案 2 :(得分:8)

lambda p => QuantitySavedExpression<Func<ViewModelBase, int>>类型的表达式。由于方法NotifyPropertyChanged正在寻找<ViewModelBase, T>的表达式,因此它适合。

因此编译器能够推断pViewModelBasep没有“来自”任何地方,它基本上就在这里宣布。它是lambda的参数。当有人使用您方法的property参数时,它将被填充。例如,如果将lambda放入名为lambda的单独变量中,则可以使用lambda(this)调用它,并返回QuantitySaved值。

您无法在lambda中使用this的原因是因为它需要参数名称,而this不是有效名称。关键是你可以在ViewModelBase的任何实例上调用它,而不仅仅是创建lambda的实例。

答案 3 :(得分:4)

理解这一点的简单方法是替换它:

p => QuantitySaved // lambda

用这个:

delegate (ViewModelBase p) { return QuantitySaved; } // anonymous delegate

实际上是一样的。 p是匿名委托的第一个参数的参数名称。您可以为其指定任何适合参数名称的名称(this是关键字,不能将其用作参数名称)

在这个特定示例中,这个p变量是冗余的,你也可以使用无参数委托。

答案 4 :(得分:3)

来自NotifyPropertyChanged签名:

void NotifyPropertyChanged<T>(Expression<Func<ViewModelBase, T>> property)

该方法需要一个表达式,该表达式接受类型为ViewModelBase的输入,并返回类型为T的实例。

p参数是ViewModelBase的一个实例。