在C#中,两个问号一起意味着什么?


1628

遍历以下代码行:

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

这两个问号是什么意思,是某种三元运算符吗?在Google中很难查找。


50
绝对不是三元运算符-它只有两个操作数!这是一个有点像条件运算符(这三元),但空合并运算符是二元运算符。
乔恩·斯基特

90
我在一次采访中对此进行了解释,在那次采访中,准雇主此前曾对我的C#能力表示怀疑,因为我之前已经在专业领域使用Java。他们之前没有听说过,之后也没有质疑我对C#的熟悉程度:)
Jon Skeet

77
@Jon Skeet自从拒绝甲壳虫乐队的那个家伙以来,就没有如此史诗般的无法识别技能。:-)从现在起,只需将书的副本发送给他们,并将其URL链接发送到封面上写的SO个人资料即可。
伊恩·霍尔德

6
IainMH:对于它的价值,我没有开始写这本书呢。(或者,也许我只是工作在第一章-类似的东西)诚然,我的搜索会很快找到我的博客+文章等
乔恩斯基特

7
回复:q的最后一句话-对于将来的引用,SymbolHound非常适合此类事情,例如symbolhound.com/?q=%3F%3F&l=&e=&n=&u= [对任何可疑的人-我不隶属于无论如何,就像当我找到一个好工具一样...]
史蒂夫·钱伯斯

Answers:


2152

它是空值合并运算符,与三元运算符(即时if)非常相似。另请参阅?? 运营商-MSDN

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

扩展为:

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

进一步扩展为:

if(formsAuth != null)
    FormsAuth = formsAuth;
else
    FormsAuth = new FormsAuthenticationWrapper();

在英语中,它的意思是“如果左边的内容不为空,则使用它,否则使用右边的内容。”

请注意,您可以依次使用任何数量的这些。以下语句将第一个非null分配Answer#Answer(如果所有Answers为null,则the Answer为null):

string Answer = Answer1 ?? Answer2 ?? Answer3 ?? Answer4;

同样值得一提的是,以上扩展在概念上是等效的,每个表达式的结果仅计算一次。例如,如果表达式是具有副作用的方法调用,则这一点很重要。(请指出@Joey,以指出这一点。)


17
锁住

6
@CodeBlend怎么回事?
lc。

68
@CodeBlend,这并不危险。如果要扩展,则只会有一系列嵌套的if / else语句。语法很奇怪,因为您不习惯看到它。
joelmdev

31
如果它也适用于空字符串,那将是
天赐的礼物

14
@Gusdor ??保持关联,所以a ?? b ?? c ?? d等效于((a ?? b) ?? c ) ?? d。“赋值运算符和三元运算符(?:)都是右关联的。所有其他二进制运算符都是左关联的。” 来源:msdn.microsoft.com/en-us/library/ms173145.aspx
Mark E. Haase 2014年

284

只是因为没有人说过神奇的话:它是null合并运算符。它在C#3.0语言规范的 7.12节中定义。

它非常方便,特别是因为它在表达式中多次使用时的工作方式。形式的表达式:

a ?? b ?? c ?? d

a如果为非null,则将给出表达式的结果,否则尝试b,否则尝试c,否则尝试d。它在每个点都短路。

同样,如果的类型d是不可为空的,则整个表达式的类型也将不可为空。


2
我喜欢您将其含义简化为“空合并运算符”的方式。那就是我所需要的。
FrankO 2015年

2
切线但相关的:它现在在斯威夫特,但它是截然不同的:“无合并运算符” developer.apple.com/library/ios/documentation/Swift/Conceptual/...
丹Rosenstark


27

谢谢大家,这是我在MSDN站点上找到的最简洁的解释:

// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;

4
这暗示了??的重要方面。运算符-引入它是为了协助处理可空类型。在您的示例中,“ x”的类型为“ int?” (Nullable <int>)。
德鲁·诺阿克斯

9
@vitule否,如果null合并运算符的第二个操作数是不可为null的,则结果是不可为null的(并且-1只是一个普通的int,不可为null)。
SuzanneDupéron'13年

我真的希望它能像这样工作。我想一直这样做,但是我不能,因为我使用的变量不是可为空的类型。以咖啡脚本y = x起作用?-1
PandaWood 2014年

@PandaWood其实可以。如果x是类型int?,但是y是类型int,则可以编写int y = (int)(x ?? -1)。如果不是,它将解析x为,或者将其分配为如果是。intnull-1yxnull
约翰·温特根斯

21

??当该值为null时,是否可以为可为null的类型提供值。因此,如果formsAuth为null,它将返回新的FormsAuthenticationWrapper()。


19

在此处输入图片说明

两个问号(??)表示其为合并运算符。

