Saltar al contenido

¿Cómo explicar este error de “la llamada es ambigua”?

José, miembro de nuestro equipo de trabajo, nos hizo el favor de redactar este tutorial porque controla a la perfección el tema.

Solución:

De acuerdo con la Especificación de C#, Invocaciones de métodos, las siguientes reglas se utilizan para considerar un método genérico F como candidato para la invocación del método:

  • El método tiene el mismo número de parámetros de tipo de método que se proporcionaron en la lista de argumentos de tipo,

    y

  • Una vez que los argumentos de tipo se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos en la lista de parámetros de
    F satisfacer sus restricciones (satisfaciendo restricciones), y la lista de parámetros de F es aplicable con respecto a A (Miembro de función aplicable). A – lista de argumentos opcional.

para expresión

Task.FromResult("foo").Map(x => $"hello x");

ambos métodos

public static T2 Map(this T1 x, Func f);
public static async Task Map(this Task x, Func f);

satisfacer estos requisitos:

  • ambos tienen dos parámetros de tipo;
  • sus variantes construidas

    // T2 Map(this T1 x, Func f)
    string       Ext.Map, string>(Task, Func, string>);
    
    // Task Map(this Task x, Func f)
    Task Ext.Map(Task, Func);
    

satisfacer las restricciones de tipo (porque no hay restricciones de tipo para Map métodos) y aplicable según argumentos opcionales (porque tampoco hay argumentos opcionales para Map métodos). Nota: para definir el tipo del segundo argumento (expresión lambda) se utiliza una inferencia de tipo.

Entonces, en este paso, el algoritmo considera ambas variantes como candidatas para la invocación del método. Para este caso, utiliza la resolución de sobrecarga para determinar qué candidato se adapta mejor a la invocación. Palabras de especificación:

El mejor método del conjunto de métodos candidatos se identifica utilizando las reglas de resolución de sobrecarga de Resolución de sobrecarga. Si no se puede identificar un solo mejor método, la invocación del método es ambigua y se produce un error de tiempo de vinculación. Al realizar la resolución de sobrecarga, los parámetros de un método genérico se consideran después de sustituir los argumentos de tipo (suministrados o inferidos) por los parámetros de tipo de método correspondientes.

Expresión

// I intentionally wrote it as static method invocation.
Ext.Map(Task.FromResult("foo"), x => $"hello x");

se puede reescribir de la siguiente manera usando variantes construidas del método Mapa:

Ext.Map, string>(Task.FromResult("foo"), (Task x) => $"hello x");
Ext.Map(Task.FromResult("foo"), (string x) => $"hello x");

La resolución de sobrecarga utiliza el algoritmo de miembro de mejor función para definir cuál de estos dos métodos se ajusta mejor a la invocación del método.

He leído este algoritmo varias veces y no he encontrado un lugar donde el algoritmo pueda definir el método. Exp.Map(Task, Func) como mejor método para la invocación del método considerado. En este caso (cuando no se puede definir un método mejor) se produce un error de tiempo de compilación.

Para resumir:

  • el algoritmo de invocación de métodos considera ambos métodos como candidatos;
  • El mejor algoritmo miembro de la función no puede definir un mejor método para invocar.

Otro enfoque para ayudar al compilador a elegir un mejor método (como lo hizo en sus otras soluciones):

// Call to: T2 Map(this T1 x, Func f);
var a = Task.FromResult("foo").Map( (string x) => $"hello x" );

// Call to: async Task Map(this Task x, Func f);
var b = Task.FromResult(1).Map( (Task x) => x.ToString() );

Ahora el primer tipo de argumento T1 se define explícitamente y no se produce una ambigüedad.

En la resolución de sobrecarga, el compilador deducirá argumentos de tipo si no se especifica.

En todos los casos de error, el tipo de entrada T1 en Fun es ambiguo. Por ejemplo:

Ambas cosas Tasky int tener ToString método, por lo que no hay forma de inferir si es tarea o int.

Sin embargo, si + se usa en expresión, está claro que el tipo de entrada es entero porque la tarea no admite + operador. .Length es la misma historia.

Esto también puede explicar otros errores.

ACTUALIZAR

La razón de pasar Task no hará que el compilador recoja el método con Task en la lista de argumentos es que el compilador necesita esforzarse para inferir T1 fuera de Task porque T1 no está directamente en la lista de argumentos del método.

Posible solución: Hacer Func<> para usar lo que existe en la lista de argumentos del método, por lo que el compilador requiere menos esfuerzo al inferir T1.

static class Extensions

    public static T2 Map(this T1 obj, Func func)
    
        return func(obj);
    

    public static T2 Map(this Task obj, Func, T2> func)
    
        return func(obj);
    

Uso:

// This calls Func
1.Map(x => x + 1);

// This calls Func, T2>
Task.FromResult(1).Map(async _=> (await _).ToString())

// This calls Func, T2>
Task.FromResult(1).Map(_=> 1)

// This calls Func, T2>.
// Cannot compile because Task does not have operator '+'. Good indication.
Task.FromResult(1).Map(x => x + 1)

Te mostramos reseñas y calificaciones

Si guardas algún problema y capacidad de mejorar nuestro ensayo te invitamos dejar una apostilla y con placer lo observaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *