C#7:在Out变量中使用下划线(_)和星号(*)


79

我在这里阅读有关C#7中新的可变功能的信息。我有两个问题:

  1. 它说

    我们还允许以“”形式将“丢弃”作为输出参数_,以使您忽略不需要的参数:

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

    问:我想这只是C#7的一个信息而不是一项新功能,因为我们也可以在C#7.0之前的版本中这样做:

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

    还是我在这里想念东西?

  2. 当我在同一博客中提到时,我的代码给出了一个错误:

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

    *不是有效的标识符。我猜是Mads Torgersen的疏忽吗?


15
inout _ _不是变量,您不声明它,也不能按名称使用它。在int _那是一个变量。
Evk

9
星号通配符似乎没有加入到C#7的最终版本中。
Michael Stum

3
@NikhilAgrawal,但这是不同的语法。在您的问题中out _,不使用var。有了var它确实和以前一样。
Evk

2
@NikhilAgrawal确实由于某种奇怪的原因而进行编译。但是,如果查看反编译的代码,则该分配不存在,已被完全删除。而且,如果您执行类似的操作Console.WriteLine(_)-则不会编译为声称没有此类变量。很奇怪。更重要的是:如果您执行类似的操作_ = SomeMethodCall()-它将仅由已SomeMethodCall()编译的代码替换。因此,毕竟您仍然无法真正以任何有意义的方式使用该变量。
Evk

Answers:


111

C#7中的Discards可以在声明变量的任何地方使用,以顾名思义,可以丢弃结果。因此,可以将out与变量一起使用:

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

它可以用来丢弃表达式结果:

_ = 42;

在这个例子中

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

没有_引入任何变量。只有两种情况使用了丢弃。

但是,如果_范围中存在标识符,则不能使用丢弃:

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

例外是将_变量用作输出变量时。在这种情况下,编译器将忽略该类型或var将其视为丢弃:

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

请注意,这种情况仅在使用out var _或的情况下发生out double _。只需使用out _,然后将其视为对现有变量的引用_,如果它在范围内,例如:

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

最后,该*符号是在有关丢弃的讨论中提早提出的,_由于后者是其他语言中更常用的符号因此被放弃使用


我认为您的意思是“ ..._由于后者是一个...”
martijnn

@ martijnn2008,发现得很好。谢谢。
David Arno

1
我认为这是隐含的,但是丢弃的要点在于它的可能值从未真正存储过?
Sinjai'7

声明_ = 42“丢弃表达式结果”具有误导性,因为_ = 42它本身就是一个带有value的表达式42,因此没有进行实际的丢弃。仍然存在差异,因为_ = 42;还有一条陈述,而42;没有,这在某些情况下很重要。
Jeroen Mostert '18

1
措词很好,只是_ = 42无法显示此丢弃的含义是什么-即,当您需要“不存储”表达式但无论如何都要对其求值时,请参见通常如何求一个(非平凡的)表达式。 ,很有用),无需存储它就可以了。我自己无法立即想到一个有用的示例(而且我不知道是否有一个示例,或者这仅仅是语法一致性的结果)。
Jeroen Mostert '18

30

_C#7中Discard Operator的另一个示例是在语句中对类型为变量的模式进行模式匹配,最近在C#7中添加了该变量:objectswitch

码:

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
}

此代码将匹配类型,并丢弃传递给的变量case ... _


13

对于更多的好奇

考虑以下片段

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)
{
    //...
}

这是正在发生的事情:

...

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 

...

正如您在幕后看到的那样,这两个调用在做同样的事情。

正如@ServéLaurijssen指出的那样,很酷的事情是,您不必预先声明变量,如果您对某些值不感兴趣的话,这很方便。


3
IL必须相同,因为您调用的函数仍然需要out变量的插槽。只是使用新的丢弃语法允许编译器对局部变量(或缺少局部变量)进行进一步的假设,从而使其能够更有效地使用它(至少在理论上;我不知道是否已经有任何优化方法)在目前的编译器中)。

9

关于第一个问题

我想这只是C#7的信息而不是新功能,因为我们也可以在C#7.0之前的版本中这样做。

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

新颖之处在于,您不必_在表达式的内部或外部进行声明,只需键入

int.TryParse(s, out _);

尝试在C#7之前执行此操作:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}

7
补充:下划线的作品真的很好用多出来的参数,例如使用的方法,SomeMethod(out _, out _, out three)有3个输出参数,但我扔掉了前两个,而无需创建变量一样unused1, unused2
迈克尔葡萄汁

@MichaelStum:这是怎么回事?if (SomeMethod(out _, out _, out _)) _ = 5; _是指什么?
Nikhil Agrawal

4
@NikhilAgrawal_即使使用,也不会有任何变量out var _。似乎下划线是特殊情况下丢弃结果。
Michael Stum

0

在C#7.0(2017年3月左右的Visual Studio 2017)中,在以下情况下的分配中支持丢弃:


其他有用的笔记

  • 丢弃可以减少内存分配。因为它们使您的代码意图清晰明了,所以它们提高了代码的可读性和可维护性
  • 请注意,_也是有效的标识符。在受支持的上下文之外使用时

简单的例子:这里我们不想使用第一和第二参数,只需要第三参数

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

开关盒的高级示例,它也使用了现代的开关盒模式匹配(

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;

}


0

问:...我们也可以在C#7.0之前的版本中这样做:

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

还是我在这里想念东西?

那不是同一回事。
您的代码正在分配。

在C#7.0中,_不是变量,它告诉编译器放弃该值
除非已将_声明为变量...如果这样做,则使用变量而不是丢弃符​​号)

示例:您可以在同一行代码中将_用作字符串和整数:

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

//...

void Test(out string a, out int b)
{
   //...
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.