Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

где "p" происходит из NotifyPropertyChanged(p => QuantitySaved);

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

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

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

NotifyPropertyChanged((ViewModelBase p) => QuantitySaved);

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

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

Компилятор проверяет все возможные перегрузки 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, нет свойства Length на x, которое возвращает double. Таким образом, x не может быть int. Может ли x быть строкой? Да. Существует свойство Length на x, которое возвращает 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.

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

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

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

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

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

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

Ответ 3

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, поскольку это не допустимое имя параметра.

Ответ 4

Легкий способ понять это - заменить это:

p => QuantitySaved // lambda

с этим:

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

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

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

Ответ 5

От подписи NotifyPropertyChanged:

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

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

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