Лямбда-выражение для свойства класса - PullRequest
7 голосов
/ 07 августа 2009

Может кто-нибудь объяснить, почему первый из двух следующих примеров действителен, а другой нет? В частности, как в первом примере создаются отношения между T и TProperty?

//Example 1  
class SomeClass<T> where T : class
{
    void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
}

//Example 2  
class SomeClass
{
    void SomeMethod<T,TProperty>( Expression<Func<T,TProperty>> expression ) 
         where T : class{ ... }
}

Учитывая два примера, я ожидаю, что следующие реализации будут работать, а вторая - нет.

//Implementation w/ Example 1  
var sc = new SomeClass<MyDateClass>();
sc.SomeMethod( dt => dt.Year );

//Implementation w/ Example 2
var sc = new SomeClass();
sc.SomeMethod<MyDateClass>( dt => dt.Year );

У меня возникли трудности с обдумыванием того, как первый пример / реализация может игнорировать универсальный тип TProperty при выполнении SomeMethod, а второй пример / реализация не может, и, как кажется, между T создается неявная связь и TProperty в примере / реализации 1.

Решение Измените подпись метода в примере 2 следующим образом:

void SomeMethod<T>( Expression<Func<T,Object>> expression ){ ... }

Хотя это позволит в результате использовать произвольные объекты в выражении, в действительности оно допускает артикуляцию свойств, как описано в реализации 2.

Ответы [ 4 ]

7 голосов
/ 07 августа 2009

Вы позволяете компилятору выводить параметры типа. Это прекрасно работает для случая 1:

class SomeClass<T> where T : class {
    void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
}

потому что вы сначала объявляете экземпляр:

var sc = new SomeClass<MyDateClass>();

, который сообщает компилятору, что T is MyDateClass. Затем при вызове метода:

sc.SomeMethod( dt => dt.Year );

компилятор знает, что T is MyDateClass, поэтому T.Year должно быть int. Этого достаточно для разрешения SomeMethod как SomeMethod<MyDateClass, int>.

Ваш второй пример, однако, явно указывает параметры типа (типов) - говорит компилятору не выводит его:

sc.SomeMethod<MyDateClass>( dt => dt.Year );

К сожалению, он пытается вызвать SomeMethod только с одним параметром типа (бит <MyDateClass>). Так как этого не существует, он будет жаловаться и скажет, что вам нужно 2 параметра типа.

Если вместо этого вам нужно было назвать это так, как вы сделали в первом примере:

sc.SomeMethod( dt => dt.Year );

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

sc.SomeMethod<MyDateClass, int>( dt => dt.Year );

Или, скажите компилятору, что такое dt (это то, что вы сделали в первом примере, обновив SomeClass<MyDateClass>):

sc.SomeMethod((MyDateClass dt) => dt.Year );

Правки и обновления:

Так эффективно ли компилятор выполняет магию в первом примере? Делая предположение, что TProperty должен быть int, потому что .Year - int? Это ответило бы на мой вопрос, но не могу сказать, что это удовлетворяет.

Не совсем волшебство, но серия маленьких шагов. Иллюстрировать:

  1. sc имеет тип SomeClass<MyDateClass>, что делает T = MyDateClass
  2. SomeMethod вызывается с параметром dt => dt.Year.
  3. dt должен быть T (вот как определяется SomeMethod), который должен быть MyDateClass для экземпляра sc.
  4. dt.Year должен быть int, поскольку MyDateClass.Year объявлено как int.
  5. Поскольку dt => dt.Year будет всегда возвращать int, возвращаемое значение expression должно быть int. Поэтому TProperty = int.
  6. Это делает параметр от expression до SomeMethod, Expression<Func<MyDateClass,int>>. Напомним, что T = MyDateClass (шаг 1) и TProperty = int (шаг 5).
  7. Теперь мы выяснили все параметры типа, сделав SomeMethod<T, TProperty> = SomeMethod<MyDateClass, int>.

[B] но в чем разница между указанием T на уровне класса и указанием на уровне метода? SomeClass<SomeType> против SomeMethod<SomeType> ... в обоих случаях известно Т, не так ли?

Да, T известен в обоих случаях. Проблема в том, что SomeMethod<SomeType> вызывает метод только с одним параметром типа. В примере 2 есть параметры типа 2 . Вы можете либо указать компилятору все параметры типа для метода, либо none из них. Вы не можете просто пройти 1 и сделать так, чтобы оно выводило другое (TProperty). Итак, если вы собираетесь явно указать T, то вы также должны явно указать TProperty.

1 голос
/ 07 августа 2009

Во-первых, ваш первый пример неверен и не будет скомпилирован должным образом. Помимо отсутствующего public в методе, вы определяете T дважды - один раз для класса и один раз для метода. Само по себе это не ошибка (хотя вы получите предупреждение), но при компиляции вызова метода вы получите ту же ошибку, что и при описании, например, получения # 2. Итак, я предполагаю, что реальный код выглядит так:

//Example 1  
class SomeClass<T> where T : class
{
  public void SomeMethod<TProperty>(Expression<Func<T,TProperty>> expression) {}
}

Кроме того, оба ваших примера "игнорируют" (то есть выводят) фактический тип TProperty в вашем вызове SomeMethod. Ваш второй пример явно указывает тип T, да, но не TProperty.

В первом примере также не установлены неявные отношения. Когда вы создаете экземпляр SomeClass<> с помощью T=MyDateClass, подпись SomeMethod для этого экземпляра фактически становится:

void SomeMethod<TProperty>(Expression<Func<MyDateClass, TProperty>> expression)

Таким образом, тип аргумента лямбды на данный момент известен, поэтому вам не нужно его указывать. Возвращаемый тип выражения лямбда выводится как тип выражения справа от =>, и это будет выводимый тип TProperty.

Во втором примере, поскольку T не был явно указан при создании экземпляра класса, и поскольку нет способа вывести его из аргументов метода, его необходимо указывать явно. Как только вы укажете его, TProperty будет выведено точно так же, как, например, # 1.

1 голос
/ 07 августа 2009

Вместо того, чтобы вводить подробности, просмотрите этот пост Эрик Липперт . Я думаю, что это ответит на то, что вы пытаетесь спросить.

Я понимаю, что это долго и надуманно, но я думаю, что это лучший удар за доллар, чтобы ответить вам.

0 голосов
/ 07 августа 2009

Насколько я понимаю, проблема в том, что DateTime не является классом ... вы передаете T как DateTime (неявно или явно).

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

//Example 1  
class SomeClass<T> where T : class
{
    void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
}

Теперь это то же самое T : class

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...