whats news new microsoft features evolution docs c# .net c#-4.0 type-inference

news - versions of c#



¿Por qué C#no infiere mis tipos genéricos? (4)

Estoy teniendo mucha diversión de Funcy (diversión intencionada) con métodos genéricos. En la mayoría de los casos, la inferencia tipo C # es lo suficientemente inteligente como para descubrir qué argumentos genéricos debe usar en mis métodos genéricos, pero ahora tengo un diseño donde el compilador C # no tiene éxito, aunque creo que podría haber tenido éxito en encontrar el tipos correctos.

¿Puede alguien decirme si el compilador es un poco tonto en este caso, o hay una razón muy clara por la cual no puede inferir mis argumentos genéricos?

Aquí está el código:

Clases y definiciones de interfaz:

interface IQuery<TResult> { } interface IQueryProcessor { TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>; } class SomeQuery : IQuery<string> { }

Algún código que no compila:

class Test { void Test(IQueryProcessor p) { var query = new SomeQuery(); // Does not compile :-( p.Process(query); // Must explicitly write all arguments p.Process<SomeQuery, string>(query); } }

¿Por qué es esto? ¿Que me estoy perdiendo aqui?

Aquí está el mensaje de error del compilador (no deja mucho a nuestra imaginación):

Los argumentos de tipo para el método IQueryProcessor.Process (TQuery) no se pueden deducir del uso. Intente especificar los argumentos de tipo explícitamente.

La razón por la que creo que C # debería ser capaz de inferirla es por lo siguiente:

  1. Suministro un objeto que implementa IQuery<TResult> .
  2. Esa única IQuery<TResult> que implementa el tipo es IQuery<string> y, por lo tanto, TResult debe ser una string .
  3. Con esta información, el compilador tiene TResult y TQuery.

SOLUCIÓN

Para mí, la mejor solución fue cambiar la interfaz IQueryProcessor y usar el tipado dinámico en la implementación:

public interface IQueryProcessor { TResult Process<TResult>(IQuery<TResult> query); } // Implementation sealed class QueryProcessor : IQueryProcessor { private readonly Container container; public QueryProcessor(Container container) { this.container = container; } public TResult Process<TResult>(IQuery<TResult> query) { var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); dynamic handler = container.GetInstance(handlerType); return handler.Handle((dynamic)query); } }

La interfaz IQueryProcessor ahora toma un IQuery<TResult> . De esta forma, puede devolver un TResult y esto resolverá los problemas desde la perspectiva del consumidor. Necesitamos usar la reflexión en la implementación para obtener la implementación real, ya que los tipos de consulta concretos son necesarios (en mi caso). Pero aquí viene el tipeo dinámico al rescate que nos hará reflexionar. Puede leer más sobre esto en este article .


C # no deducirá tipos genéricos basados ​​en el tipo de devolución de un método genérico, solo los argumentos para el método.

Tampoco utiliza las restricciones como parte de la inferencia de tipo, lo que elimina la restricción genérica de proporcionar el tipo para usted.

Para más detalles, vea blogs.msdn.com/b/ericlippert/archive/2009/12/10/… .


La especificación establece esto claramente:

Sección 7.4.2 Tipo de inferencia

Si el número de argumentos proporcionado es diferente al número de parámetros en el método, la inferencia falla inmediatamente. De lo contrario, suponga que el método genérico tiene la siguiente firma:

Tr M (T1 x1 ... Tm xm)

Con una llamada de método de la forma M (E1 ... Em) la tarea de inferencia de tipo es encontrar argumentos de tipo únicos S1 ... Sn para cada uno de los parámetros de tipo X1 ... Xn, de modo que la llamada M (E1 ... Em) sea válida.

Como puede ver, el tipo de devolución no se utiliza para la inferencia de tipo. Si la llamada al método no se asigna directamente al tipo, la inferencia de argumentos falla inmediatamente.

El compilador no solo supone que usted quería string como el argumento TResult , ni tampoco puede hacerlo. Imagine un TResult derivado de una cadena. Ambos serían válidos, ¿para qué elegir? Mejor ser explícito.


No usa restricciones para inferir tipos. Más bien infiere tipos (cuando es posible) y luego verifica las restricciones.

Por lo tanto, si bien es el único TResult posible que podría usarse con un parámetro SomeQuery , no lo verá.

Tenga en cuenta también que sería perfectamente posible que SomeQuery también implemente IQuery<int> , que es una de las razones por las que esta limitación en el compilador puede no ser una mala idea.


Un grupo de personas ha señalado que C # no hace inferencias basadas en restricciones. Eso es correcto y relevante para la pregunta. Las inferencias se hacen al examinar los argumentos y sus correspondientes tipos de parámetros formales y esa es la única fuente de información de inferencia.

Un grupo de personas se han vinculado a este artículo:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

Ese artículo está desactualizado e irrelevante para la pregunta. Está desactualizado porque describe una decisión de diseño que hicimos en C # 3.0, que luego revirtimos en C # 4.0, en su mayoría en función de la respuesta a ese artículo. Acabo de agregar una actualización de ese efecto al artículo.

Es irrelevante porque el artículo trata sobre la inferencia del tipo de retorno desde los argumentos del grupo de métodos hasta los parámetros formales de delegado genérico . Esa no es la situación sobre la que pregunta el cartel original.

El artículo relevante para leer es más bien este:

blogs.msdn.com/b/ericlippert/archive/2009/12/10/…