Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net lambda expression-- where did this parameter come from?

Tags:

c#

lambda

I'm a lambda newbie, so if I'm missing vital information in my description please tell me. I'll keep the example as simple as possible.

I'm going over someone else's code and they have one class inheriting from another. Here's the derived class first, along with the lambda expression I'm having trouble understanding:

    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..

Note the call to NotifyPropertyChanged(p => QuantitySaved) bit above. I don't understand where the "p" is coming from.

Here's the base class:

  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);
        }
    }

There's a lot in there that's not germane to the question I'm sure, but I wanted to err on the side of inclusiveness.

The problem is, I don't understand where the 'p' parameter is coming from, and how the compiler knows to (evidently?) fill in a type value of ViewModelBase from thin air?

For fun I changed the code from 'p' to 'this', since SampleViewModel inherits from ViewModelBase, but I was met with a series of compiler errors, the first one of which statedInvalid expression term '=>' This confused me a bit since I thought that would work.

Can anyone explain what's happening here?

like image 550
larryq Avatar asked Nov 30 '11 17:11

larryq


People also ask

What is parameter lambda?

A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.

What is the meaning of => in C#?

In lambda expressions, the lambda operator => separates the input parameters on the left side from the lambda body on the right side. The following example uses the LINQ feature with method syntax to demonstrate the usage of lambda expressions: C# Copy. Run.

Why do we use lambda expression in C#?

Lambda expressions are like anonymous methods but with much more flexibility. When using a lambda expression, you don't need to specify the type of the input. Hence, a lambda expression provides a shorter and cleaner way of representing anonymous methods.

How does a lambda expression differ from a subroutine?

The syntax of a lambda expression resembles that of a standard function or subroutine. The differences are as follows: A lambda expression does not have a name. Lambda expressions cannot have modifiers, such as Overloads or Overrides .


4 Answers

where does 'p' come from in NotifyPropertyChanged(p => QuantitySaved);

The lambda is being passed to a method called NotifyPropertyChanged. There is one overload of that method. It has formal parameter type Expression<Func<ViewModelBase, T>>. That is, the formal parameter expects to get a lambda that takes a ViewModelBase and returns a T, for some T.

The p is the parameter that the lambda takes.

The compiler is able to infer that the author of the code neglected to spell out the type of the lambda parameter explicitly. The author could also have written:

NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);

had they wanted to be explicit about it.

how does the compiler know to fill in a type value of ViewModelBase from thin air?

The compiler examines all possible overloads of NotifyPropertyChanged that could possibly take a lambda in that argument position. It infers the formal parameter type of the lambda from the delegate types that are in the formal parameter types of the NotifyPropertyChanged methods. An example might help. Suppose we have:

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

and a call

M(x=>x.Length);

The compiler must infer the type of the lambda parameter x. What are the possibilities? There are two overloads of M. Both take a delegate in the formal parameter of M corresponding to the first argument passed in the call. In the first one, the function is from int to double, so x could be of type int. In the second one, the formal parameter of M is a function from string to int, so x could be string.

The compiler must now determine which one is correct. In order for the first one to be correct, the body of the lambda must return a double. But if x is int, there is no property Length on x that returns a double. So x cannot be int. Can x be string? Yes. There is a property Length on x that returns an int if x is string.

Therefore the compiler deduces that x is string.

These deductions can get extraoridinarily complicated. A slightly more complicated example:

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);

Type inference must infer the types A, B, C and therefore the types of x and y. The compiler first infers that A must be int since a1 is 123. It then infers that x must be List<int> from that fact. It then infers that B must be string, and therefore y is string, and therefore C is the type of y.Length, which is int.

It gets a lot more complicated from there, believe me.

If this subject interests you, I have written a number of articles and filmed some videos on the subject of various kinds of type inference performed by the compiler. See

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

for all the details.

For fun I changed the code from 'p' to 'this', since SampleViewModel inherits from ViewModelBase, but I was met with a series of compiler errors, the first one of which statedInvalid expression term '=>' This confused me a bit since I thought that would work.

The only acceptable left-hand-side of a lambda operator is a lambda parameter list; "this" is never a legal lambda parameter list. The compiler is expecting "this" to be followed by ".SomeMethod()" or some such thing; the compiler assumes that "this" will never be followed by "=>". When you violate that assumption, bad things happen.

like image 89
Eric Lippert Avatar answered Oct 12 '22 03:10

Eric Lippert


p is just a dummy name, it's the name of the parameter like in any method. You could name it x or Fred, if you like.

Remember, lambda expressions are just very, very special anonymous methods.

In regular methods you have parameters, and they have names:

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

In anonymous methods you have parameters, and they have names:

delegate(ViewModelBase p) { return QuantitySaved; }

In lambda expressions you have parameters, and they have names:

p => QuantitySaved

The p here plays the same role in all three versions. You can name it whatever you want. It's just the name of a parameter to the method.

In the last case, the compiler does a lot of work to figure out that p represents a parameter of type ViewModelBase so that p => QuantitySaved can play the role of

Expression<Func<ViewModelBase, T>> property

For fun I changed the code from p to this, since SampleViewModel inherits from ViewModelBase, but I was met with a series of compiler errors, the first one of which stated Invalid expression term '=>' This confused me a bit since I thought that would work.

Well, this is not a valid parameter name because it's a reserved keyword. It's best to think of p => QuantitySaved as

delegate(ViewModelBase p) { return QuantitySaved; }

until you get comfortable with the idea. In this case, this could never be substituted in for p as it is not a valid parameter name.

like image 39
jason Avatar answered Oct 12 '22 03:10

jason


The lambda p => QuantitySaved is an expression of type Expression<Func<ViewModelBase, int>>. Since the method NotifyPropertyChanged is looking for an expression of <ViewModelBase, T>, it fits.

So the compiler is able to infer that p is a ViewModelBase. p didn't "come from" anywhere, it's basically being declared right here. It's a parameter for the lambda. It's going to be filled in when someone uses the property parameter of your method. For example, if you put your lambda into a separate variable called lambda, you could call it with lambda(this) and it would return the QuantitySaved value.

The reason you can't use this in the lambda is because it's expecting a parameter name, and this isn't a valid name. The point is that you could call it on any instance of ViewModelBase, not just the one that created the lambda.

like image 28
Tesserex Avatar answered Oct 12 '22 02:10

Tesserex


The easy way to understand this is to replace this:

p => QuantitySaved // lambda

with this:

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

Which is effectively the same. p is a parameter name for first parameter of your anonymous delegate. You can give it any name appropriate for parameter names (this is a keyword, you cannot use it as parameter name)

In this particular example this p variable is redundant by the way, you could use parameterless delegate as well.

like image 30
Snowbear Avatar answered Oct 12 '22 02:10

Snowbear