C#变量名只能使用下划线"_"吗?

57

我在 C# 中遇到了一个小问题,这只是一个复制粘贴错误,但不知道为什么 C# 接受了它。

这段代码居然能够成功编译... 究竟是为什么呢?

namespace DemoNS
{
    class DemoClass
    {
        String _ = new String('a', 1);        
    }
}

变量名为 _ 有默认的特殊含义吗?


5
正如svick所指出的,这个名字和其他名字一样有效。然而,将变量命名为“_”可能是非常糟糕的习惯。 - Justin Morgan
5
请问您为什么认为它不会或不应该奏效呢? - Chris Dunaway
1
肯定的是,我不会把“_”作为变量名。我只是无意中这样命名了它。 - Samy
5个回答

92

现今,随着 C# 7.0 的推出,下划线符号_的意义有时是重要的。它成为了新特性out var丢弃运算符(discard operator)。当函数返回一个值,而你并不会使用它时,你可以使用这个符号向编译器发出信号,以便优化掉这个值。或者在解构操作中(又是 C# 7.0 的新功能),你可以使用它来忽略你不感兴趣的元组部分。

例子: out var

void Test(out int i) => i = 1;

Test(out _); // _ was never declared, it will still compile in C# 7.0

var r = _;   // error CS0103: The name '_' does not exist in the current context

示例:解构元组

var Person = ("John", "Smith");

var (First, _) = Person; // '_' is not a declared

Debug.Print(First); // prints "John"
Debug.Print(_); // error CS0103: The name '_' does not exist in the current context
如果你声明了自己的名为_的变量,然后使用丢弃运算符,就会导致歧义问题。该问题已在此处报告。 编辑 如@maf-soft在评论中指出,上述问题并不是问题。如果声明了_,它就像C# 7.0之前一样被视为普通变量。 编辑2021有点过期了 在c# 8.0中,_也成为switch表达式中的通配符运算符,正式命名为丢弃运算符示例丢弃运算符
var moreThan20 = val switch
{
    >20 => "Yes",
    >50 => "Yes - way more!",
    _ => "No",
};
< p > < em > 丢弃运算符 在没有其他模式匹配时分配一个值

1
我想补充一点,如果你声明了一个名为“_”的变量,它将像以前一样使用,因此旧代码不会出错(这在你的回答中并不完全清楚)。对我来说没问题,但也许发出一个警告会更好。 - maf-soft
我不确定那个丢弃运算符是否真的属于“out var”功能(是的,你的源链接给出了这种印象)。 - maf-soft
@maf-soft,你说得对。我将在引用的gist上发表评论。 - MotKohn
另一个有趣的参考:https://dev59.com/mlgQ5IYBdhLWcg3wWCg0 - 有趣的是,对于丢弃运算符的其他想法都使用了 void 关键字 :) - maf-soft
这非常出乎意料。它不像C#那样。 - Bamdad

47

不,没有默认的意义,_就像其他变量名一样。

我喜欢将其用于类似于Prolog匿名变量的方式:当你创建一个忽略其中一个参数的lambda表达式时,你可以将其命名为_

EventHandler handler = (_, e) => Console.WriteLine(e);

另一方面,我不会在其他地方使用它,你应该使用一个描述性的名称。

编辑:请注意,在C# 7.0中,“_”有时具有特殊含义。例如,您可以编写_ = new String('a', 1);,即使您没有声明名为_的变量。


14
严肃地说:不要使用它。 - Femaref
30
如果你不关心一个参数的话,在lambda表达式中使用惯用语。 - user7116
2
而且作为参考(虽然有点古老),这里是标识符规范:http://msdn.microsoft.com/en-us/library/aa664670.aspx - Brad Christie
1
@Justin:嗯,我做了很多函数式编程(OCaml等),这可能是从那里带过来的习惯。 - user7116
2
在Python中,当您不关心参数/参数/值时,下划线也是惯用语。 - Hamish Grubijan
显示剩余3条评论

19

之前的回答都很有用,但我认为它们忽略了一个使用情况。如果你不想使用函数的返回值,你可以使用_字符,例如:

