回到LookupName例子,如果一个命名变量仅在被局部变量替换前短暂使用,看上去创建这样的变量好像是自找麻烦。C# 7中使用一种称为“析构”的方法解决了这个问题。该语法有多种变体,例如:
(string first, string last) = LookupName(0); (var first, var last) = LookupName(0); var (first, last) = LookupName(0); (first, last) = LookupName(0);上例中的最后一行,我们假定变量first和last已事先声明。
析构函数虽然析构函数从名字上看像是“毁灭者”,但是析构函数与对象销毁毫无关系。正如构造函数将各个独立值组合成一个对象,析构函数输入一个对象并分离对象中的各个值。析构函数允许任何类使用如上所示的析构语法。让我们看一下Rectangle类,它具有如下的构造函数:
public Rectangle(int x, int y, int width, int height)在一个新的实例上调用ToString方法时,会得到“{X=0,Y=0,Width=0,Height=0}”。这些事实组合在一起,指明了自定义析构方法中字段的提供顺序。
public void Deconstruct(out int x, out int y, out int width, out int height) { x = X; y = Y; width = Width; height = Height; } var (x, y, width, height) = myRectangle; Console.WriteLine(x); Console.WriteLine(y); Console.WriteLine(width); Console.WriteLine(height);你可能会有疑问,为什么在此使用的是输出参数,而不是返回元组。这部分原因是出于性能上的考虑,因为这种做法减少了需拷贝的数量。但是Microsoft这样做的最主要原因在于,它为重载Deconstruct开启了便利之门。
继续研究上面的例子。我们注意到,Rectangle类还有另一个构造函数:
public Rectangle(Point location, Size size);我们构建与之相匹配的析构方法:
public void Deconstruct(out Point location, out Size size); var (location, size) = myRectangle;每个析构方法需要具有不同的参数数量。否则,即便类型是显式列出的,编译器还是无法确定应使用哪个析构方法。
从API设计的角度看,析构函数通常更适用于结构体。在一些类上或许不能有析构方法,尤其是Customer和Employee这样的模型或DTO(数据传输对象,Data Transfer Object)。一些问题并不存在可满足每个人需要的解决方法,例如,“应该使用(firstName, lastName, phoneNumber, email),还是(firstName, lastName, email, phoneNumber)?”。
析构函数的指导原则在读取元组返回值时应考虑使用析构函数,但要注意误标识的问题。
结构体一定要提供自定义的析构方法。
类构造函数、ToString覆写和析构方法一定要匹配函数中字段的顺序。
如果一个结构体有多个构造函数,那么可以考虑提供多个析构方法。
应考虑对大型的值元组立即进行析构。规模大于16个字节的大型ValueTuple的重复拷贝开销很大。注意:在32位操作系统中,引用变量总是4个字节,而在64位操作系统中总是8个字节。
X 如果不清楚字段的出现顺序,就不要在类上暴露析构方法。
X 不要声明具有相同参数数量的多个析构方法。
out变量C# 7对调用具有“out”参数的函数提供了两种语法。一种是在函数调用中声明变量。例如:
if (int.TryParse(s, out var i)) { Console.WriteLine(i); }另一种用法是使用“通配符”,完全无需顾及输出参数。例如:
if (int.TryParse(s, out _)) { Console.WriteLine("success"); }如果你使用过C# 7预览版,那么你可能已经注意到,忽略参数由原来的使用星号(“*”)改为使用下划线了。这一语法修改的部分原因在于,下划线已在函数式编程语言中广为使用。还可考虑使用关键字“void”或“ignore”。
虽然通配符用起来非常便利,但另一方面也意味着存在API设计上的缺陷。大多数情况下仅提供一个忽略out参数的重载函数即可,out参数一般也会被忽略。
out变量的指导原则译者注: 本文在InfoQ发表后,原文作者根据社区的反馈对部分内容进行了更新:“我们不再建议完全避免使用大型的ValueTuple,而是建议开发人员应考虑尽快对它们进行析构。拷贝大型ValueTuple的开销依然很大。与将每个值作为out参数传递相比,拷贝的开销更大。”
局部函数和迭代器