.Net лямбда-выражение - откуда появился этот параметр?

17

Я новичок в лямбда-выражении, поэтому, если мне не хватает важной информации в моем описании, пожалуйста, сообщите мне. Я буду держать пример как можно более простым.

Я перебираю чужой код, и один класс унаследован от другого. Вот сначала производный класс вместе с лямбда-выражением, с которым мне трудно разобраться:

    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 '=>' . Это немного смутило меня, так как Я думал, что это сработает.

Кто-нибудь может объяснить, что здесь происходит?

    
задан larryq 30.11.2011 в 18:42
источник

5 ответов

8

Лямбда p => QuantitySaved является выражением типа Expression<Func<ViewModelBase, int>> . Поскольку метод NotifyPropertyChanged ищет выражение <ViewModelBase, T> , он подходит.

Таким образом, компилятор может сделать вывод, что p является ViewModelBase . p никуда не "пришёл", он в основном объявляется прямо здесь. Это параметр для лямбды. Он будет заполнен, когда кто-то использует параметр property вашего метода. Например, если вы поместите лямбду в отдельную переменную с именем lambda , вы можете вызвать ее с помощью lambda(this) , и она вернет значение QuantitySaved .

Причина, по которой вы не можете использовать this в лямбде, заключается в том, что он ожидает имя параметра, а this не является допустимым именем. Дело в том, что вы можете вызывать его на любом экземпляре ViewModelBase , а не только на том, который создал лямбду.

    
ответ дан Tesserex 30.11.2011 в 18:52
  • А, ок. Таким образом, это объявление метода. Я думал, что этот фрагмент - это тот момент, который называется NotifyPropertyChanged () с параметром «p». Спасибо всем, мои глаза, должно быть, устали. –  larryq 30.11.2011 в 18:57
  • Нет, вы не можете использовать это, потому что это зарезервированное ключевое слово. Он никогда не действует как имя параметра. –  jason 30.11.2011 в 19:00
  • @larryq: он вызывает NotifyPropertyChanged с деревом выражений с параметром p. Больше не нужно отделять p от лямбда, чем вы можете отделить объявление формального параметра «свойство» от NotifyPropertyChanged. –  Eric Lippert 30.11.2011 в 19:08
  • @Tesserex: лямбда является выражением типа Expression <Func <ViewModelBase, int >>, так как это тип параметра метода. –  phoog 30.11.2011 в 19:16
  • Спасибо, что поймал меня Эрик. Я должен был более четко заявить, что это дерево выражений, а не просто «p», которое передается NotifyPropertyChanged. –  larryq 30.11.2011 в 19:46
17

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

Лямбда передается методу NotifyPropertyChanged . Существует одна перегрузка этого метода. Он имеет формальный тип параметра Expression<Func<ViewModelBase, T>> . То есть формальный параметр ожидает получить лямбду, которая принимает ViewModelBase и возвращает T для некоторого T.

p - это параметр, который принимает лямбда.

Компилятор может сделать вывод, что автор кода не учел явно указывать тип лямбда-параметра. Автор мог бы также написать:

NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);

если бы они хотели быть откровенными об этом.

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

Компилятор проверяет все возможные перегрузки NotifyPropertyChanged , которые могут занять лямбду в этой позиции аргумента. Он выводит формальный тип параметра лямбда из типов делегат , которые находятся в типах формальных параметров методов NotifyPropertyChanged . Пример может помочь. Предположим, у нас есть:

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

и звонок

M(x=>x.Length);

Компилятор должен определить тип лямбда-параметра x. Каковы возможности? Существует две перегрузки M. Оба принимают делегата в формальном параметре M, соответствующем первому аргументу, переданному в вызове. В первом случае функция имеет тип int или double, поэтому x может иметь тип int. Во втором формальный параметр M - это функция от строки до int, поэтому x может быть строкой.

Компилятор должен теперь определить, какой из них правильный. Для того, чтобы первый из них был правильным, тело лямбды должно возвращать двойное число. Но если x - это int, у x нет свойства Length, которое возвращает double. Так что x не может быть int. Может ли x быть строкой? Да. На x есть свойство Length, которое возвращает int, если x - строка.

Поэтому компилятор определяет, что 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.

Оттуда все становится намного сложнее, поверь мне.

Если эта тема вас интересует, я написал несколько статей и снял несколько видеороликов на тему различных типов вывода типов, выполняемых компилятором. Смотрите

Ссылка

для всех деталей.

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.

Единственной приемлемой левой стороной лямбда-оператора является список лямбда-параметров; «this» никогда не является допустимым списком лямбда-параметров. Компилятор ожидает, что за «this» последует «.SomeMethod ()» или что-то подобное; компилятор предполагает, что за «this» никогда не последует «= >». Когда вы нарушаете это предположение, случаются плохие вещи.

    
ответ дан Eric Lippert 30.11.2011 в 19:04
  • Большое спасибо за объяснение Эрика. Очень признателен. –  larryq 30.11.2011 в 19:49
10

p - это просто фиктивное имя, это имя параметра, как в любом методе. Вы можете назвать его x или Fred , если хотите.

Помните, что лямбда-выражения - это очень особые анонимные методы.

В обычных методах у вас есть параметры, и у них есть имена:

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

В анонимных методах у вас есть параметры, и у них есть имена:

delegate(ViewModelBase p) { return QuantitySaved; }

В лямбда-выражениях у вас есть параметры, а у них есть имена:

p => QuantitySaved

p здесь играет одинаковую роль во всех трех версиях. Вы можете назвать это как хотите. Это просто имя параметра метода.

В последнем случае компилятор проделал большую работу, чтобы выяснить, что p представляет параметр типа ViewModelBase , так что p => QuantitySaved может играть роль

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.

Ну, this не является допустимым именем параметра, потому что это зарезервированное ключевое слово. Лучше всего думать о p => QuantitySaved как

delegate(ViewModelBase p) { return QuantitySaved; }

пока вы не освоитесь с идеей. В этом случае this никогда не может быть заменен на p , так как это недопустимое имя параметра.

    
ответ дан jason 30.11.2011 в 18:48
4

Простой способ понять это - заменить это:

p => QuantitySaved // lambda

с этим:

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

Что, по сути, то же самое. p - это имя параметра для первого параметра вашего анонимного делегата. Вы можете дать ему любое имя, подходящее для имен параметров ( this является ключевым словом, вы не можете использовать его в качестве имени параметра)

В этом конкретном примере эта переменная p избыточна, кстати, вы также можете использовать делегат без параметров.

    
ответ дан Snowbear 30.11.2011 в 18:53
  • В этом случае это не одно и то же, поскольку лямбда не преобразована в делегат, а в выражение. И вы не можете сделать это с анонимными функциями делегата. –  svick 05.12.2011 в 16:55
3

Из подписи NotifyPropertyChanged:

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

Метод ожидает выражение, которое принимает входные данные типа ViewModelBase и возвращает экземпляр типа T .

Параметр p является экземпляром ViewModelBase.

    
ответ дан Rich O'Kelly 30.11.2011 в 18:46