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

94
我正在阅读有关C#7中新的输出变量功能的文章,可以在这里找到。我有两个问题:
  1. 它说:

    我们也允许将"discards"作为输出参数,形式为 _,让您忽略您不关心的输出参数:

    p.GetCoordinates(out var x, out _); // I only care about x
    
    问:我想这只是一条信息而不是C#7的新功能,因为在之前的C# 7.0版本中,我们也可以这样做。
    var _;
    if (Int.TryParse(str, out _))
    ...
    
  2. 或者我在这里漏掉了什么吗?

  3. 当我按照同一篇博客中所述去做时,我的代码会出现错误:

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

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


18
out _ 中,_ 不是一个变量,你没有声明它,也不能用名称来使用它。而在 int _ 中,_ 就是一个变量。 - Evk
9
看起来通配符星号在 C# 7 的最终版本中没有被包含进去。 - Michael Stum
3
@NikhilAgrawal 但这是不同的语法。 在您的问题中,您使用 out _,没有使用 var。 有了 var,它确实与以前相同。 - Evk
2
@NikhilAgrawal 那确实由于某种奇怪的原因编译通过了。然而,如果查看反编译的代码 - 这个赋值语句根本不存在,被完全删除了。如果像这样做 Console.WriteLine(_) - 编译器会报告没有这样的变量,不能编译通过。相当奇怪。更奇怪的是:如果像这样做 _ = SomeMethodCall() - 这将被编译为 SomeMethodCall(),直接替换掉它。因此,在所有这些之后,你仍然无法真正地使用那个变量。 - Evk
3
我已经在这个观察结果上提交了一个错误报告。 - poke
显示剩余5条评论
6个回答

137

Discards,在C#7中可以在变量声明的任何地方使用,如其名称所示,用于丢弃结果。因此,可以在没有变量的情况下使用discard:

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

最后,在讨论废弃的时候,“*”符号被提出来了,但是由于“_”在其他语言中更常用,所以被放弃了。请注意保留HTML标签。

2
我猜这是暗示,但丢弃的重点在于它本应该有的值实际上从未被存储? - Sinjai
_ = 42 "丢弃表达式结果" 是误导性的,因为 _ = 42 本身就是一个值为 42 的表达式,所以实际上并没有丢弃任何东西。但是仍然有所不同,因为 _ = 42; 是一条语句,而 42; 不是,在某些情况下这很重要。 - Jeroen Mostert
@JeroenMostert,您会如何用不同的措辞表达?该表达式被丢弃,因为使用了一个discard。虽然您说'_ = 42'是一个表达式语句,但我不清楚这与使用discard有什么关系,因此表达式的结果没有存储在任何地方; 它被丢弃了。 - David Arno
1
措辞没问题,只是 _ = 42 没能展示出这个抛弃的意义——也就是说,当你需要“不存储”一个表达式但仍需求值时,通常情况下你可以很好地对(非平凡、有用的)表达式求值而无需存储。我自己暂时想不到一个有用的例子(也不知道是否存在),或者说这只是语法保持一致的结果。 - Jeroen Mostert
@JeroenMostert,啊,好的,明白了。是的,一个比42更好的例子会更有用。我会考虑一个更好的例子,并更新答案。谢谢。 - David Arno

35

在C# 7中使用弃元操作符_的另一个示例是,在switch语句中对类型为object的变量进行模式匹配。这是最近在C# 7中添加的功能:

代码:

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 ... _ 的变量。


17

更加好奇的话

请考虑以下片段

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的相同,因为您调用的函数仍需要输出变量的插槽。只是使用新的丢弃语法使编译器对本地变量(或缺乏本地变量)进行更多假设,从而可以更有效地使用它(至少在理论上;我不知道编译器是否已经有任何优化)。 - poke

9
关于第一个问题

I guess this is just an info and not a new feature of C#7 because we can do so in pre C#7.0 too.

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),下划线非常有效,因为我可以丢弃前两个输出参数而无需创建类似 unused1、unused2 的变量。请注意,这不会改变方法的功能。 - Michael Stum
@MichaelStum:这里发生了什么?if (SomeMethod(out _, out _, out _)) _ = 5; 它所指的 _ 是哪一个? - Nikhil Agrawal
4
即使使用 out var _,也不会有 _ 变量存在。似乎下划线被特殊处理为丢弃结果。 - Michael Stum

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

Chris - 关闭...作为变量的下划线也成为了废弃 :) - David V. Corbin
@DavidV.Corbin 是的...从技术上讲。我认为“_不是一个变量,除非你已经将_声明为变量”对于不理解抛弃的人来说是更好的指导。我猜你的笑脸意味着你明白了 :) - J. Chris Compton

0
在C# 7.0(大约在2017年3月的Visual Studio 2017中),在以下上下文中支持弃用赋值:

其他有用的笔记

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

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

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

在 switch case 中使用现代的模式匹配,这是一个高级示例(source

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;

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接