1. 程式人生 > 其它 >C#中問號(?)用法總結

C#中問號(?)用法總結

  三元運算子(?:)

  三元運算子應該都很熟,通常我們也可以使用if-else來代替三元運算,這一點就不多說了,一個簡單的例子:  

    //取一個0.5-1之間的隨機值
    var value = new Random().NextDouble();
    value = value < 0.5 ? 0.5 + value : value;

  可為空的值型別(T?)

  C#中的值型別指的是結構體型別和列舉型別,他們有一個特點,就是擁有預設值(有時候也叫零值),而且它們不允許被賦予null值,因此C#在此基礎上,引入了可空的值型別:  

    //T是值型別,T?本質上是Nullable<T>的簡寫,可以認為是T值型別加上null的組合型別
T?

  可空值型別是Nullable<T>的物件,可以認為是T值型別加上null的組合型別,比如bool?,表示它的值只能是true、false或者null。

  需要注意,Nullable<T>也是值型別,但是於普通的值型別不一樣,它在未初始化時並非是你泛型引數的預設值,而是null,我們也可以認為Nullable<T>的預設值是null。

  Nullable<T>有兩個很常用的屬性:

    HasValue:表示當前可空值型別是否有值,如果有值(即不等於null),那麼返回true,否則返回false
    Value:如果HasValue
=true,則返回這個可空值型別對應的那個值型別型別的值,否則丟擲 InvalidOperationException。

  值運算的一元運算子和二元運算子也使用於可空值型別,當運算子所有運算物件都不為null時,它就等價於運算子作用於它們對應的值型別物件,否則返回null,也就是說可空型別的一元運算子和二元運算子返回的也是可空型別:  

    int? a = 10;
    int? b = null;
    int? c = 10;

    a++;            //等價於a=a+1
    b++;            //等價於b=b+1,因為b=null,因此自增後b還是null
var d = a * c; //雖然a和c都不等於null,但d還是可空型別 var e = a + b; //因為b=null,因此e=null

  注:bool?的 & 和 | 運算時,不遵循這個規則,詳細見如下:  

x y x & y x|y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

  可空引用型別(T?)

  可空引用型別是為可空上下文而生的,具體可以參考:C# 可空上下文、可空引用型別(?)與可空容忍(!)

  null 條件運算子(?.和?[])

  null 條件運算子是一種成員訪問符的拓展,從C#6.0開始出現:

    ?.表示成員訪問運算子,主要用於物件屬性、方法等成員訪問,?[]表示元素訪問運算子,主要用於集合中元素訪問

  null 條件運算子主要用於簡化我們的程式碼:  

    a?.b 或 a?[b]的運算邏輯是:
    1、如果a是null,那麼a.b和a[b]都會丟擲空指標異常,而a?.b和a?[b]都返回null,而不會丟擲異常
    2、如果a不是null,那麼a?.b和a?[b]等價於a.b和a[b],如果a.b和a[b]丟擲異常,a?.b和a?[b]也會丟擲相同的異常

  例如:  

    static void Method(Exception exception,string[] array)
    {
        //這裡我們不知道exception和array會不會是null,所以我們用它前做if判斷,否則會丟擲空指標異常
        if (exception != null)
        {
            Console.WriteLine(exception.Message);
        }
        if (array != null)
        {
            Console.WriteLine(array[0]);
        }

        //可以簡寫成
        Console.WriteLine(exception?.Message);
        Console.WriteLine(array?[0]);
    }

  需要注意的是,null 條件運算子的返回值是可空型別,如果我們原本值是值型別,那麼經過null 條件運算子將會變成可空的值型別。例如:  

    //Length是int型別的值型別
    int? length=array?.Length;//經過null 條件運算子,變成了可空的值型別

  特別是在級聯式呼叫時,null 條件運算子會很有用:  

    A?.B?.C?.D()
    E?.F?.G?[0]

  這樣,我們就不需要寫很多的if判斷了。

  其實很多時候,我們是將null 條件運算子和null 合併運算子(??)一起使用的。

  null 合併運算子(??和??=)

  合併運算子(??)比較簡單,它表示:  

    expr1 ?? expr2
    1、如果表示式expr1的結果是null,那麼會計算表示式expr2,並返回表示式expr2的結果
    2、如果表示式expr1的結果不是null,那麼會直接返回表示式expr1的結果,此時並不會計算表示式expr2的結果

  例如:

    List<int> numbers = null;
    int? a = null;

    //如果numbers=null,則重新初始化一個List,這樣不用使用if判斷了,list始終不為null
    var list = numbers ?? new List<int>();
    list.Add(a ?? 1);//如果a=null,則返回1

    int? m = 1, n = 2, k = 10;
    var i = (m + n) ?? ++k;//因為m+n!=null,所以++k並不會被計算
    Console.WriteLine(k);//10

  合併運算子(??)常常和null 條件運算子(?.),用於實現一種實現預設值得情況:  

    public class MyClass
    {
        public int Value { get; set; }
    }

    void Method(MyClass myClass)
    {
        //如果只是用?.運算,那麼value將會值int?型別,這時可以結合??運算,value就會是int型別
        var value = myClass?.Value ?? 1;//相當於說:如果myClass=null,那麼myClass?.Value返回null,??就會返回1給value變數
    }

  此外,合併運算子(??)也可以類似級聯的使用,與null 條件運算子(?.)一起使用時,可能會有意想不到的簡潔:  

    //??合併運算子類似級聯
    var a = A ?? B ?? C ?? D ?? default;
    //與null 條件運算子(?.)一起使用,想想如果是使用if會有多麼繁瑣
    var b = A?.B?.C() ?? D?.E?.F() ?? G?.H?.I() ?? default;

  合併運算子(??)還經常和throw表示式結合,用於簡化空指標判斷,它常出現在建構函式和屬性構造器中:  

    public class MyClass
    {
        public MyClass(string name)
        {
            this.name = name ?? throw new ArgumentNullException(nameof(name));
        }

        string name;
        public string Name
        {
            get => name;
            set => name = value ?? throw new ArgumentNullException(nameof(value));
        }
    }

  ??=也稱為合併賦值運算子,是合併運算子(??)的一個特殊情況,它類似於+=、-=等運算,用於簡化賦值運算:  

    a ??= b
    
    //等價於

    a = a ?? b

    //等價於

    if (a is null)
    {
        a = b;
    }

  同樣的,??=也具有賦值運算子的一些特性,如:  

    a ??= b ??= c

  總結

  有關問號(?)的這些語法糖可以方便我們的開發,而且像ECMAScript也提出可選鏈操作符(?.)和空值合併運算子(??)的語法,所以這些語法糖是值得我們學習。

  此外,不知道你發現沒有,除了三元運算子,包含?的運算子多多少少都和null有關,可能以後還會出現和?結合的運算子,但是不出意外的話也會和null有關吧。

  參考文件:

  https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types

  https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-

  https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator

一個專注於.NetCore的技術小白