C# 7编程模式与实践(8)

前一类的例子是Ruby语言,Ruby中的声明也是表达式。与之相对比,后一类的代表性例子是Visual Basic。VB的语句和表达式间有着明显的差别。例如,if语句在独立使用时与作为大型表达式的一部分使用时,具有完全不同的语法。

C#基本上可以归为第二类,但是由于其源自于C语言,也可将赋值语句看成是表达式。在C#中允许编写如下代码:

while ((current = stream.ReadByte()) != -1) { //执行具体工作的代码。 }

C# 7首次允许非赋值语句做为表达式使用。无需对语法做任何更改,就可在正常表达式的任意位置放置“throw”语句。下面是Mads Torgersen在发行声明中所给出的例子:

class Person { public string Name { get; } public Person(string name) => Name = name ?? throw new ArgumentNullException("name"); public string GetFirstName() { var parts = Name.Split(' '); return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!"); } public string GetLastName() => throw new NotImplementedException(); }

很容易看出每个例子所执行的功能。但是如果我们移动了代码中throws表达式的位置,那么会发生什么?例如:

return (parts.Length == 0) ? throw new InvalidOperationException("No name!") : parts[0];

现在代码就不容易读懂了。虽然左右两边的语句是相关的,但是中间的语句与两者完全无关。从结构上看,第一个版本左边给出的是“正确路径”,右边给出的是错误路径。第二个版本中,错误路径将正确路径分隔为两部分,破坏了整个流程。

(点击放大图像)

C# 7编程模式与实践

让我们再看一个例子。在下面的代码中,我们添加了一个函数调用:

void Save(IList<Customer> customers, User currentUser) { if (customers == null || customers.Count == 0) throw new ArgumentException("No customers to save"); _Database.SaveEach("dbo.Customer", customers, currentUser); } void Save(IList<Customer> customers, User currentUser) { _Database.SaveEach("dbo.Customer", (customers == null || customers.Count == 0) ? customers : throw new ArgumentException("No customers to save"), currentUser); }

这时我们发现代码行过于冗长,尽管有时用LINQ也会编写出十分长的代码行。为了改进代码的可读性,我们使用橙色标记条件部分,函数调用蓝色标出,函数参数标为黄色,错误路径标为红色。

(点击放大图像)

这样我们就能看出,上下文是如何随参数位置的改变而发生变化的。

throw表达式的指导原则

在赋值和返回语句中,考虑将throw表达式置于条件(a ? b : c)和空值合并(x ?? y)操作符的左侧。

X 不要将throw表达式置于条件操作符的中间位置。

X 不要在函数的参数列表中放置throw表达式。

要详细了解异常是如何影响API设计的,参见“.NET异常设计原则”一文。

模式匹配与switch语句的改进

模式匹配改进了switch语句,但并未影响API的设计。因此,虽然模式匹配的确可以简化异构集合类的操作,但是如有可能,最好还是使用共享接口和多态。

这也就是说,有一些实现细节值得考虑。看一下在八月份的发布中所给出的例子:

switch(shape) { case Circle c: WriteLine($"circle with radius {c.Radius}"); break; case Rectangle s when (s.Width == s.Height): WriteLine($"{s.Width} x {s.Height} square"); break; case Rectangle r: WriteLine($"{r.Width} x {r.Height} rectangle"); break; default: WriteLine("<unknown shape>"); break; case null: throw new ArgumentNullException(nameof(shape)); }

以前,case表达式中选项的出现次序是无关紧要的。但是在C# 7中提供了类似于Visual Basic的机制,switch语句几乎是严格地按声明次序进行求值。这一方式对于when表达式同样适用。

实际上,正如在一系列的if-else-if语句中那样,最常见的情况应该成为switch语��块的第一个选项。类似地,如果存在开销很大的情况检查,应该将该选项尽可能置于switch语句底部,使得只是在有必要时才被执行。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/82947a4b5f7533883a344c52de3e78ac.html