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 

 

posted @ 2022-02-10 14:14  没有星星的夏季  阅读(7614)  评论(0编辑  收藏  举报