.Net lambda expression - откуда взялся этот параметр?

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) выше. Я не понимаю, откуда приходит «р».

Вот базовый класс:

  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

lambda 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
источник
17
  

, где «p» происходит из   % Co_de%

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

Expression<Func<ViewModelBase, T>> - это параметр, который принимает лямбда.

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

p

если бы они хотели быть явным.

  

как компилятор знает, чтобы заполнить значение типа ViewModelBase из тонкого воздуха?

Компилятор проверяет все возможные перегрузки NotifyPropertyChanged((ViewModelBase p) => QuantitySaved); , которые могут принимать лямбда в этой позиции аргумента. Он описывает формальный тип параметра lambda из типов delegate , которые находятся в формальных типах параметров методов 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, нет свойства Length на x, которое возвращает double. Таким образом, x не может быть int. Может ли x быть строкой? Да. Существует свойство Length на x, которое возвращает int, если x является строкой.

Поэтому компилятор выводит, что x является строкой.

Эти вычеты могут усложнять extraoridinarily . Несколько более сложный пример:

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 должен быть NotifyPropertyChanged от этого факта. Затем он указывает, что B должен быть строкой, и поэтому y является строкой, и поэтому C является типом List<int> , который является int.

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

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

Ссылка

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

  

Для удовольствия я изменил код с 'p' на 'this', так как SampleViewModel наследует от ViewModelBase, но меня встретил ряд ошибок компилятора, первый из которых заявил, что выражение выражения '= & gt;' Это немного смутило меня, так как я думал, что это сработает.

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

    
ответ дан Eric Lippert 30.11.2011 в 19:04
источник
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
  

Для удовольствия я изменил код с p на this , так как SampleViewModel наследует от ViewModelBase , но меня встретила серия ошибок компилятора, первая из которых указала Invalid expression term '=>' Это смутило меня немного, так как я думал, что это сработает.

Ну, 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
источник
3

От подписи NotifyPropertyChanged:

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

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

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

    
ответ дан Rich O'Kelly 30.11.2011 в 18:46
источник