Me topé con un comportamiento que no esperaba y del que no puedo encontrar la documentación. Tengo una función con 2 parámetros, uno de ellos tiene un valor predeterminado. Cuando se llama a la función dentro de un Select, por alguna razón toma el valor al que se llama y lo aplica para ambos parámetros. Ejemplo:
Tener la función:
private static Directions PrintDir(Directions dir, int distance = 1) { Console.WriteLine($"Direction {dir}, distance {distance}"); return dir; }Si se llama por sí solo, se comporta como se esperaba:
using System; using System.Linq; public class Program { enum Directions { Left, Up, Right, Down }; public static void Main() { PrintDir(Directions.Left); PrintDir(Directions.Up); PrintDir(Directions.Right); PrintDir(Directions.Down); } }huellas dactilares:
Direction Left, distance 1 Direction Up, distance 1 Direction Right, distance 1 Direction Down, distance 1sin embargo, cuando se llama dentro de la expresión LINQ:
using System; using System.Linq; public class Program { enum Directions { Left, Up, Right, Down }; public static void Main() { var dirs = Enum.GetValues(typeof(Directions)) .Cast<Directions>() .Select(PrintDir) .ToList(); } }Yo obtengo:
Direction Left, distance 0 Direction Up, distance 1 Direction Right, distance 2 Direction Down, distance 3 Entonces, parece que el valor de Indicaciones se aplicó correctamente como el parámetro dir , pero también se convirtió a int y se aplicó en lugar del valor predeterminado como el segundo parámetro de distance .
¿Por qué pasó esto? ¿Cuáles son las reglas para las funciones dentro de Seleccionar?
Hay una sobrecarga de Select que también contiene el índice. Si usó lambdas, su código se vería así:
Select((item,index) => PrintDir(item, index)) Si quieres evitarlo, llama a tu Select así:
Select(item => PrintDir(item))Para agregar un poco más a la respuesta de SomeBody: si escribe PrintDir(Directions.Left) , el compilador compila el código PrintDir(Directions.Left, 1) -- toma el valor predeterminado y lo inserta en la llamada al método por usted.
Ahora, si intenta crear un delegado directamente desde el método, por ejemplo:
var f = new Func<Directions, Directions>(PrintDir) no hay ningún lugar para que el compilador inserte este valor predeterminado, por lo que no se compilará. El único tipo de delegado al que puede asignar el método directamente es uno que toma todos los parámetros, por ejemplo, Func<Directions, int, Directions> .
Sin embargo, si escribes:
var f = new Func<Directions, Directions>(x => PrintDir(x))El compilador puede convertir esto en:
var f = new Func<Directions, Directions>(x => PrintDir(x, 1))Cuando escribes:
.Select(PrintDir)en realidad estás escribiendo abreviatura para:
.Select(new SomeDelegateType(PrintDir)) donde SomeDelegateType es un tipo de delegado adecuado. El compilador busca en todas las sobrecargas de Select buscando una que acepte un tipo de delegado que compilará.
Hay una sobrecarga de Select que acepta Func<Direction, Direction> , por lo que intenta esto:
.Select(new Func<Direction, Direction>(PrintDir)) ... pero eso no compila, por lo que intenta la sobrecarga que toma el índice int :
.Select(new Func<Direction, int, Direction>(PrintDir))y eso compila, por lo que el compilador lo elige.
(De hecho, las cosas son un poco más complejas, ya que el compilador infiere el tipo de retorno (supuse que es la Direction anterior), y también intenta encontrar la mejor coincidencia, pero esa es la esencia).