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

lar*_*ryq 17 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..
Run Code Online (Sandbox Code Playgroud)

注意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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Eri*_*ert 17

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

lambda被传递给一个名为的方法NotifyPropertyChanged.该方法有一个重载.它有正式的参数类型Expression<Func<ViewModelBase, T>>.也就是说,形式参数期望得到一个带有ViewModelBase的lambda并返回一个T.

p是lambda采用的参数.

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

NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);

他们想要明确它吗?

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

编译器检查所​​有可能的重载NotifyPropertyChanged,可能在该参数位置采用lambda.据推断为正式参数类型的拉姆达代表是在类型的形式参数类型的NotifyPropertyChanged方法.一个例子可能有帮助.假设我们有:

void M(Func<int, double> f) {}
void M(Func<string, int> f) {}
Run Code Online (Sandbox Code Playgroud)

和一个电话

M(x=>x.Length);
Run Code Online (Sandbox Code Playgroud)

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

编译器现在必须确定哪一个是正确的.为了使第一个正确,lambda的主体必须返回一个双精度.但是如果x是int,则x上没有返回double的属性Length.所以x不能是int.x可以是字符串吗?是.如果x是字符串,则x上有一个属性Length,它返回一个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);
Run Code Online (Sandbox Code Playgroud)

类型推断必须推断类型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,但我遇到了一系列编译器错误,其中第一个声明了无效的表达式术语'=>'这让我感到很困惑我认为这会奏效.

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


jas*_*son 10

p它只是一个虚拟名称,它是任何方法中的参数名称.您可以命名,x或者Fred,如果您愿意.

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

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

public double GetQuantitysaved(ViewModelBase p) {
    return QuantitySaved;
}
Run Code Online (Sandbox Code Playgroud)

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

delegate(ViewModelBase p) { return QuantitySaved; }
Run Code Online (Sandbox Code Playgroud)

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

p => QuantitySaved
Run Code Online (Sandbox Code Playgroud)

p这里扮演这三个版本相同的作用.您可以随意命名.它只是方法参数的名称.

在最后一种情况下,编译器做了很多工作来弄清楚它p代表了一个类型的参数,ViewModelBase以便p => QuantitySaved可以发挥作用

Expression<Func<ViewModelBase, T>> property
Run Code Online (Sandbox Code Playgroud)

为了好玩,我从改变的代码pthis,因为SampleViewModel从继承ViewModelBase,但我遇到了一系列的编译器错误,其中第一个说的Invalid expression term '=>'这个困惑我一点,因为我认为这是可行的.

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

delegate(ViewModelBase p) { return QuantitySaved; }
Run Code Online (Sandbox Code Playgroud)

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


Tes*_*rex 8

lambda p => QuantitySaved是类型的表达式Expression<Func<ViewModelBase, int>>.由于该方法NotifyPropertyChanged正在寻找表达式<ViewModelBase, T>,因此适合.

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

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

  • @larryq:它使用*表达式树*调用NotifyPropertyChanged,*树*具有*参数*,称为p.不能将p与lambda分开,只能将形式参数'property'的声明与NotifyPropertyChanged分开. (3认同)