C# 8.0本质论
上QQ阅读APP看书,第一时间看更新

5.1 方法的调用

初学者主题:什么是方法

目前在程序中写的所有语句其实都在一个名为Main()的方法内。随着程序逐渐变大,方法很快就会变得难以维护,且可读性越来越差。

方法组合一系列语句以执行特定操作或计算特定结果。它能为构成程序的语句提供更好的结构和组织。假定要用Main()方法统计某个目录下源代码的行数,不是在一个巨大的Main()方法中写所有代码,而是提供更简短的版本,隐藏每个方法的实现细节,如代码清单5.1所示。

代码清单5.1 语句组合成方法

这里没有将所有语句都放到Main()中,而是把它们划分在多个方法中。例如,程序先用一系列System.Console.WriteLine()语句显示帮助文本,这些语句全部放在DisplayHelpText()方法中。类似地,用GetFiles()方法获取要统计行数的文件。最后调用CountLines()方法实际统计行数,调用DisplayLineCount()方法显示结果。一眼就能看清楚整个程序的结构,因为方法名清楚地描述了方法的作用。

设计规范

·要为方法名使用动词或动词短语。

方法总是和类型(通常是)关联。类型将相关方法分为一组。

方法通过实参接收数据,实参由方法的参数或形参[1]定义。参数是调用者(发出方法调用的代码)用于向被调用的方法(例如Write()、WriteLine()、GetFiles()、CountLines()等)传递数据的变量。在代码清单5.1中,files和lineCount分别是传给CountLines()和DisplayLineCount()方法的实参。方法通过返回值将数据返回调用者。在代码清单5.1中,GetFiles()方法调用的返回值被赋给files。

这里重新讨论一下第1章讲过的System.Console.Write()、System.Console.WriteLine()和System.Console.ReadLine()方法。这次要从方法调用的角度讨论,而不是强调控制台的输入和输出细节。代码清单5.2展示了这三个方法的应用。

代码清单5.2 简单方法调用

方法调用由方法名称、实参列表和返回值构成。完全限定的方法名称包括命名空间、类型名和方法名,每部分以句点分隔。稍后会讲到,调用方法时经常只使用方法名称,而不必完全限定。

5.1.1 命名空间

命名空间是一种分类机制,用于分组功能相关的所有类型。命名空间是分级的,级数任意,但超过6级就很罕见了。一般从公司名开始,然后是产品名,最后是功能领域。例如在Microsoft.Win32.Networking中,最外层的命名空间是Microsoft,它包含内层命名空间Win32,后者又包含嵌套更深的Networking命名空间。

命名空间主要用于按功能领域组织类型,以便查找和理解这些类型。此外,命名空间还有助于防范类型名称冲突。两个都叫Button的类型只要在不同命名空间,比如System.Web.UI.WebControls.Button和System.Windows.Controls.Button,编译器就能区分。

在代码清单5.2中,Console类型在System命名空间中。System命名空间包含用于执行大量基本编程活动的类型。几乎所有C#程序都要使用System命名空间中的类型。表5.1总结了其他常用命名空间。

调用方法并非一定要提供命名空间。例如,假定要调用的方法与发出调用的方法在同一个命名空间,就没必要指定命名空间。本章稍后会讲解如何利用using指令避免每次调用方法都指定命名空间限定符。

表5.1 常用命名空间

设计规范

·要为命名空间使用PascalCase大小写。

·考虑组织源代码文件目录结构以匹配命名空间层次结构。

5.1.2 类型名称

调用静态方法时,如果目标方法和调用者不在同一个类型(或基类)中,就需要添加类型名称限定符。(本章稍后会介绍如何用using static指令省略类型名称。)例如,从HelloWorld.Main()中调用静态方法Console.WriteLine()时就需要添加类型名称Console。但和命名空间一样,如果要调用的方法是调用表达式所在类型的成员,C#就允许在调用时省略类型名称(代码清单5.4展示了一个例子)。之所以不需要类型名称,是因为编译器能够根据调用位置推断类型。显然,如果编译器无法进行这样的推断,就必须将类型名称作为方法调用的一部分。

类型本质上是对方法及其相关数据进行分组的一种方式。例如,Console类型包含常用的Write()、WriteLine()和ReadLine()等方法。所有这些方法都在同一个“组”中,都从属于Console类型。

5.1.3 作用域

第4章讲过,事物的“作用域”是可用非限定名称引用它的那个区域。两个方法在同一个类型中声明,一个方法调用另一个就不需要类型限定符,因为这两个方法具有整个包容类型的作用域。类似地,类型的作用域是声明它的那个命名空间。所以,特定命名空间的一个类型中的方法调用不需要指定该命名空间。

5.1.4 方法名称

每个方法调用都要指定一个方法名称。如前所述,它可能用也可能不用命名空间和类型名称加以限定。方法名称之后是圆括号中的实参列表,每个实参以逗号分隔,对应于声明方法时指定的形参。

5.1.5 形参和实参

方法可获取任意数量的形参,每个形参都具有特定的数据类型。调用者为形参提供的值称为实参,每个实参都要和一个形参对应。例如,以下方法调用有三个参数:

该方法位于File类中,后者位于System.IO命名空间中。方法声明为有三个参数,第一个和第二个是string类型,第三个是bool类型。本例传递string变量oldFileName和newFileName代表旧的和新的文件名,第三个参数传递false,用于判断在新文件名存在的情况下文件拷贝失败的情形。

5.1.6 方法返回值

和System.Console.WriteLine()相反,代码清单5.2中的System.Console.ReadLine()没有任何参数,因为该方法声明为没有参数。但这个方法有返回值。可利用返回值将调用方法所产生的结果返回调用者。因为System.Console.ReadLine()有返回值,所以可将返回值赋给变量firstName。还可将方法的返回值作为另一个方法的实参使用,如代码清单5.3所示。

代码清单5.3 将方法返回值作为实参传给另一个方法调用

代码清单5.3不是先为变量赋值,再在System.Console.WriteLine()调用中使用这个变量。相反,是在调用System.Console.WriteLine()时直接调用System.Console.ReadLine()方法。运行时会先执行System.Console.ReadLine()方法,返回值直接传给System.Console.WriteLine()方法,而不是传给一个变量。

并非所有方法都返回数据,System.Console.Write()和System.Console.WriteLine()就是如此。稍后会讲到这种方法指定了void返回类型,好比在HelloWorld的例子中,Main的返回类型就是void。

5.1.7 对比语句和方法调用

代码清单5.3演示了语句和方法调用的差异。System.Console.WriteLine("Hello {0}!", System.Console.ReadLine());语句包含两个方法调用。语句通常包含一个或多个表达式,本例有两个表达式都是方法调用。所以,方法调用构成了语句的不同部分。

虽然在一个语句中包含多个方法调用能减少编码量,但不一定能增强可读性,而且很少能带来性能上的优势。开发者应更注重代码的可读性,而不要将过多精力放在写简短的代码上。

注意 通常,开发者应更注重代码的可读性,而不要将过多精力放在写简短的代码上。为了使代码一目了然,进而在长时间里更容易维护,可读性是关键。

[1] 以后不需要区分形参和实参时一般以“参数”代之。——译者注