用下面的方式代替:

int returnvalue = RandomFunction();

你可以做

_ = RandomFunction();

7
_ = RandomFunction(); 相对于 RandomFunction(); (即根本不分配返回值) 的好处是什么? - Marteng
11
从技术上讲,它做的是同样的事情,但明确标记你不想使用返回值 1) 可以创建更干净的代码并且2) 告诉IDE你是有意为之,这样你就不会因此收到警告。 - Olivér Raisz
@OlivérRaisz,你真的认为这样可以创造更干净的代码吗?如果我们有一个像这样的“查询”函数,它不应该具有副作用: _ = GetUsers();对我来说,这段代码意味着存在副作用,否则我们为什么要丢弃返回值?另一方面,如果我们有一个“命令”: _ = DeleteAllUsers();在这种情况下,我们只是将完全干净的命令调用变得视觉上看起来像一个查询。在lambda表达式或表示将未使用的参数时,我认为它的效果更好,但对于任何阅读者,也许不要到处都使用这些... - Oscar Lundberg
1
@OscarLundberg:我不明白你评论中的逻辑。DeleteAllUsers(); 应该是一个返回 void 的函数;_ = DeleteAllUsers(); 应该是编译器错误。如果 GetUsers(); 没有副作用但返回一个值,那么调用它却不使用返回值是毫无意义的。在这两种情况下,都不会编写你展示的代码。唯一需要使用 _ = SomeFunction(); 的时候 如果正在为其副作用而调用该函数,但其返回值不需要。_ = 告诉未来的程序员:“我正在调用此函数以获得其副作用”。 - ToolmakerSteve
@ToolmakerSteve 我同意我的评论不是很清楚。我想表达的观点是,函数既有副作用又有返回值并不能使代码更加清晰,我认为一个函数应该要么有返回值,要么有副作用。讨论暗示这样做可以创造更干净的代码,但按照我的标准,这实际上会间接地创建出更不干净的代码,因为它允许你同时拥有两者。 - Oscar Lundberg
谢谢你澄清。这是一个有用的原则(正如1986年EIFFEL语言所示),但在我看来与此讨论无关,此讨论以这样一个假设开始:无论出于何种原因,一个函数会导致副作用并返回一个值。例如,执行操作的函数经常返回成功/失败的布尔值或错误代码。通常应该处理该结果,但很少有不处理的理由。_ =正如此答案所说的那样:表明程序员有意忽略了返回值。 - ToolmakerSteve

6

_ 是一个有效的字符,与 ai 相同。在语法上,变量可以以 _ 开头,因此单个字符名 _ 完全符合语法要求。虽然不是一个非常好的选择,但它可以编译并正常工作。


这在C#中已经不再有效。 - VineetYadav
@VineetYadav。我没有看到任何禁止这样做的C#更新。也许有一些编译器选项?在C# 7语言规范/抛弃中,有这样一句话:“该示例假定在作用域中没有名称_的声明。”[暗示“_”仍然是一个有效的名称。因此,旧代码不会出现问题。]据我所知,后来的C#规范只是添加了新功能。为什么要使现有代码无效呢? - ToolmakerSteve

5

这是一个占位变量,没有被使用。它告诉编译器我们对输出值不感兴趣。

例子:创建了一个文件处理类。构造函数将启动该过程。我们有复制、删除操作。假设有一个特定的类负责启动该过程,但不需要担心其他操作。那么我会声明像这样:_ = new FileListener(); 我不用担心输出。 其他类可以实例化FileListener obj = new FileListener(); 或调用其他操作如 FileListener.CopyFile()
    class Program
        {
            static void Main(string[] args)
            {
   /// Ignore the instance value but initialized the operation by instantiation
                _ = new FileListener();
                
                Console.WriteLine("Hello World!");
            }
        }
        
        public class FileListener 
        {
            public FileListener()
            {
                /// Logic to listen external file changes
            }
            public static void DeleteFile()
            { }
            public static void CopyFile()
            { }
        }

1
感谢您抽出时间来解释这个问题。现在更有意义了。祝福 <3 - Cyril Ikelie

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