170

我知道在 C# 中使用空合并运算符的标准方法是设置默认值。

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // Returns Mr. T
anybody = somebody ?? "Mr. T"; // Returns "Bob Saget"

但是还能??用来做什么呢?它似乎不如三元运算符有用,除了更简洁和更易于阅读之外:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // Returns Bob Saget

因此,鉴于很少有人知道空合并运算符...

  • 你用过??别的吗?

  • ??必要的,或者您应该只使用三元运算符(大多数人都熟悉)

4

16 回答 16

224

嗯,首先,它比标准的三元运算符更容易链接:

string anybody = parm1 ?? localDefault ?? globalDefault;

对比

string anyboby = (parm1 != null) ? parm1
               : ((localDefault != null) ? localDefault
               : globalDefault);

如果可能为 null 的对象不是变量,它也可以很好地工作:

string anybody = Parameters["Name"]
              ?? Settings["Name"]
              ?? GlobalSetting["Name"];

对比

string anybody = (Parameters["Name"] != null ? Parameters["Name"]
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];
于 2008-11-10T18:25:22.957 回答
178

我用它作为一个惰性加载单行:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

可读吗?自己决定。

于 2008-11-10T18:25:55.573 回答
53

我发现它以两种“有点奇怪”的方式有用:

  • out作为编写例程时使用参数的替代方法TryParse(即,如果解析失败,则返回空值)
  • 作为比较的“不知道”表示

后者需要更多信息。通常,当您创建与多个元素的比较时,您需要查看比较的第一部分(例如年龄)是否给出了明确的答案,然后只有在第一部分没有帮助时才查看下一部分(例如姓名)。使用空合并运算符意味着您可以编写非常简单的比较(无论是排序还是相等)。例如,在MiscUtil中使用几个帮助类:

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

诚然,我现在在 MiscUtil 中拥有 ProjectionComparer,以及一些扩展,这使得这种事情变得更加容易——但它仍然很整洁。

在开始实现 Equals 时检查引用相等(或无效)也可以这样做。

于 2008-11-10T19:30:56.957 回答
34

另一个优点是三元运算符需要双重评估或临时变量。

考虑一下,例如:

string result = MyMethod() ?? "default value";

而使用三元运算符时,您剩下的是:

string result = (MyMethod () != null ? MyMethod () : "default value");

它调用 MyMethod 两次,或者:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

无论哪种方式,空合并运算符都更干净,而且我猜想效率更高。

于 2008-12-19T21:30:48.813 回答
24

要考虑的另一件事是,coalesce 运算符不会像三元组那样调用属性的 get 方法两次。

所以有些场景你不应该使用三元运算符,例如:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

如果您使用:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

getter 将被调用两次,count变量将等于 2,如果您使用:

var b = a.Prop ?? 0

count变量应该等于 1 。

于 2015-08-17T13:52:03.330 回答
15

我发现??运算符的最大优势是您可以轻松地将可为空的值类型转换为不可为空的类型:

int? test = null;
var result = test ?? 0; // 'result' is int, not int?

我经常在 LINQ 查询中使用它:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?
于 2012-11-27T22:25:08.233 回答
9

??在 IDataErrorInfo 的实现中使用过:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

如果任何单个属性处于“错误”状态,我会得到那个错误,否则我会得到 null。它真的很好用。

于 2008-11-11T09:56:36.490 回答
7

您可以使用 null 合并运算符使其更清晰地处理未设置可选参数的情况:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...
于 2013-07-01T07:45:01.867 回答
6

我喜欢使用空合并运算符来延迟加载某些属性。

一个非常简单(和人为)的例子只是为了说明我的观点:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}
于 2014-06-19T14:51:43.467 回答
6

我最近一直在做的一件事是使用空合并来备份到as. 例如:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

它对于备份?.可能会失败的长链也很有用

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";
于 2016-06-29T14:09:47.800 回答
5

是 ??必要的,或者您应该只使用三元运算符(大多数人都熟悉)

实际上,我的经验是,很少有人熟悉三元运算符(或更准确地说,条件运算符;在与二元或一元或二元?:相同的意义上是“三元” ;但它确实恰好是只有许多语言的三元运算符),所以至少在那个有限的样本中,你的陈述在那里失败了。||+

此外,如前所述,当空合并运算符非常有用时,存在一种主要情况,即每当要计算的表达式有任何副作用时。在这种情况下,您不能在不 (a) 引入临时变量或 (b) 更改应用程序的实际逻辑的情况下使用条件运算符。(b) 显然在任何情况下都不合适,虽然这是个人喜好,但我不喜欢用大量无关紧要的变量来混淆声明范围,即使是短暂的变量,所以 (a) 也在其中特定场景。

当然,如果您需要对结果进行多次检查,条件运算符或一组if块可能是这项工作的工具。但是对于简单的“如果 this 为 null,则使用 that,否则使用它”,null 合并运算符??是完美的。

于 2011-02-15T13:12:03.567 回答
4

唯一的问题是 null-coalesce 运算符不检测空字符串。


IE

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

输出

result1 = ""

result2 = coalesced!

我目前正在研究覆盖 ?? 操作员来解决这个问题。将它内置到框架中肯定会很方便。

于 2009-03-10T16:01:25.633 回答
3

是 ??必要的,或者您应该只使用三元运算符(大多数人都熟悉)

你应该使用最能表达你意图的东西。由于有一个空合并运算符,请使用它

另一方面,由于它是如此专业,我认为它没有其他用途。我会更喜欢运算符的适当重载||,就像其他语言一样。这在语言设计中会更加简洁。不过还好……</p>

于 2008-11-10T18:24:38.340 回答
3

凉爽的!把我算作一个不知道空合并运算符的人——这是非常漂亮的东西。

我发现它比三元运算符更容易阅读。

我首先想到的可能使用它的地方是将所有默认参数保存在一个地方。

public void someMethod(object parm2, ArrayList parm3)
{
  someMethod(null, parm2, parm3);
}

public void someMethod(string parm1, ArrayList parm3)
{
  someMethod(parm1, null, parm3);
}

public void someMethod(string parm1, object parm2)
{
  someMethod(parm1, parm2, null);
}

public void someMethod(string parm1)
{
  someMethod(parm1, null, null);
}

public void someMethod(object parm2)
{
  someMethod(null, parm2, null);
}

public void someMethod(ArrayList parm3)
{
  someMethod(null, null, parm3);
}

public void someMethod(string parm1, object parm2, ArrayList parm3)
{
  // Set your default parameters here rather than scattered 
  // through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}
于 2008-11-10T18:31:37.607 回答
2

这是一个有点奇怪的用例,但我有一个方法,其中一个IDisposable对象可能作为参数传递(因此由父级处理),但它也可能是 null (因此应该在本地创建和处理方法)

要使用它,代码要么看起来像

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

但是使用空合并它变得更加整洁:

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}
于 2016-12-19T18:23:02.990 回答
0

我这样使用它:

for (int i = 0; i < result.Count; i++)
{
    object[] atom = result[i];

    atom[3] = atom[3] ?? 0;
    atom[4] = atom[4] != null ? "Test" : string.Empty;
    atom[5] = atom[5] ?? "";
    atom[6] = atom[6] ?? "";
    atom[7] = atom[7] ?? "";
    atom[8] = atom[8] ?? "";
    atom[9] = atom[9] ?? "";
    atom[10] = atom[10] ?? "";
    atom[12] = atom[12] ?? false;
}
于 2019-09-02T13:53:10.120 回答