.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 '=>'
. Это немного смутило меня Я думал, что это сработает.
Может ли кто-нибудь объяснить, что происходит здесь?
Ответы
Ответ 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.