Después de haber migrado mi proyecto de VS2013 a VS2015, el proyecto ya no se compila. Se produce un error de compilación en la siguiente declaración LINQ:
static void Main(string[] args)
{
decimal a, b;
IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
var result = (from v in array
where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
orderby decimal.Parse(v)
select v).ToArray();
}
El compilador devuelve un error:
Error CS0165 Uso de variable local no asignada 'b'
¿Qué causa este problema? ¿Es posible solucionarlo a través de una configuración de compilador?
b
después de asignarlo mediante unout
parámetro.out
argumentos. ¿TryParse
Eso devolvería un valor anulable (o equivalente)?where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= b
ve mucho mejordecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;
. He abierto un error Roslyn levantar esto.Respuestas:
Me parece un error del compilador. Al menos, lo hizo. Aunque las expresiones
decimal.TryParse(v, out a)
ydecimal.TryParse(v, out b)
se evalúan dinámicamente, esperaba que el compilador aún entendiera que para cuando lleguea <= b
, ambosa
yb
definitivamente están asignados. Incluso con las rarezas que se pueden encontrar en la escritura dinámica, esperaría evaluar soloa <= b
después de evaluar ambasTryParse
llamadas.Sin embargo, resulta que a través del operador y la conversión engañosa, es completamente factible tener una expresión
A && B && C
que evalúeA
yC
pero noB
, si es lo suficientemente astuto. Consulte el informe de errores de Roslyn para ver el ingenioso ejemplo de Neal Gafter.Hacer que funcione
dynamic
es aún más difícil: la semántica involucrada cuando los operandos son dinámicos es más difícil de describir, porque para realizar una resolución de sobrecarga, es necesario evaluar los operandos para averiguar qué tipos están involucrados, lo que puede ser contrario a la intuición. Sin embargo, de nuevo Neal ha llegado con un ejemplo que demuestra que se requiere que el error del compilador ... esto no es un error, es un error del arreglo . Enormes felicitaciones a Neal por demostrarlo.No, pero existen alternativas que evitan el error.
En primer lugar, puede evitar que sea dinámico: si sabe que solo usará cadenas, entonces podría usar
IEnumerable<string>
o darle a la variable de rangov
un tipo destring
(es decirfrom string v in array
). Esa sería mi opción preferida.Si realmente necesita mantenerlo dinámico, simplemente dé
b
un valor para comenzar con:Esto no hará ningún daño: sabemos que en realidad su evaluación dinámica no hará nada loco, por lo que aún terminará asignando un valor a
b
antes de usarlo, haciendo que el valor inicial sea irrelevante.Además, parece que agregar paréntesis también funciona:
Eso cambia el punto en el que se activan varias piezas de resolución de sobrecarga, y hace feliz al compilador.
Hay una cuestión que aún permanecen - reglas de la especificación de la asignación definitiva a la
&&
necesidad del operador a aclararse a estado que sólo se aplican cuando el&&
operador se está utilizando en su aplicación "regular" con dosbool
operandos. Intentaré asegurarme de que esto se solucione para el próximo estándar ECMA.fuente
IEnumerable<string>
o agregar corchetes funcionó para mí. Ahora el compilador se compila sin errores.decimal a, b = 0m;
podría eliminar el error, pero luegoa <= b
usaría siempre0m
, ya que el valor de salida aún no se ha calculado.decimal? TryParseDecimal(string txt)
puede ser una solución tambiénb
podría no asignarse"; Sé que es un razonamiento inválido pero explica por qué el paréntesis lo corrige ...Esto parece ser un error, o al menos una regresión, en el compilador de Roslyn. Se ha presentado el siguiente error para rastrearlo:
Mientras tanto, la excelente respuesta de Jon tiene un par de soluciones.
fuente
Como me educaron tan duro en el informe de errores, intentaré explicarlo yo mismo.
Imagine
T
es un tipo definido por el usuario con una conversión implícitabool
que alterna entrefalse
ytrue
, comenzando confalse
. Por lo que el compilador sabe, eldynamic
primer argumento del primero&&
podría evaluar ese tipo, por lo que tiene que ser pesimista.Si, entonces, deja que el código se compile, esto podría suceder:
&&
, hace lo siguiente:T
- implícitamente lo envía abool
.false
, así que no necesitamos evaluar el segundo argumento.&&
evaluación sea el primer argumento. (No, nofalse
, por alguna razón).&&
, hace lo siguiente:T
- implícitamente lanzarlo abool
.true
, así que evalúe el segundo argumento.b
no está asignado.En términos de especificaciones, en resumen, existen reglas especiales de "asignación definida" que nos permiten decir no solo si una variable está "definitivamente asignada" o "no definitivamente asignada", sino también si está "definitivamente asignada después de la
false
instrucción" o "definitivamente asignado despuéstrue
declaración ".Estos existen para que al tratar con
&&
y||
(y!
y??
y?:
) el compilador pueda examinar si se pueden asignar variables en ramas particulares de una expresión booleana compleja.Sin embargo, estos solo funcionan mientras los tipos de expresiones permanecen booleanos . Cuando parte de la expresión es
dynamic
(o un tipo estático no booleano) ya no podemos decir de manera confiable que la expresión estrue
ofalse
; la próxima vez que la usemosbool
para decidir qué rama tomar, es posible que haya cambiado de opinión.Actualización: esto ahora se ha resuelto y documentado :
fuente
out
a un método que tengaref
. Felizmente lo hará, y hará asignadas variables, sin cambiar el valor.false
/true
en lugar del operador de conversión implícito? Localmente, llamaráimplicit operator bool
al primer argumento, luego invocará el segundo operando, llamaráoperator false
al primer operando, seguido de nuevoimplicit operator bool
al primer operando . Esto no tiene sentido para mí, el primer operando debería esencialmente reducirse a un booleano una vez, ¿no?dynamic
encadenado&&
? Lo he visto básicamente ir (1) evaluar el primer argumento (2) usar la conversión implícita para ver si puedo cortocircuitar (3) No puedo, así que evalúe el segundo argumento (4) ahora conozco ambos tipos, yo Puedo ver que lo mejor&&
es un&
operador de llamada definido por el usuario (5)false
en el primer argumento para ver si puedo cortocircuitar (6) Puedo (porquefalse
yimplicit bool
no estoy de acuerdo), por lo que el resultado es el primer argumento ... y luego el siguiente&&
, (7) uso de conversión implícita para ver si puedo cortocircuitar (de nuevo).Esto no es un error. Consulte https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 para ver un ejemplo de cómo una expresión dinámica de este formulario puede dejar una variable sin asignar.
fuente