c# - uso - ¿De qué manera la adición de un salto en un bucle while resuelve la ambigüedad de sobrecarga?
usar continue en c (2)
Dejando async
de esto para comenzar con ...
Con la ruptura, el final de la expresión lambda es alcanzable, por lo tanto, el tipo de retorno de la lambda debe ser void
.
Sin el corte, el final de la expresión lambda es inalcanzable, por lo que cualquier tipo de devolución sería válido. Por ejemplo, esto está bien:
Func<string> foo = () => { while(true); };
mientras que esto no es:
Func<string> foo = () => { while(true) { break; } };
Entonces, sin el break
, la expresión lambda sería convertible a cualquier tipo de delegado con un solo parámetro. Con el break
, la expresión lambda solo es convertible a un tipo de delegado con un solo parámetro y un tipo de retorno de void
.
Agregue la parte async
y el void
se void
o Task
, vs void
, Task
o Task<T>
para cualquier T
donde anteriormente podría tener cualquier tipo de devolución. Por ejemplo:
// Valid
Func<Task<string>> foo = async () => { while(true); };
// Invalid (it doesn''t actually return a string)
Func<Task<string>> foo = async () => { while(true) { break; } };
// Valid
Func<Task> foo = async () => { while(true) { break; } };
// Valid
Action foo = async () => { while(true) { break; } };
Considere este fragmento de Extensiones reactivas (ignore la practicidad de esto):
return Observable.Create<string>(async observable =>
{
while (true)
{
}
});
Esto no se compila con Reactive Extensions 2.2.5 (usando el paquete NuGet Rx-Main). Falla con:
Error 1 La llamada es ambigua entre los siguientes métodos o propiedades: ''System.Reactive.Linq.Observable.Create <string> (System.Func <System.IObserver <string>, System.Threading.Tasks.Task <System.Action> >) ''y'' System.Reactive.Linq.Observable.Create <string> (System.Func <System.IObserver <string>, System.Threading.Tasks.Task>) ''
Sin embargo, agregar un break
en cualquier lugar del ciclo while corrige el error de compilación:
return Observable.Create<string>(async observable =>
{
while (true)
{
break;
}
});
El problema se puede reproducir sin extensiones reactivas (más fácil si quieres probarlo sin tocar el Rx):
class Program
{
static void Main(string[] args)
{
Observable.Create<string>(async blah =>
{
while (true)
{
Console.WriteLine("foo.");
break; //Remove this and the compiler will break
}
});
}
}
public class Observable
{
public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task> subscribeAsync)
{
throw new Exception("Impl not important.");
}
public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task<Action>> subscribeAsync)
{
throw new Exception("Impl not important.");
}
}
public interface IObserver<T>
{
}
Haciendo caso omiso de la parte de Extensiones reactivas, ¿por qué añadir compilación ayuda al compilador de C # a resolver la ambigüedad? ¿Cómo se puede describir esto con las reglas de resolución de sobrecarga de la especificación C #?
Estoy usando la actualización 2 de Visual Studio 2013 dirigida a 4.5.1.
Es más fácil simplemente sacar async
así como las lambdas aquí, ya que enfatiza lo que está sucediendo. Ambos métodos son válidos y compilarán:
public static void Foo()
{
while (true) { }
}
public static Action Foo()
{
while (true) { }
}
Sin embargo, para estos dos métodos:
public static void Foo()
{
while (true) { break; }
}
public static Action Foo()
{
while (true) { break; }
}
La primera compila y la segunda no. Tiene una ruta de código que no devuelve un valor válido.
De hecho, while(true){}
(junto con throw new Exception();
) es una declaración interesante en el sentido de que es el cuerpo válido de un método con cualquier tipo de devolución.
Como el ciclo infinito es un candidato adecuado para ambas sobrecargas, y ninguna de las sobrecargas es "mejor", da como resultado un error de ambigüedad. La implementación de ciclo no infinito solo tiene un candidato adecuado en resolución de sobrecarga, por lo que se compila.
Por supuesto, para volver a poner async
en juego, en realidad es relevante de una manera aquí. Para los métodos async
, ambos siempre devuelven algo , ya sea una Task
o una Task<T>
. Los algoritmos de "mejora" para resolución de sobrecarga preferirán delegados que devuelven un valor sobre delegados void
cuando hay una lambda que podría coincidir, sin embargo en su caso ambas sobrecargas tienen delegados que devuelven un valor, el hecho de que para métodos async
regresen una Task
lugar de una Task<T>
es el equivalente conceptual de no devolver un valor que no está incorporado en ese algoritmo de mejora. Debido a esto, el equivalente no asincrónico no generaría un error de ambigüedad, aunque ambas sobrecargas son aplicables.
Por supuesto, vale la pena señalar que escribir un programa para determinar si un bloque arbitrario de código se completará es un problema famoso sin solución, sin embargo, mientras el compilador no puede evaluar correctamente si cada fragmento se completará, puede probar, en ciertos casos simples tales como este, que el código, de hecho, nunca se completará. Debido a esto, hay formas de escribir código que claramente (para usted y para mí) nunca se completarán, pero que el compilador considerará como posibles de completar.