使用空聚合运算符的唯一方法

我知道在 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

因此,假设知道空合并运算符的人更少..。

  • 你用 ??做过其他的事情吗?

  • ??是必需的,还是应该只使用三元运算符(即 大多数人都熟悉)

70961 次浏览

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

你应该使用最能表达你的意图。因为有一个空合并运算符,好好利用

另一方面,因为它是如此的专业化,我不认为它有其他的用途。我希望像其他语言一样,适当地重载 ||操作符。这在语言设计中会更加简约。但是..。

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

string anybody = parm1 ?? localDefault ?? globalDefault;

对。

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

如果一个可能为空的对象不是变量,那么它也能很好地工作:

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

对。

string anybody = (Parameters["Name"] != null ? Parameters["Name"]
: (Settings["Name"] != null) ? Settings["Name"]
:  GlobalSetting["Name"];

我把它用作一个懒惰的加载俏皮话:

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

可读性? 你自己决定吧。

酷! 把我算作一个不知道空结合运算符的人吧——真是个好主意。

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

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

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
}

我发现它在两个“有点奇怪”的方面很有用:

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

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

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 之初检查引用相等性(或空性) ,也可以执行同样的操作。

我在 IDataErrorInfo 的实现中使用了 ??:

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


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

如果任何单独的属性处于“错误”状态,我将得到该错误,否则我将得到 null。效果非常好。

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

例如,考虑一下这个问题:

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

而对于三元运算符,则只剩下以下两种情况:

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

它调用 MyMethod 两次,或者:

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

无论哪种方式,空合并运算符都更干净,我想,也更有效。

唯一的问题是空合并运算符不检测空字符串。


也就是说。

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


string result2 = null ?? "coalesced!";

输出

result1 = ""


result2 = coalesced!

我现在正在研究重写?来解决这个问题。将此内置到框架中肯定会很方便。

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

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

另外,正如前面提到的,当 null 合并运算符非常有用时,有一种主要的情况,那就是要计算的表达式有任何副作用的时候。在这种情况下,你使用条件运算符时不需要(a)引入一个临时变量,或者(b)改变应用程序的实际逻辑。(b)在任何情况下显然都是不合适的,虽然这是我个人的偏好,但我不喜欢在声明范围内添加大量无关的变量,即使这些变量是短暂的,所以(a)在这种特殊情况下也是不合适的。

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

我发现 ??操作符的最大优点是,您可以轻松地将可空值类型转换为不可空值类型:

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?

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

public void Method(Arg arg = null)
{
arg = arg ?? Arg.Default;
...

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

一个非常简单(且人为的)的例子来说明我的观点:

public class StackOverflow
{
private IEnumerable<string> _definitions;
public IEnumerable<string> Definitions
{
get
{
return _definitions ?? (
_definitions = new List<string>
{
"definition 1",
"definition 2",
"definition 3"
}
);
}
}
}

另一件需要考虑的事情是,合并操作符不会像三元组那样两次调用属性的 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。

我最近一直在做的一件事就是对 as的备份使用 null 合并。例如:

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";

这是一个有点奇怪的用例,但是我有一个方法,其中一个 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);
[...]
}

我是这样使用它的:

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;
}