关于c#:使用Null Coalescing运算符的独特方法

Unique ways to use the Null Coalescing operator

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

1
2
3
4
5
6
string nobody = null;
string somebody ="Bob Saget";
string anybody ="";

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

但是,??还能用来做什么呢?它不像三元运算符那样有用,除了更简洁和易读之外:

1
2
nobody = null;
anybody = nobody == null ?"Bob Saget" : nobody; // returns Bob Saget

所以考虑到对空合并运算符的了解更少…

  • 你用过??做其他的东西吗?

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


首先,要比标准的三元更容易连接:

1
string anybody = parm1 ?? localDefault ?? globalDefault;

VS

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

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

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

VS

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


我用它作为一个懒惰的负载一行程序:

1
2
3
4
public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

可读?自己决定。


我发现它有两种"稍微奇怪"的方式:

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

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

1
2
3
4
5
6
7
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开始时检查引用是否相等(或无效)。


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

考虑一下,例如:

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

当使用三元运算符时,您只剩下:

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

两次调用MyMethod,或者:

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

不管怎样,空合并操作符都更干净,而且,我猜,效率更高。


另一个需要考虑的问题是,与三元运算符一样,合并运算符不会两次调用属性的get方法。

因此,有些情况下不应该使用三元,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

如果使用:

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

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

1
var b = a.Prop ?? 0

count变量应该等于1。


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

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

我经常在LINQ查询中使用这个:

1
2
3
4
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时:

1
2
3
4
5
6
7
8
9
10
11
12
public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

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

如果任何单个属性处于"错误"状态,我将得到该错误,否则将得到空值。工作得很好。


您可以使用空合并运算符使处理未设置可选参数的情况更加简单:

1
2
3
4
public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...


Is ?? necessary, or should you just use the ternary operator (that most are familiar with)

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

此外,如前所述,当空合并运算符非常有用时,还有一个主要情况,即每当要计算的表达式有任何副作用时。在这种情况下,如果没有(a)引入临时变量,或(b)更改应用程序的实际逻辑,就不能使用条件运算符。(b)显然在任何情况下都不合适,虽然这是个人偏好,但我不喜欢将声明范围与许多无关的东西混在一起,即使是短期的变量,所以(a)在这种特定的情况下也是不合适的。

当然,如果您需要对结果进行多次检查,那么条件运算符或一组if块可能是该作业的工具。但是对于简单的"如果这是空的,使用它,否则使用它",空合并操作符??是完美的。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                   "definition 1",
                   "definition 2",
                   "definition 3"
                }
            );
        }
    }
}


最近我经常做的一件事是使用空合并来备份到as。例如:

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

Console.WriteLine(i); // Prints 4

它也有助于备份可能失败的?.的长链。

1
2
int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ??"not there";


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

1
2
3
string result1 = string.empty ??"dead code!";

string result2 = null ??"coalesced!";

输出:

1
2
3
result1 =""

result2 = coalesced!

我目前正在考虑覆盖??操作员处理此问题。将它内置到框架中肯定很方便。

思想?


Is ?? necessary, or should you just use the ternary operator (that most are familiar with)

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

另一方面,由于它是如此专业,我认为它没有其他用途。我宁愿像其他语言一样,适当地超载||操作符。这在语言设计上会更为节俭。但是…


酷!把我算为一个不知道空合并操作符的人——这真是太棒了。

我发现读起来比三元运算符容易得多。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
}


有点奇怪,但我有一个方法,其中IDisposable对象可能作为参数传递(因此由父对象释放),但也可以为空(因此应该在本地方法中创建和释放)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Channel channel;
Authentication authentication;

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

但如果合并为空,则会变得更整洁。

1
2
3
4
5
using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

空合并运算符

此运算符检查变量,如果为空,则返回""旁边的值。?"operator else给出变量中保存的值。

Eg:- 1

1
2
3
var name="ATHUL";
var result =name ??"The name is null"
Console.WriteLine(result);

O/P:阿瑟尔

Eg:- 2

1
2
3
var name=null;
var result =name ??"The name is null"
Console.WriteLine(result);

O/P:名称为空