En muchas ocasiones, cuando trabajas con la plataforma .NET debes forzosamente pasar parámetros o recibir resultados de llamadas que en realidad no necesitas para nada.

Un caso típico es cuando, por ejemplo, obtienes una cadena de texto introducida por el usuario y quieres validar que se trata de un número entero válido (o una fecha, etc...) y "parsearlo" como tal en una variable. Lo habitual que harás es algo similar a esto:

//Simulamos algo introducido por el usuario
string valorUsuario = "Lo que sea";
//Vamos a intentar convertirlo en entero
int valorEntero = 0;
bool esEntero = int.TryParse(valorUsuario, out valorEntero);
Console.WriteLine(valorEntero);

En este caso verías en la consola el 0 ya que la cadena no es un entero válido y por lo tanto no se asigna valor alguno en el parámetro de salida valorEntero, quedando con su valor inicial.

Perfecto. En este caso concreto lo único que nos interesa es el valor "parseado", pero el resultado de la función nos da exactamente igual, ya que no necesitamos saber si se ha interpretado como entero con éxito o no. Así que podríamos escribir simplemente esto:

//Simulamos algo introducido por el usuario
string valorUsuario = "Lo que sea";
//Vamos a intentar convertirlo en entero
int valorEntero = 0;
int.TryParse(valorUsuario, out valorEntero);
Console.WriteLine(valorEntero);

Le hemos quitado la variable que recoge el resultado. Funciona igual y no presente problemas aparentes. Pero es algo un tanto feo ya que no sabremos si hemos obviado el resultado o simplemente nos hemos despistado, y además si pasamos un Linter al código (como el que trae integrado el propio Visual Studio) tendremos una advertencia por si acaso.

Aquí es donde entra en juego una característica que apareció con C# 7 y que está pensada para este y otros casos similares: los descartes.

En C# 7 o posterior el símbolo _ (el guion bajo) tiene el significado explícito de que nos interesa descartar algún resultado de una llamada que estemos haciendo.

Así, lo anterior podríamos escribirlo como:

//Simulamos algo introducido por el usuario
string valorUsuario = "Lo que sea";
//Vamos a intentar convertirlo en entero
int valorEntero = 0;
_ = int.TryParse(valorUsuario, out valorEntero);
Console.WriteLine(valorEntero);

Usando ese guión bajo como si fuera una variable (que no hemos declarado en ningún lado) le indica al compilador que queremos hacer caso omiso del resultado devuelto por el método al que estamos llamando. Es equivalente al fragmento anterior en el que no hacíamos las asignación, pero tiene la ventaja de ser más explícito, ya que estamos dejando claro para cualquier que lea el código (incluyéndonos a nosotros mismos dentro de unos meses) que nuestra intención era sin duda no usar ese valor devuelto, y no ha sido un despiste.

De hecho podemos utilizarlo en otros lados, por ejemplo en el parámetro de salida. Si en el ejemplo anterior no nos interesa parsear la cadena sino simplemente saber si representa a un entero válido, en condiciones normales escribiríamos el código del principio pero no usaríamos para nada la variable "out" llamada valorEntero. Podemos ser más explícitos al hacerlo y de paso ahorrarnos una variable en memoria (por pequeña que sea) escribiendo esto:

string valorUsuario = "Lo que sea";
//Vamos a intentar convertirlo en entero
bool esEntero = int.TryParse(valorUsuario, out _);
Console.WriteLine(esEntero);

Fíjate en que ahora ni siquiera declaramos la variable con el resultado, pues no pensamos usarla, y le pasamos el guión bajo como segundo parámetro a TryParse. Esto le indica al compilador con total claridad que no nos interesa ese valor para nada, lo cual se traduce en que la variable ni siquiera se declara.

Nota: Por cierto, no debemos olvidar que el guión bajo es una letra válida para usar como nombre de una variable en C#, así que nadie nos impide declarar una variable estilo: int _ = 1; y luego poder usarla en el código. Mientras en el ámbito actual esté definida una variable con ese nombre no tendrá efecto como símbolo de descarte. De todos modos hay que tener mal carácter para declarar una variable simplemente con ese nombre 😛

También podemos usar el descarte en varios sitios a la vez. Es decir, podemos poner varios _ en el mismo ámbito sin problema, ya que no es como si declarásemos varias veces la misma variable.

Un ejemplo habitual es cuando devolvemos tuplas como resultados de una función y hacemos su decostrucción para obtener los valores por separado. Por ejemplo, supongamos que tenemos una función que va a la base de datos (o a un servicio REST o a donde sea) para obtener los datos de un alumno a partir de su identificador único. En lugar de ser personas decentes y devolver una clase o una estructura devolvemos una tupla (no soy muy fan, pero h de reconocer que, a veces, para cosas sencillas son muy útiles). Algo como esto:

public (string, string, int, double) GetDatosAlumno(int idAlumno)
{
  //Se supone que vamos a la base de datos y obtenemos
  //estos valores para devolverlos
  return ("Antonio", "Español", idAlumno*3, 8.5);
}

Vale, si ahora queremos llamar a esta función y recibir estos resultados la llamada sería algo así:

(string, string, int, double) datos = GetDatosAlumno(7);
Console.WriteLine(datos.Item1);
Console.WriteLine(datos.Item3);

que es feo porque debemos usar las propiedades Item1, Item2... de la tupla. O bien mejor así:

var (nombre, apellidos, edad, nota) = GetDatosAlumno(7);
Console.WriteLine(nombre);
Console.WriteLine(edad);

Más bonito y sobre todo claro sobre lo que pretendemos hacer, pero que nos hace declarar una variable por cada valor devuelto donde se desconstruye la tupla resultante de llamar a la función.

Pero en este caso solo queremos usar el nombre y la edad. Los otros dos resultados no nos importan y no queremos declarar las correspondientes variables para nada. Solución: descartarlas con el guión bajo:

var (nombre, _, edad, _) = GetDatosAlumno(7);
Console.WriteLine(nombre);
Console.WriteLine(edad);

De este modo descartamos directamente dos de los resultados en la decostrucción de la tupla y usamos las que nos interesan.

Nota: no te olvides de que, para que esto funcione, debes estar usando C# 7 o posterior, es decir, .NET 4.6.2 o posterior y/o Visual Studio 2017 o posterior, asi que ojo.

Bueno, la verdad es que no es de las características más espectaculares de C#, eso es hay que reconocerlo. Pero merece la pena utilizarla para tratar de crear código más mantenible. Y desde luego es interesante conocer su existencia.

¡Espero que te resulte útil!

Escrito por un humano, no por una IA