2011-12-14 22 views
45

Estoy teniendo mucha diversión 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.¿Por qué C# no infiere mis tipos genéricos?

¿Alguien puede 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> 
{ 
} 

un código que no se 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 (que no deja mucho a la imaginación):

Los argumentos de tipo de método IQueryProcessor.Process (TQuery) no puede deducirse del uso. Intente especificar explícitamente los argumentos de tipo .

La razón creo que C# debe ser capaz de inferir que es por lo siguiente:

  1. Puedo proporcionar un objeto que implementa IQuery<TResult>.
  2. Que solo la versión IQuery<TResult> que implementa el tipo es IQuery<string> y por lo tanto TResult debe ser string.
  3. Con esta información el compilador tiene TResult y TQuery.

SOLUCIÓN

Para mí la mejor solución era cambiar la interfaz IQueryProcessor y el uso de 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 en un parámetro IQuery<TResult>. De esta manera 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.

+0

¿Cuál es el mensaje de error? –

+4

El compilador no sabe qué 'TResult' usar. En el momento en que necesita tomar la decisión, no sabe que la va a poner en una 'cadena' (así que tal vez debería inferir 'cadena').E incluso si lo supiera, también podría ser legalmente 'Process ' donde 'customClass' es cualquier clase derivada de' string'. Ver también: [La inferencia del tipo de devolución no funciona en los grupos miembros] (http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference- does-not-work-on-member-groups.aspx). –

+0

Y @Raymond dice que no es un tipo de .NET ... pshaw –

Respuesta

36

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

Un montón de gente entonces se han remitido 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 es a la vez fuera de fecha e irrelevante para la cuestión. 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 devuelve la inferencia del tipo desde los argumentos del grupo de métodos a los parámetros formales del delegado genérico. Esa no es la situación sobre la que pregunta el cartel original.

El artículo pertinente de la mina de leer es más bien ésta:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

+2

Gracias por su respuesta (LA respuesta). ¿Es posible que el compilador C# haga una inferencia de restricción en el futuro, o eso está fuera de discusión? – Steven

+0

Diría que tener tal inferencia está fuera de discusión. Eric afirma en un comentario: "Esta es una opción deliberada de diseño de acuerdo con los principios de larga data del diseño del lenguaje C#". – Brian

+0

@Steven: Brian está en lo cierto; no tenemos ninguna intención de agregar restricciones a la lista de hechos que se utilizan para hacer inferencias. –

14

C# no deducirá tipos genéricos según 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 obtener más información, consulte Eric Lippert's post on the subject.

7

No utiliza 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 posible TResult que se puede utilizar con un parámetro SomeQuery, no se verá esto.

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.

+0

¡esta es la única respuesta que explica lógicamente la situación! aún no hay votos. – nawfal

4

La especificación establece esto con bastante claridad:

Sección 7.4.2 Inferencia de tipos

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

Tr M (T1 x1 ... Tm XM)

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

Como puede ver, el tipo de retorno 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 asume que usted quería string como el argumento TResult, ni tampoco. Imagine un TResult derivado de una cadena. Ambos serían válidos, ¿para qué elegir? Mejor ser explícito.

+0

No es solo el tipo de retorno lo que haría que la inferencia del tipo sea exitosa. El tipo implementa 'IQuery '. – Steven

+3

Observe también que la primera instrucción resaltada es un error; esta línea nunca fue actualizada cuando agregamos argumentos nombrados y opcionales, y también es inconsistente con la inferencia de tipo en los métodos "params" aplicables en su forma expandida. Una versión futura de la especificación corregirá la línea para tenerlos en cuenta. –

Cuestiones relacionadas