C# 6.0 的?.运算符
What is it? Here’s the scenario
Consider getting the grandchild of a parent object like this:
var g1 = parent.child.child.child;
很明显parent类里面有一个child类的成员,child类又包含child类的成员,这是一个递归调用,child.child.child....可以延伸到无限级。
Okay, so, this is some poor coding because the value of child could be null. If I attempt to ask for the value of child and the containing object is null, a NullReferenceException will be raised and my app will crash.
Here’s what I mean. Consider this:
var g1 = [parent].[child].[null].child;
In this case, I was lucky enough to get two levels in before I hit a null object. But I did hit a null object, and as a result the runtime will thrown an exception.
Instead, you might add some error checking and do this:
// variation 1 var item = this.Parent; item = (item == null) ? null : item.child; item = (item == null) ? null : item.child; var g1 = (parent == null) ? null : item.child; if (g1 != null) // TODO
// variation 2 var g1 = (Child)null; var item = this.Parent; if (item != null) { item = item.Child; if (item != null) { item = item.Child; if (item != null) { g1 = item.Child; } } } if (g1 != null) // TODO
Good. Now, this is safe and effective coding. The sample above shows two of many potential approaches. The first using the ternary(三元的) operator in C#, the second using simple conditional blocks. As we know, we cannot ask for a child property from an object that is null. So, we must check each level.
The conditional code is not difficult to write, but it certainly impacts the size of your code, the complexity of your code, and the readability of your code. Having said that, it’s what we all do today. Code like this shows up all the time and there is nothing wrong with that; but, what if there were a better way.
How the new operator works
Consider getting the grandchild of a parent object like this:
var g1 = parent?.child?.child?.child; if (g1 != null) // TODO
Wow! That’s the equivalent of testing every single level, but in a single line of code. The “?.” operator is basically saying, if the object to the left is not null, then fetch what is to the right, otherwise return null and halt the access chain.
It’s a wonderful addition to the language’s syntax.
Furthermore
Mad’s Visual Studio User Voice comment continued with a little more explanation of the operator’s implementation. He said, “If the type of e.x (etc) is a non-nullable value type S, then the type of e?.x is S?. Otherwise the type of e?.x is the same as that of e.×. If we can’t tell whether the type is a non-nullable value type (because it is a type parameter without sufficient constraints) we’ll probably give a compile-time error.” This comment, and the idea that a method call or indexer can be to the right of the operator are just candy.
> Now, let’s add NonNullable reference types and we’re really cooking.
此外如果使用!运算符声明类为NonNullable类型,再使用?.运算符时会导致编译错误,如下所示:
public void DoSomething(Order! order) { Customer customer = order?.Customer; // Compiler error: order can’t be null }
因为这时order对象永远不可能为null,C#编译器认为在order后使用?.是多此一举
注意,如果在?.或?[]运算符右边最终返回的是非空类型(值类型),那么实际上?.或?[]运算符会返回该非空类型(值类型)的nullable value type,如下代码所示:
using System; using System.Net.Http.Headers; namespace NetCoreNullConditional { enum MyType { Normal = 0, Abnormal } struct MyStruct { } class MyClass { public MyType myType = MyType.Normal; public MyStruct myStruct = new MyStruct(); } class MyParentClass { public MyClass myClass = new MyClass(); } class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); //MyStruct myStruct = myClass?.myStruct;//编译错误:CS0266 Cannot implicitly convert type 'NetCoreNullConditional.MyStruct?' to 'NetCoreNullConditional.MyStruct'. MyStruct? myStruct1 = myClass?.myStruct; Nullable<MyStruct> myStruct2 = myClass?.myStruct; //MyType myType = myClass?.myType;//编译错误:CS0266 Cannot implicitly convert type 'NetCoreNullConditional.MyType?' to 'NetCoreNullConditional.MyType'. MyType? myType1 = myClass?.myType; Nullable<MyType> myType2 = myClass?.myType; MyParentClass myParentClass = new MyParentClass(); //MyStruct myStruct = myParentClass?.myClass.myStruct;//编译错误:CS0266 Cannot implicitly convert type 'NetCoreNullConditional.MyStruct?' to 'NetCoreNullConditional.MyStruct'. myStruct1 = myParentClass?.myClass.myStruct; myStruct2 = myParentClass?.myClass.myStruct; int[] numbers = new int[] { 1, 2, 3 }; //int number = numbers?[1];//编译错误:CS0266 Cannot implicitly convert type 'int?' to 'int'. int? number1 = numbers?[1]; Nullable<int> number2 = numbers?[1]; Console.WriteLine("Press any key to end..."); Console.ReadKey(); } } }
我们在上面的代码示例中,尝试用?.或?[]运算符返回结构体和枚举,可以看到实际上?.或?[]运算符返回的都是结构体和枚举的可空值类型(nullable value type),如果取消上面代码中的四个注释行,会发生编译错误,因为我们不能将可空值类型(nullable value type)赋值给值类型。
关于 ?.或?[]运算符,还可以参考:
"Member access operators and expressions (C# reference)"中的章节"Null-conditional operators ?. and ?[]"
Null-Conditional Operator in C# (?.)
C# 6 features – Null-conditional (?. and ?[]) and null-coalescing (??) operators