合并运算符从链返回第一个NON-NULL值。您可以观看此youtube视频,它实际上演示了整个过程。

但是,让我在视频中添加更多内容。

如果您看到合并的英文含义,那就说“巩固在一起”。例如,下面是一个简单的合并代码,它链接了四个字符串。

所以,如果str1null,它会尝试str2,如果str2null,它会尝试str3依此类推,直到它找到一个非空值的字符串。

string final = str1 ?? str2 ?? str3 ?? str4;

用简单的话说,合并运算符从链中返回第一个NON-NULL值。


我喜欢这种解释,因为它有一个图表,然后最后一句话用这么简单的方式解释了它!它使理解变得容易,并增强了我的使用信心。谢谢!
约书亚K,

14

这是三元运算符的简写。

FormsAuth = (formsAuth != null) ? formsAuth : new FormsAuthenticationWrapper();

或对于那些不做三元的人:

if (formsAuth != null)
{
  FormsAuth = formsAuth;
}
else
{
  FormsAuth = new FormsAuthenticationWrapper();
}

3
我只更正了“三进制”的拼写,但实际上您要表示的运算符是条件运算符。它恰好是C#中唯一的三元运算符,但在某些时候,他们可能会添加另一个,这时“三元”将是模棱两可的,而“条件”则不会。
乔恩·斯基特

这是什么东西,你可以简写与三元(条件)运算符。在您的长格式中,可以更改测试(!= null)和第二个formsAuth(在之后?);在空合并形式中,两个都隐式采用您提供的值。
Bob Sammers

12

如果您熟悉Ruby,那么对我来说,它就像||=C#??。这是一些Ruby:

irb(main):001:0> str1 = nil
=> nil
irb(main):002:0> str1 ||= "new value"
=> "new value"
irb(main):003:0> str2 = "old value"
=> "old value"
irb(main):004:0> str2 ||= "another new value"
=> "old value"
irb(main):005:0> str1
=> "new value"
irb(main):006:0> str2
=> "old value"

在C#中:

string str1 = null;
str1 = str1 ?? "new value";
string str2 = "old value";
str2 = str2 ?? "another new value";

4
x ||= y对像x = x || y这样的东西,??实际上更类似于||Ruby中的plain 。
qerub

6
请注意,??只关心null,而||在Ruby中操作,因为在大多数语言中,更多的是nullfalse或者任何可以被认为是具有价值的布尔false(在某些语言如"")。这不是一件好事或坏事,只是一个区别。
Tim S.

11

合并运算符

相当于

FormsAuth = formsAUth == null ? new FormsAuthenticationWrapper() : formsAuth

11

没什么危险的。实际上,它是美丽的。如果需要,可以添加默认值,例如:

int x = x1 ?? x2 ?? x3 ?? x4 ?? 0;

1
所以x1,x2,x3和x4可以是Nullable类型,例如:int? x1 = null;是吗
Kevin Meredith

1
@KevinMeredith x1- x4必须是可空类型:它是没有意义的说法,有效“的结果是0,如果x4是,它不可能采取值”( null)。当然,这里的“可空类型”包括可空类型和引用类型。如果一个或多个链接变量(最后一个除外)不可为空,则是编译时错误。
Bob Sammers

11

