C # 7: subrayado (_) y asterisco (*) en la variable de salida

79

Estaba leyendo sobre las nuevas características de las variables de salida en C # 7 aquí . Tengo dos preguntas:

  1. Dice

    También permitimos "descartes" como parámetros de salida, en forma de a _, para permitirle ignorar los parámetros que no le interesan:

    p.GetCoordinates(out var x, out _); // I only care about x
    

    P: Supongo que esto es solo una información y no una característica nueva de C # 7 porque también podemos hacerlo en versiones anteriores a C # 7.0:

    var _;
    if (Int.TryParse(str, out _))
    ...
    

    o me estoy perdiendo algo aquí?

  2. Mi código da un error cuando hago lo mencionado en el mismo blog:

    ~Person() => names.TryRemove(id, out *);
    

    *no es un identificador válido. ¿Un descuido de Mads Torgersen, supongo?

Nikhil Agrawal
fuente
15
in out _ _no es una variable, no la declaras y no puedes usarla por su nombre. En int _eso hay una variable.
Evk
9
Parece que el comodín de asterisco no llegó a la versión final de C # 7.
Michael Stum
3
@NikhilAgrawal pero esa es una sintaxis diferente. En su pregunta usa out _, sin var. De varhecho, es lo mismo que antes.
Evk
2
@NikhilAgrawal que de hecho se compila por alguna extraña razón. Sin embargo, si mira el código descompilado, esta asignación simplemente no está allí, completamente eliminada. Y si hace algo como Console.WriteLine(_), esto no compilará alegando que no existe tal variable. Bastante raro. Aún más: si hace algo como _ = SomeMethodCall(), esto será reemplazado por solo SomeMethodCall()en código compilado. Entonces, después de todo, todavía no puedes usar esa variable en ningún sentido significativo.
Evk

Respuestas:

111

Los descartes , en C # 7, se pueden usar siempre que se declare una variable, para, como sugiere el nombre, descartar el resultado. Entonces, un descarte se puede usar sin variables:

p.GetCoordinates(out var x, out _);

y se puede usar para descartar el resultado de una expresión:

_ = 42;

En el ejemplo,

p.GetCoordinates(out var x, out _);
_ = 42;

No se _está introduciendo ninguna variable`` . Solo hay dos casos en los que se utiliza un descarte.

Sin embargo, si _existe un identificador en el alcance, los descartes no se pueden utilizar:

var _ = 42;
_ = "hello"; // error - a string cannot explicitly convert from string to int

La excepción a esto es cuando _se utiliza una variable como variable de salida. En este caso, el compilador ignora el tipo o vary lo trata como un descarte:

if (p.GetCoordinates(out double x, out double _))
{
    _ = "hello"; // works fine.
    Console.WriteLine(_); // error: _ doesn't exist in this context.
}

Tenga en cuenta que esto solo ocurre si, en este caso, se usa out var _o out double _. Solo use out _y luego se trata como una referencia a una variable existente _, si está dentro del alcance, por ejemplo:

string _;
int.TryParse("1", out _); // complains _ is of the wrong type

Finalmente, la *notación se propuso al principio de las discusiones sobre los descartes, pero se abandonó _debido a que esta última es una notación más utilizada en otros idiomas .

David Arno
fuente
Creo que te refieres a '... _debido a que este último es un ...'
martijnn2008
@ martijnn2008, bien visto. Gracias.
David Arno
1
Supongo que esto está implícito, pero el punto del descarte es que su valor potencial nunca se almacena realmente.
Sinjai
Decir que _ = 42"descartar [s] el resultado de la expresión" es engañoso, porque en _ = 42sí mismo es una expresión con el valor 42, por lo que no hay un descarte real. Todavía hay una diferencia porque _ = 42;también es una declaración, mientras 42;que no lo es, lo que importa en algunos contextos.
Jeroen Mostert
1
La redacción está bien, es solo que _ = 42no muestra cuál es el punto de este descarte, es decir, cuando necesitaría "no almacenar" una expresión, pero evaluarla de todos modos, ya que generalmente puede evaluar un (no trivial , útil) expresión muy bien sin almacenarla. Yo mismo no puedo pensar inmediatamente en un ejemplo útil (y no sé si hay uno, o si esto es solo el resultado de la coherencia en la gramática).
Jeroen Mostert
30

Otro ejemplo del operador de descarte _en C # 7 es hacer coincidir el patrón con una variable de tipo objecten una switchdeclaración, que se agregó recientemente en C # 7:

Código:

