独特的方式来使用空合并运算符

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

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 

所以,即使知道更less的空合并操作符…

  • 你用过?? 为了别的吗?

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

那么,首先,比标准三元链更容易链接:

 string anybody = parm1 ?? localDefault ?? globalDefault; 

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

如果null可能的对象不是一个variables,它也可以工作:

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

可读? 自己决定。

我发现它有两个“有点奇怪”的方法是有用的:

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

后者需要更多的信息。 通常,当您创build一个与多个元素的比较时,您需要查看比较的第一部分(例如年龄)是否给出明确的答案,然后是仅当第一部分没有帮助时才接下来的部分(例如姓名)。 使用空合并运算符意味着您可以编写非常简单的比较(无论是sorting还是相等)。 例如,在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之前,检查引用相等(或无效)也是一样的。

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

考虑到这一点,例如:

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

而与三元运算符,你留下了:

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

这会调用MyMethod两次,或者:

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

无论哪种方式,空合并运算符更清洁,我猜,效率更高。

另一个要考虑的事情是,联合运算符不要像三元组那样两次调用属性的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将被调用两次, countvariables将等于2,如果您使用:

 var b = a.Prop ?? 0 

countvariables将等于1,因为它应该。

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

 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? 

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

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

如果任何个人财产处于“错误”状态,我得到这个错误,否则我得到空。 工作得很好。

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

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

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


 string result1 = string.empty ?? "dead code!"; string result2 = null ?? "coalesced!"; 

OUTPUT:

 result1 = "" result2 = coalesced! 

我目前正在寻找重写? 操作员来解决这个问题。 将它embedded到框架中肯定会很方便。

思考?

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

实际上,我的经验是,很less有人熟悉三元运算符(或者更准确地说, 条件运算符; ?:是“三元”,就像||是二元的,或者是一元或二元的;然而恰巧是许多语言中唯一的三元运算符),所以至less在这个有限的样本中,你的陈述就在那里失败了。

而且,正如前面所提到的,当一个主要的情况是空合并运算符是非常有用的,那就是每当被评估的expression式都有任何副作用。 在这种情况下,如果没有(a)引入临时variables,或者(b)更改应用程序的实际逻辑, 则不能使用条件运算符。 (b)在任何情况下都显然是不合适的,虽然这是一种个人偏好,但我不喜欢用很多无关的,即使是短暂的variables来夸大声明范围,所以(a)也是特定情况。

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

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

你应该使用最好的expression你的意图。 由于有一个空的合并运算符,请使用它

另一方面,由于它很专业,我认为它没有其他用途。 我会更喜欢适当的重载|| 和其他语言一样。 在语言devise上这将更加简单。 但是…

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

我发现比三元操作符更容易阅读。

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

 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 } 

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

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

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

有一件事我最近做了很多事情,就是使用null合并备份到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"; 

一个奇怪的用例位,但我有一个方法,其中一个IDisposable对象可能作为parameter passing(因此由父处理),但也可以为空(所以应创build和本地方法处置)

要使用它,代码看起来像

 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); [...] }