正如在许多答案中正确指出的那样,它是“空合并运算符”(??),谈到它,您可能还想查看其表亲在“空条件运算符”(?。?[)中是它与??一起多次使用

空条件运算符

用于在执行成员访问(?。)或索引(?[)操作之前测试null 。这些运算符可帮助您编写更少的代码来处理空检查,尤其是在下降到数据结构中时。

例如:

// if 'customers' or 'Order' property or 'Price' property  is null,
// dollarAmount will be 0 
// otherwise dollarAmount will be equal to 'customers.Order.Price'

int dollarAmount = customers?.Order?.Price ?? 0; 

没有的旧方法?? 这样做是

int dollarAmount = customers != null 
                   && customers.Order!=null
                   && customers.Order.Price!=null 
                    ? customers.Order.Price : 0; 

这比较冗长和繁琐。


10

仅出于娱乐目的(知道您都是C#专家;-)。

我认为它起源于Smalltalk,已经存在了很多年。在那里定义为:

在对象中:

? anArgument
    ^ self

在UndefinedObject中(又名nil的类):

? anArgument
    ^ anArgument

有评估版(?)和非评估版(??)。
经常在getter方法中发现延迟初始化的私有(实例)变量,直到真正需要时才将其保留为零。


听起来像用UserControl上的属性包装ViewState。如果之前未设置,则仅在首次获取时进行初始化。=)
塞提岛2009年

9

这里使用合并获得价值的一些示例效率很低。

您真正想要的是:

return _formsAuthWrapper = _formsAuthWrapper ?? new FormsAuthenticationWrapper();

要么

return _formsAuthWrapper ?? (_formsAuthWrapper = new FormsAuthenticationWrapper());

这样可以防止每次都重新创建对象。这样,可以确保在创建新对象时分配私有变量,而不是使私有变量保持为空并在每次请求时都创建一个新对象。


??评估捷径吗?仅当为nil时new FormsAuthenticationWrapper();评估。 _formsAuthWrapper
MSalters

是的,这就是重点。如果变量为null,则只想调用该方法。@MSalters
KingOfHypocrites

8

注意:

我已经阅读了整个主题以及其他主题,但是找不到如此详尽的答案。

通过它,我完全理解了“为什么使用??,何时使用??以及如何使用”。

资源:

Windows通信基础已释放Craig McMurtry ISBN 0-672-32948-4

可空值类型

在两种常见情况下,您想知道是否已将值分配给值类型的实例。第一种是实例代表数据库中的值时。在这种情况下,我们希望能够检查实例以确定数据库中是否确实存在值。另一种情况是与实例有关的,当实例表示从某个远程源接收到的数据项时。同样,人们想根据实例确定是否接收到该数据项的值。

.NET Framework 2.0包含一个通用类型定义,该定义提供了类似的情况,在这种情况下,您希望为值类型的实例分配null,并测试该实例的值是否为null。该通用类型定义为System.Nullable,它将可替换T的通用类型参数约束为值类型。从System.Nullable构造的类型的实例可以分配为null值;实际上,它们的值默认为空。因此,从System.Nullable构造的类型可以称为可为空的值类型。System.Nullable具有属性Value,如果该实例的值不为null,则可以通过该属性获得分配给从其构造的类型的实例的值。因此,可以写出:

System.Nullable<int> myNullableInteger = null;
myNullableInteger = 1;
if (myNullableInteger != null)
{
Console.WriteLine(myNullableInteger.Value);
}

C#编程语言提供了一种简短的语法,用于声明从System.Nullable构建的类型。该语法允许缩写:

System.Nullable<int> myNullableInteger;

int? myNullableInteger;

编译器将以这种方式防止尝试将可为空的值类型的值分配给普通值类型:

int? myNullableInteger = null;
int myInteger = myNullableInteger;

它阻止了这样做,因为可为空的值类型可能具有null值(在这种情况下它实际上会具有此值),并且该值不能分配给普通的值类型。尽管编译器允许使用此代码,

int? myNullableInteger = null;
int myInteger = myNullableInteger.Value;

第二条语句将引发异常,因为如果从System.Nullable构造的类型尚未分配有效的T值,则任何尝试访问System.Nullable.Value属性的操作都是无效操作,而在此操作中没有发生案件。

结论:

将可空值类型的值分配给普通值类型的一种正确方法是使用System.Nullable.HasValue属性来确定是否已将T的有效值分配给可空值类型:

int? myNullableInteger = null;
if (myNullableInteger.HasValue)
{
int myInteger = myNullableInteger.Value;
}

另一种选择是使用以下语法:

int? myNullableInteger = null;
int myInteger = myNullableInteger ?? -1;

如果为空整数“ myNullableInteger”分配了有效的整数值,则通过该值将普通整数myInteger分配给它;否则,将为myInteger分配值-1。


1

这是一个空合并运算符,其作用与三元运算符相似。

    a ?? b  => a !=null ? a : b 

另一个有趣的地方是, “可为null的类型可以包含一个值,也可以是未定义的”。因此,如果您尝试将可为空的值类型分配给不可为空的值类型,则会出现编译时错误。

int? x = null; // x is nullable value type
int z = 0; // z is non-nullable value type
z = x; // compile error will be there.

因此使用?? 操作员:

z = x ?? 1; // with ?? operator there are no issues

1
FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

相当于

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

但是,最酷的是您可以像其他人一样将它们链接起来。尚未触及的一个问题是您可以实际使用它引发异常。

A = A ?? B ?? throw new Exception("A and B are both NULL");

您可以在帖子中包含示例,这真是太好了,尽管问题是在寻找操作符是什么或做什么的解释。
TGP1994 '18

1

??运算符称为null-coalescing运算符。如果操作数不为null,则返回左侧的操作数;否则,返回0。否则返回右手操作数。

int? variable1 = null;
int variable2  = variable1 ?? 100;

如果不为null ,则设置variable2为的值。否则,如果设置为100。variable1variable1variable1 == nullvariable2


0

其他人描述Null Coalescing Operator得很好。对于那些感兴趣的人,这里有一个简化的语法(SO问题):

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

等效于此:

FormsAuth ??= new FormsAuthenticationWrapper();

有些人认为它更易读和简洁。

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.