static void Main(string[] args)
{
    object x = 6.4; 
    switch (x)
    {
        case string _:
            Console.WriteLine("it is string");
            break;
        case double _:
            Console.WriteLine("it is double");
            break;
        case int _:
            Console.WriteLine("it is int");
            break;
        default:
            Console.WriteLine("it is Unknown type");
            break;
    }

    // end of main method
}

Este código coincidirá con el tipo y descartará la variable pasada al case ... _.

Progs cibernéticos
fuente
14

Para mas curiosos

Considere el siguiente fragmento

static void Main(string[] args)
{
    //....
    int a;
    int b;

    Test(out a, out b);
    Test(out _, out _);    
    //....
}

private static void Test(out int a, out int b)
{
    //...
}

Esto es lo que está pasando:

...

13:             int  a;
14:             int  b;
15: 
16:             Test(out a, out b);
02340473  lea         ecx,[ebp-40h]  
02340476  lea         edx,[ebp-44h]  
02340479  call        02340040  
0234047E  nop  
    17:             Test(out _, out _);
0234047F  lea         ecx,[ebp-48h]  
02340482  lea         edx,[ebp-4Ch]  
02340485  call        02340040  
0234048A  nop 

...

Como puede ver detrás de escena, las dos llamadas hacen lo mismo.

Como señaló @ Servé Laurijssen, lo bueno es que no tiene que declarar previamente las variables, lo cual es útil si no está interesado en algunos valores.

Sid
fuente
3
El IL tiene que ser el mismo porque la función a la que está llamando aún requiere los espacios para las variables de salida. Es solo que usar la nueva sintaxis de descarte le permite al compilador hacer más suposiciones sobre la variable local (o más bien la falta de), lo que le permite usarla de manera más eficiente (al menos en teoría; no sé si ya hay optimizaciones) en el compilador a partir de ahora).
empuje el
9

Respecto a la primera pregunta

Supongo que esto es solo una información y no una característica nueva de C # 7 porque también podemos hacerlo en versiones anteriores a C # 7.0.

var _;
if (Int.TryParse(str, out _))
    // ...

La novedad es que ya no tienes que declarar _dentro o fuera de la expresión y solo puedes escribir

int.TryParse(s, out _);

Intente hacer este trazador de líneas previo a C # 7:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}
Sirve a Laurijssen
fuente
7
Para agregar: el guión bajo funciona muy bien para métodos con múltiples parámetros de salida, por ejemplo, SomeMethod(out _, out _, out three)tiene 3 parámetros de salida, pero estoy desechando los dos primeros sin tener que crear variables como, unused1, unused2etc.
Michael Stum
@MichaelStum: ¿Qué está pasando aquí? if (SomeMethod(out _, out _, out _)) _ = 5; ¿A qué _se refiere?
Nikhil Agrawal
4
@NikhilAgrawal No habría una _variable en absoluto, incluso si usara out var _. Parece que el guión bajo tiene mayúsculas especiales para descartar el resultado.
Michael Stum
0

En C # 7.0 (Visual Studio 2017 alrededor de marzo de 2017), los descartes se admiten en las asignaciones en los siguientes contextos:


Otras notas útiles

  • los descartes pueden reducir las asignaciones de memoria. Debido a que dejan clara la intención de su código, mejoran su legibilidad y facilidad de mantenimiento.
  • Tenga en cuenta que _ también es un identificador válido. Cuando se usa fuera de un contexto admitido

Ejemplo simple: aquí no queremos usar el primer y segundo parámetro y solo necesitamos el tercer parámetro

(_, _, area) = city.GetCityInformation(cityName);

Ejemplo avanzado en caso de interruptor que utilizó también la coincidencia de patrones de caso de interruptor moderno ( fuente )

switch (exception)                {
case ExceptionCustom exceptionCustom:       
        //do something unique
        //...
    break;
case OperationCanceledException _:
    //do something else here and we can also cast it 
    //...
    break;
default:
    logger?.Error(exception.Message, exception);
    //..
    break;

}

Iman
fuente
0

P: ... también podemos hacerlo en versiones anteriores a C # 7.0:

var _;
if (Int.TryParse(str, out _))

o me estoy perdiendo algo aquí?

Eso no es lo mismo.
Tu código está realizando una asignación.

En C # 7.0 _ no es una variable, le dice al compilador que descarte el valor
(a menos que haya declarado _ como una variable ... si lo hace, la variable se usa en lugar del símbolo de descarte)

Ejemplo: puede usar _ como una picadura y un int en la misma línea de código :

string a; 
int b;
Test(out a, out b);
Test(out _, out _);

//...

void Test(out string a, out int b)
{
   //...
}
J. Chris Compton
fuente