2009-08-06 14 views
7

¿Alguien puede explicar por qué el primero de los dos ejemplos siguientes es válido y el otro no? Más específicamente, ¿cómo se crea una relación entre T y TProperty en el primer ejemplo?Expresión lambda para propiedad de clase

//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{ ... } 
} 

Teniendo en cuenta los dos ejemplos que cabe esperar que las siguientes implementaciones funcionarían, pero el segundo no.

//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); 

Lo que estoy teniendo dificultades para envolver alrededor de mi mente es como el primer ejemplo/aplicación puede ignorar el tipo genérico TProperty al ejecutar SomeMethod, sin embargo, el segundo ejemplo/aplicación no puede y cómo una relación implícita parece establecer entre T y TProperty en el ejemplo/aplicación 1.

Solución Cambio firma del método del ejemplo 2 de la siguiente manera:

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

Si bien esto permitirá que se utilicen objetos arbitrarios en la expresión como resultado, permite la articulación de propiedades como se describe en la implementación 2.

Respuesta

7

Está permitiendo que el compilador infiera los parámetros de tipo. Esto funciona bien para el caso 1:

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

porque se declara en primer lugar una instancia:

var sc = new SomeClass<MyDateClass>(); 

que le dice al compilador que T is MyDateClass. Entonces, cuando se llama al método:

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

el compilador sabe que T is MyDateClass, por lo T.Year debe ser un entero. Es suficiente información para resolver SomeMethod como SomeMethod<MyDateClass, int>.

Su segundo ejemplo, sin embargo, se indica explícitamente el parámetro (s) Tipo - diciendo al compilador que no inferir que:

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

Por desgracia, se está tratando de llamar SomeMethod con un solo tipo parámetro (el bit <MyDateClass>). Como eso no existe, se quejará y dirá que necesita 2 parámetros de tipo.

En cambio, si se va a llamar como lo hizo el primer ejemplo:

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

El compilador se quejará, que le dice que no puede inferir los parámetros de tipo - simplemente no hay suficiente información para determinar qué dt es. Por lo tanto, se puede establecer explícitamente ambos parámetros Tipo:

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

O decirle al compilador lo dt es (que es lo que hizo en el primer ejemplo, por Newing SomeClass<MyDateClass>):

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

ediciones y Actualizaciones:

¿De manera efectiva el compilador está realizando magia en el primer ejemplo? ¿Asumiendo que TProperty debería ser un int porque .Year es un int? Eso respondería a mi pregunta, no puedo decir que sea satisfactorio.

No es realmente mágico, sino una serie de pequeños pasos.Para illustate:

  1. sc es de tipo SomeClass<MyDateClass>, haciendo T = MyDateClass
  2. SomeMethod se llama con un parámetro de dt => dt.Year.
  3. dtdebe ser una T (que es como se define SomeMethod), que debe ser un MyDateClass para la instancia sc.
  4. dt.Yeardebe ser un int, ya que MyDateClass.Year se declara como int.
  5. Desde dt => dt.Year se siempre devuelven un int, el regreso de expression debe ser int. Por lo tanto, TProperty = int.
  6. Eso hace que el parámetro expression a SomeMethod, Expression<Func<MyDateClass,int>>. Recuerde que T = MyDateClass (paso 1) y TProperty = int (paso 5).
  7. Ahora hemos descubierto todos los parámetros de tipo, haciendo SomeMethod<T, TProperty> = SomeMethod<MyDateClass, int>.

[B] ut cuál es la diferencia entre especificar T a nivel de clase y especificando en el nivel de método? SomeClass<SomeType> vs. SomeMethod<SomeType> ... en ambos casos, T se sabe, ¿no es así?

Sí, T es conocido en ambos casos. El problema es que SomeMethod<SomeType> llama a un método con un solo parámetro de tipo. En el ejemplo 2, hay parámetros de tipo. Puede hacer que el compilador infiera todos los parámetros de tipo para el método o ninguno de ellos. No puede pasar 1 y hacer que infiera el otro (TProperty). Por lo tanto, si va a indicar explícitamente T, también debe indicar explícitamente TProperty.

+1

¿De manera efectiva el compilador está realizando magia en el primer ejemplo? ¿Asumiendo que TProperty debería ser un int porque .Year es un int? Eso respondería a mi pregunta, no puedo decir que sea satisfactorio. :) –

