【问题标题】:.Net lambda expression-- where did this parameter come from?.Net lambda 表达式——这个参数是从哪里来的?
【发布时间】:2011-12-01 05:10:50
【问题描述】:

我是 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 =&gt; 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 '=&gt;' 这让我有点困惑,因为我认为这会起作用。

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

【问题讨论】:

    标签: c# lambda


    【解决方案1】:

    “p”从何而来 NotifyPropertyChanged(p =&gt; QuantitySaved);

    lambda 被传递给一个名为NotifyPropertyChanged 的方法。该方法有一个重载。它有形参类型Expression&lt;Func&lt;ViewModelBase, T&gt;&gt;。也就是说,形参期望得到一个 lambda,它接受一个 ViewModelBase 并返回一个 T,对于一些 T。

    p 是 lambda 采用的参数。

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

    NotifyPropertyChanged((ViewModelBase p) =&gt; QuantitySaved);

    他们是否想要明确说明。

    编译器如何知道凭空填写 ViewModelBase 的类型值?

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

    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 的主体必须返回一个 double。但是如果 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&lt;int&gt; 从这个事实。然后它推断B必须是字符串,因此y是字符串,因此C是y.Length的类型,它是int。

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

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

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

    了解所有细节。

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

    lambda 运算符的唯一可接受的左侧是 lambda 参数列表; "this" 绝不是合法的 lambda 参数列表。编译器期望“this”后面跟着“.SomeMethod()”或类似的东西;编译器假定“this”后面永远不会跟“=>”。当您违反该假设时,就会发生不好的事情。

    【讨论】:

      【解决方案2】:

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

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

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

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

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

      delegate(ViewModelBase p) { return QuantitySaved; }
      

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

      p => QuantitySaved
      

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

      在最后一种情况下,编译器做了很多工作来弄清楚p代表ViewModelBase类型的参数,以便p =&gt; QuantitySaved可以起到作用

      Expression<Func<ViewModelBase, T>> property
      

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

      好吧,this 不是有效的参数名称,因为它是保留关键字。最好将p =&gt; QuantitySaved 视为

      delegate(ViewModelBase p) { return QuantitySaved; }
      

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

      【讨论】:

        【解决方案3】:

        lambda p =&gt; QuantitySavedExpression&lt;Func&lt;ViewModelBase, int&gt;&gt; 类型的表达式。由于方法NotifyPropertyChanged 正在寻找&lt;ViewModelBase, T&gt; 的表达式,所以它适合。

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

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

        【讨论】:

        • 啊,好的。所以这是一个方法声明。我认为有问题的 sn-p 是——在那一刻——以“p”作为参数调用 NotifyPropertyChanged()。谢谢大家,我的眼睛一定是累了。
        • 不,您不能使用this,因为它是保留关键字。它永远不能作为参数名称。
        • @larryq:它使用一个 表达式树 调用 NotifyPropertyChanged,该表达式树有一个名为 p 的 参数。没有将 p 与 lambda 分开,就像您可以将形式参数“property”的声明与 NotifyPropertyChanged 分开一样。
        • @Tesserex:lambda 是Expression&lt;Func&lt;ViewModelBase, int&gt;&gt; 类型的表达式,因为这是方法参数的类型。
        • 感谢 Eric。我应该更清楚地说明,传递给 NotifyPropertyChanged 的​​是表达式树,而不仅仅是“p”。
        【解决方案4】:

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

        p => QuantitySaved // lambda
        

        用这个:

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

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

        在这个特定的例子中,这个p 变量是多余的,顺便说一下,你也可以使用无参数委托。

        【讨论】:

        • 在这种情况下,情况不一样,因为 lambda 不是转换为委托,而是Expression。而delegate 匿名函数则无法做到这一点。
        【解决方案5】:

        来自 NotifyPropertyChanged 签名:

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

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

        p 参数是 ViewModelBase 的一个实例。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-12-14
          • 1970-01-01
          • 1970-01-01
          • 2020-07-31
          • 2019-11-18
          • 2018-09-25
          • 2014-10-23
          相关资源
          最近更新 更多