params sobrecarga ambigüedad aparente-todavía compila y obras?


Acabamos de encontrar estos en nuestro código:

public static class ObjectContextExtensions
{

    public static T Find<T>(this ObjectSet<T> set, int id, params Expression<Func<T, object>>[] includes) where T : class
    {
        ...
    }

    public static T Find<T>(this ObjectSet<T> set, int id, params string[] includes) where T : class
    {
       ...
    }
}

Como puedes ver, estos tienen la misma firma excepto para el params.

Y se están utilizando de varias maneras, una de ellas:

DBContext.Users.Find(userid.Value); //userid being an int? (Nullable<int>)

Que, por extraño que parezca, se resuelve a la primera sobrecarga.

P1: ¿Por qué esto no produce un error de compilación?

P2: ¿Por qué el compilador de C# resuelve la llamada anterior al primer método?

Editar: Solo para aclarar, esto es C# 4.0,. Net 4.0, Visual Studio 2010.

Author: svick, 2014-01-07

3 answers

Esto es claramente un error en la resolución de sobrecarga.

Se reproduce en C# 5 y C# 3 pero no en Roslyn; no recuerdo si decidimos tomar deliberadamente el cambio de ruptura o si esto es un accidente. (No tengo C# 4 en mi máquina en este momento, pero si se reproduce en 3 y 5, entonces lo hará en 4 también es casi seguro.)

Lo he traído a la atención de mis antiguos colegas en el equipo de Roslyn. Si me contestan con algo interesante actualizaré esto respuesta.

Ya no tengo acceso al C# 3 / 4 / 5 código fuente No puedo decir cuál es la causa del error. Considere reportarlo en connect.microsoft.com.

Aquí hay una reproducción muy simplificada:

class P
{
    static void M(params System.Collections.Generic.List<string>[] p) {}
    static void M(params int[] p)  {}
    static void Main()
    {
        M();
    }
}

Parece tener algo que ver con la genericidad del tipo de elemento. Curiosamente, como Chris señala en su respuesta, el compilador elige el más genérico! Habría esperado que el error fuera al revés, y elegir el menos genérico una.

El error es, por cierto, probablemente mi culpa, ya que hice una buena parte del trabajo en el algoritmo de resolución de sobrecarga en C# 3. Disculpas por el error.

ACTUALIZAR

Mis espías en el equipo de Roslyn me dicen que este es un error conocido de larga data en la resolución de sobrecarga. Había una regla de desempate implementada que nunca fue documentada o justificada que decía que el tipo con la aridad genérica mayor era el mejor tipo. Esta es una regla extraña con no justificación, pero nunca ha sido eliminado del producto. El equipo de Roslyn decidió hace algún tiempo tomar el cambio de ruptura y corregir la resolución de sobrecarga para que produzca un error en este caso. (No recuerdo esa decisión, pero hicimos un lote de decisiones sobre este tipo de cosas!)

 28
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-07 21:08:35

En IDEONE el compilador produce correctamente un error . Y debería ser un error, si analizas el algoritmo de resolución paso a paso:

1) Se construye el conjunto de métodos candidatos para la invocación del método. Comenzando con el conjunto de métodos asociados con M, que fueron encontrados por una búsqueda de miembros anterior [...] La reducción del conjunto consiste en aplicar las siguientes reglas a cada método T. N en el conjunto, donde T es el tipo en el que el método N es declarado:

Para simplificar, podemos deducir aquí que el conjunto de métodos aquí contiene ambos métodos.

Luego procede la reducción:

2) Si N no es aplicable con respecto a A ( Sección 7.4.2.1), entonces N se elimina del conjunto.

Ambos métodos son aplicables con respecto a Reglas aplicables del miembro de la función en su forma expandida :

La forma expandida se construye por sustitución de la matriz de parámetros en la declaración de miembro de función por cero o más parámetros de valor del tipo de elemento de la matriz de parámetros de modo que el número de argumentos en la lista de argumentos A coincida con el número total de parámetros. Si A tiene menos argumentos que el número de parámetros fijos en la declaración de miembro de función, la forma expandida del miembro de función no se puede construir y, por lo tanto, no es aplicable.

Esta regla deja ambos métodos en el conjunto de reducción.

Los experimentos (cambiando el tipo de parámetro id a floaten uno o ambos métodos) confirman que ambas funciones permanecen en el conjunto candidato, y son discriminadas aún más por las reglas de comparación de conversiones implícitas .

Esto sugiere que el algoritmo anterior funciona bien en términos de crear el conjunto candidato, y no depende de algún ordenamiento interno del método. Dado que lo único que discrimina los métodos más allá son Reglas de resolución de sobrecarga esto parece ser un error , porque:

El mejor miembro de función es el miembro de función que es mejor que todos los demás miembros de función con respecto a la lista de argumentos dada, siempre que cada miembro de función se compare con todos los demás miembros de función utilizando las reglas en Sección 7.4.2.2.

Y claramente ninguno de estos métodos es mejor que otros, porque aquí no existen conversiones implícitas.

 7
Author: BartoszKP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-07 17:41:56

Esta no es una respuesta completa ya que explica las diferencias pero no por qué. Realmente necesita s especificación de referencia para la integridad. Sin embargo, no quería que la investigación que he hecho se pierda en los comentarios, así que estoy publicando como respuesta.

La diferencia entre las dos sobrecargas radica en el hecho de que los parámetros para uno es genérico y el otro no. El compilador parece decidir que el tipo genérico es más cercano que el no genérico.

Es decir, si el tipo Expression<...> se cambió a un int el compilador se quejaría de ambigüedad. Similar si los tipos son genéricos entonces se queja de ambigüedad.

El siguiente fragmento mostrará este comportamiento de forma más sencilla:

void Main()
{
    TestMethod();
}

public void TestMethod(params string[] args)
{
    Console.WriteLine("NonGeneric");
}

public void TestMethod(params List<string>[] args)
{
    Console.WriteLine("Generic");
}

Esto imprimirá "Genérico".

 6
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-07 17:02:59