+0

... Creo que la luz simplemente se apagó. La diferencia entre la clase y el método es que la clase implica que una instancia de T existe en contexto mientras que el método no? De ahí las suposiciones del compilador? –

+0

No estoy seguro de por qué no está satisfecho, pero la inferencia de tipo estático no es mágica, y tiene reglas explícitas y es una característica nueva en C#. –

0

El problema, hasta donde puedo ver, es simplemente que DateTime no es un clase ... está pasando T como DateTime (ya sea implícita o explícita).

Probablemente también sea un poco confuso que en el primer ejemplo tenga dos parámetros de tipo llamados T - uno en el tipo y otro en el método. En realidad son completamente separado ... a ser el mismo, vuelve a escribir el primer ejemplo como:

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

Esta es ahora la misma T : class

+0

Lo siento mal ejemplo ... obviamente DateTime es una estructura. Hice ediciones a mi ejemplo para poder articular mejor el problema. Asumiendo que el objeto es realmente una clase con propiedades y no una estructura. –

+1

Si bien esto es cierto, y ciertamente puede escribirse de esta manera: me interesa saber dónde se establece la conexión entre TProperty y T, permitiendo así que p => p.Property en la expresión sin definir el tipo genérico de TProperty. Más importante aún es cómo logro lo mismo con un método que no está en una clase genérica. ¿Estoy teniendo sentido? :) –

1

En primer lugar, el primer ejemplo es incorrecto, y no compilar como dado. Además del public que falta en el método, usted define T dos veces, una vez en la clase y una vez en el método. Esto no es un error en sí mismo (aunque recibirá una advertencia), pero al compilar la llamada al método, obtendrá el mismo error que describe al obtener, por ejemplo, el n. ° 2. Por lo tanto, supongo que el código actual es la siguiente:

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

Por otra parte, tanto sus ejemplos "ignorar" (es decir, inferir) el tipo real de TProperty en su llamada a SomeMethod. Su segundo ejemplo especifica explícitamente el tipo de T, sí, pero no TProperty.

No existe una relación implícita establecida en el primer ejemplo, tampoco. Al crear una instancia SomeClass<> con T=MyDateClass, la firma de SomeMethod para que instanciación se convierte efectivamente en:

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

Así que el tipo de argumento de la lambda se sabe en este momento, por lo que no tiene que especificarlo. El tipo de expresión devuelta lambda se deduce como el tipo de expresión a la derecha de =>, y ese será el tipo inferido de TProperty.

En el segundo ejemplo, como T no se especificó explícitamente al crear una instancia de la clase, y como no hay forma de inferirlo de los argumentos del método, debe especificarse explícitamente. Una vez que lo especifique, TProperty se deduce de la misma manera que, por ejemplo, # 1.

+0

No entiendo lo que está implicando con su comentario inferir. ¿Estás diciendo que ninguno de los dos ejemplos debería funcionar porque el tipo de TProperty es desconocido en los ejemplos? En cuanto a los errores de sintaxis, sí, culpable según lo acusado, no intenté compilar estos ejemplos antes de publicarlos. –

+0

"Infer" significa "averiguar automáticamente el tipo". En este caso, esto es así: se conoce el tipo de 'T' ->' T1' en 'Func ' se conoce -> tipo de 'dt' -> tipo de retorno de lambda se deduce como tipo de expresión' dt .Year' (una vez que sabemos el tipo de 'dt', obviamente sabemos que el tipo de' dt.Year' -> 'TResult' en' Func 'es conocido ->' TProperty' es conocido. –

+0

La confusión no es con una comprensión de lo que significa inferir, pero cómo la definición de MyDateClass en el nivel de clase vs. en el nivel de método hace la diferencia.es decir; SomeClass , SomeMethod ... en ambos casos, T1 es conocido, ¿por qué se infiere el tipo de TProperty en el primer ejemplo pero no en el segundo? –

1

En lugar de escribir los detalles, echa un vistazo a esta publicación por Eric Lippert. Creo que responderá a lo que intentas preguntar.

Me doy cuenta de que es largo y artificial, pero creo que es el mejor golpe para el dinero por responderle.

Cuestiones relacionadas