方法
方法是包含一系列语句的代码块。 程序通过调用该方法并指定任何所需的方法参数使语句得以执行。 在 C# 中,每个执行的指令均在方法的上下文中执行。 Main 方法是每个 C# 应用程序的入口点,并在启动程序时由公共语言运行时 (CLR) 调用。
规范
要为方法名使用动词或动词短语。
1 | System.Console.Write("Enter Your first name: ") |
解析
System
命名空间Console
类型名称Write
方法名称"Enter Your first name: "
实参
命名空间
命名空间是一种分类机制,用于组合功能相关的所有类型。System 命名空间包含了用于执行大量基本变成活动的类型。
常见命名空间
命名空间 | 描述 |
---|---|
System | 主命名空间 |
System.Collections.Generic | 泛型集合接口和类 |
System.Collections | 使用Arraylist |
System.Linq | 语言集成查询 |
System.Text | 编码方式 |
System.Text.RegularExpressions | 正则表达式 |
System.Threading | 多线程 |
System.Windows.Forms | winform窗体 |
System.Windows.Forms.Control | winform控件 |
System.IO | 操作文件、文件夹 |
System.Data | 操作ado.net |
System.Data.SqlClient | 操作ado.net |
System.Net | 操作网络 |
System.Net.Sockets | 操作网络套接字 |
System.Management | 获取硬件信息(需要添加引用) |
System.Drawing | 绘制系统 |
System.Xml | 操作xml |
System.Media | 播放wav和系统wav文件 |
Microsoft.Win32 | 操作注册表 |
System.Runtime.InteropServices | 引用dll |
System.Security.Cryptography | 加密解密 |
System.Text.RegularExpressions | 正则表达式 |
Microsoft.VisualBasic | 简繁体转换(需要添加引用) |
System.Diagnostics | 调试输出 |
System.Web.UI | web页面 |
System.Web.UI.Control | web控件 |
System.Configuration | 配置信息 |
System.DateTime | 操作时间 |
System.Math | 操作数字类 |
Microsoft.Win32.Registry | 操作注册表 |
方法的声明和调用
在类或者结构中声明方法。
例子
1 | class IntroducingMethods { |
初学者主题:用方法进行重构
将一组相关语句转移到一个方法中,而不是把它们留在一个较大的方法中,这是重构 refactoring
的一种形式。
- 重构有助于减少重复代码,因为可以从多个位置调用方法,而不必在每个位置都重复这个方法的代码。
- 重构还有助于增强代码的可读性。
方法的返回类型和返回值
方法可以将值返回给调用方。 如果列在方法名之前的返回类型 不是 void
,则该方法可通过使用 return
关键字返回值。 带 return
关键字,后跟与返回类型匹配的值的语句将该值返回到方法调用方。
return
语句并非只能在方法末尾出现,例如,if
或 switch
语句中可以包含 return
语句,举例如下:
1 | class Program |
如果 return
之后有“不可达”的语句,编译器会发出警告,指出有永远执行不到的语句。
指定 void
作为返回类型,表示没有返回值,所以,return
可有可无
。
using 指令
C# 允许简写类的全名,为此,要在文件的顶部列出类的命名空间(名称空间),前面加上 using
关键字。在文件的其他地方,就可以使用其类型名称来引用空间中的类型了。
例子
1 | // The using directive imports all types from the |
如果要引用某个命名空间的子命名空间中的类型,同样需要在文件顶部用 using 语句明确指定子命名空间引用。例如,要访问 System.Text
中的 StringBuilder
类型,必须增加一个 using System.Text;
指令,或者使用 Syste.Text.StringBuilder
对类型进行完全限定,而不能仅仅写成 Text.StringBuilder
。
使用别名
可以利用 using 指令为命名空间或类型取一个别名。别名alias是在using指令起作用范围内可以使用的替代名称。别名的两个最常见的用途是消除两个同名类型的歧义和缩写长名称。
例如,System.Timers 中存在 Timer 类型,System.Threading 中也存在一个 Timer 类型,当这两个命名空间同时存在并同时需要被引用时,需要用别名来加以区分:
1 | using System; |
如果别名跟要区别的类型名相同,或者说别名占用了类型名的名字,那么如果要引用另外一个类型,就必须完全限定或者定义新的不同的别名,如下例:
1 | using System; |
上例中,Timer 时别名,如果要引用 System.Threading.Timer类型,必须完全限定或者定义不同的别名。这又是要搞么?😥
高级主题:嵌套的 using 指令
也可以在命名空间内部使用 using
指令,有效范围限于命名空间内部。不过,很少这样使用。
1 | namespace EssentialCSharp |
Main() 和命令行参数
Main
方法是 C# 应用程序的入口点。 (库和服务不要求使用 Main
方法作为入口点)。Main
方法是应用程序启动后调用的第一个方法。
C# 程序中只能有一个入口点。 如果多个类包含 Main
方法,必须使用 /main
编译器选项来编译程序,以指定将哪个 Main
方法用作入口点。 有关详细信息,请参阅 /main(C# 编译器选项)。
概述
Main
方法是可执行程序的入口点,也是程序控制开始和结束的位置。Main
在类或结构中声明。 Main 必须是静态方法,不得为公共方法。 (在前面的示例中,它获得的是私有成员的默认访问权限)。封闭类或结构不一定要是静态的。Main
可以具有 void、int,或者以 C# 7.1、Task 或 Task返回类型开头。 - 当且仅当
Main
返回 Task 或 Task时, Main
的声明可包括 async 修饰符。 请注意,该操作可明确排除async void Main
方法。 - 使用或不使用包含命令行自变量的 string[] 参数声明
Main
方法都行。 使用 Visual Studio 创建 Windows 应用程序时,可以手动添加此形参,也可以使用 Environment 类来获取命令行实参。 参数被读取为从零开始编制索引的命令行自变量。 与 C 和 C++ 不同,程序的名称不被视为第一个命令行自变量。
命令行参数
可以通过以下方式之一定义方法来将自变量发送到 Main 方法:
1 | static int Main(string[] args) |
1 | static void Main(string[] args) |
程序运行时,通过 string 数组参数将命令行参数传递给 Main()。要获取参数,操作数组就可以了。
示例
命令行输入 | 传递给 Main 的字符串数组 |
---|---|
executable.exe a b c | “a” “b” “c” |
executable.exe one two | “one” “two” |
executable.exe “one two” three | “one two” “three” |
实例说明
1 | class CommandLine { |
高级主题:多个 Main() 方法
如果多个类包含 Main 方法,对 csc.exe
使用 /main:class
选项将指定包含程序入口点的类。
示例
编译 t2.cs 和 t3.cs,指出 Main 方法可在 Test2 中找到:
csc t2.cs t3.cs /main:Test2
方法的参数
按引用传递和按值传递参数
默认情况下,值类型传递给方法时,传递的是副本而不是对象本身。 因此,对参数的更改不会影响调用方法中的原始副本。 可以使用 ref
关键字按引用传递值类型。 有关详细信息,请参阅传递值类型参数。 有关内置值类型的列表,请参阅值类型表。
引用类型的对象传递到方法中时,将传递对对象的引用。 也就是说,该方法接收的不是对象本身,而是指示该对象位置的参数。 如果通过使用此引用更改对象的成员,即使是按值传递该对象,此更改也会反映在调用方法的参数中。
例子
1 | using System; |
在 C# 中,实参可以按值或按引用传递给形参。 按引用传递使函数成员、方法、属性、索引器、运算符和构造函数可以更改参数的值,并让该更改在调用环境中保持。 若要按引用传递参数,请使用 ref
或 out
关键字。
例子
1 | using System; |
引用参数 ref
在方法的参数列表中使用 ref
关键字时,它指示参数按引用传递,而非按值传递。 按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref
参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象。
若要使用
ref
参数,方法定义和调用方法均必须显式使用ref
关键字,如下面的示例所示。
1 | using System; |
输出参数 out
out
关键字通过引用传递参数。 它与 ref
关键字相似,只不过 ref
要求在传递之前初始化变量。 若要使用 out
参数,方法定义和调用方法均必须显式使用 out
关键字。 例如:
1 | using System; |
调用具有 out 参数的方法
在 C# 6
及更早版本中,必须先在单独的语句中声明变量,然后才能将其作为 out
参数传递。 下面的示例先声明了变量 number,然后再将它传递给将字符串转换为数字的 Int32.TryParse
方法。
1 | using System; |
从 C# 7
开始,可以在方法调用的参数列表而不是单独的变量声明中声明 out
变量。 这使得代码更简洁可读,还能防止在方法调用之前无意中向该变量赋值。 下面的示例与上一个示例基本相同,不同之处在于它在对 Int32.TryParse
方法的调用中定义了 number
变量。
1 | using System; |
数组参数
参数以数组形式体现,即数组参数。数组可以作为实参传递给方法形参。由于数组是引用类型,因此方法可以更改元素的值。
将一维数组作为参数传递
- 可将初始化的一维数组传递给方法。 例如,下列语句将一个数组发送给了 Print 方法。
1 | int[] theArray = { 1, 3, 5, 7, 9 }; |
- 可在同一步骤中初始化并传递新数组,如下例所示。
1 | PrintArray(new int[] { 1, 3, 5, 7, 9 }); |
将多维数组作为参数传递
- 通过与传递一维数组相同的方式,向方法传递初始化的多维数组。
1 | int[,] theArray = { { 1, 2 }, { 2, 3 }, { 3, 4 } }; |
- 可在同一步骤中初始化并传递新数组,如下例所示。
1 | Print2DArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }); |
示例
在下列示例中,初始化一个整数的二维数组,并将其传递至 Print2DArray 方法。 该方法将显示数组的元素。
1 | class ArrayClass2D { |
示例
传递一个长度可变的参数列表
1 | using System; |
数组参数需要注意的地方
- 参数不一定是方法的唯一参数,但必须时方法声明中的最后一个参数。所以,只能有一个数组参数;
- 调用者可以显式地使用数组,而不必是以都好分隔的参数列表。最终生成的CIL代码是一样的。
递归
递归是一项非常重要的编程技巧,它使函数调用其本身。
示例
返回目录中所有
.cs
文件代码行总数:
1 | using System; |
示例
遍历目录中所有目录和文件,在终端中格式化输出:
1 | using System; |
递归很容易陷入无限递归调用,编码/调试时多注意。
方法重载 Mathod Overloading
一个类包含两个或者更多同名的方法,就会发生方法重载。以WriteLine()方法为例,可向它传递一个格式字符串和其他一些参数,也可以只传递一个整数。两者的实现肯定不同,但在逻辑上,对于调用者,这个方法就是负责输出数据,至于方法内部如恶化实现,调用者并不关心。
使用重载统计.cs文件中的行数
1 | using System.IO; |
可选参数(可选实参)
方法、构造函数、索引器或委托的定义可以指定其形参为必需还是可选。 任何调用都必须为所有必需的形参提供实参,但可以为可选的形参省略实参。
每个可选形参都有一个默认值作为其定义的一部分。 如果没有为该形参发送实参,则使用默认值。 默认值必须是以下类型的表达式之一:
- 常量表达式;
- new ValType() 形式的表达式,其中 ValType 是值类型,例如 enum 或 struct;
- default(ValType) 形式的表达式,其中 ValType 是值类型。
可选参数定义于参数列表的末尾
和必需参数之后
。 如果调用方为一系列可选形参中的任意一个形参提供了实参,则它必须
为前面的所有可选形参提供实参。例如,在以下代码中,使用一个必选形参和两个可选形参定义实例方法 ExampleMethod
。
1 | public void ExampleMethod(int required, string optionalstr = "default string", int optionalint = 10) |
下面对 ExampleMethod 的调用会导致编译器错误,原因是为第三个形参而不是为第二个形参提供了实参。
//anExample.ExampleMethod(3, ,4);
但是,如果知道第三个形参的名称,则可以使用命名实参
来完成此任务。
anExample.ExampleMethod(3, optionalint: 4);
命名参数(命名实参)
命名参数
是 C# 4.0
新增的方法调用功能。有了命名实参,你将不再需要记住或查找形参在所调用方法的形参列表中的顺序。 每个实参的形参都可按形参名称进行指定。
如果你不记得参数的顺序,但知道其名称,你可以按任何顺序发送自变量。
PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);
高级主题:方法解析
当编译器必须从一系列“适用”的方法中训责一个最适合某个特定调用的方法时,会选择拥有最据提的参数类型的那个方法。
如果有多个适用的方法,但无法从中挑选出最具唯一性的,编译器就会宝座,指明调用存在的歧义。
例如,给定以下的方法:
1 | static void Method(object thing){} |
调用 Method(42)
会被解析成 Method(int thing)
,因为存在着一个从实参类型到形参类型的完全匹配的方法。如果删除该方法,那么重载解析会选择 long
版本,因为 long
比 double
和 object
更具体。
异常处理语句
C# 提供用于处理反常情况(称为异常,可能会在执行程序的过程中发生)的内置支持。 这些异常由正常控制流之外的代码进行处理。
try-catch
Try
块之后必须紧跟着一个或多个 catch
块(或/和一个 Finally
块),这些子句指定不同异常的处理程序。只要数据类型与异常类型匹配,对应的 catch 块就会执行。
在前面例子“遍历目录中所有目录和文件,在终端中格式化输出”中,如果权限不够,就会引发异常,下面例子加入异常处理:
1 | using System; |
throw
Throw
会抛出/传递程序执行期间出现异常的信号,通过在catch
块里使用throw
语句.可以改变产生的异常,比如我们可以抛出一个新的异常。
C#允许开发人员从代码中引发异常,如下例所示:
1 | using System; |
有时catch
块能捕捉到异常,但不能正确或者完整处理它。这种情况下,可以让这个catch
块重新抛出异常,据提办法时使用一个单独的throw
语句,不要在它后面指定任何异常。
throw;
和throw exception;
的区别:throw;
保持了异常中的“调用栈”信息,而throw exception;
将那些信息替换成当前调用栈信息。如下例所示:
1 | using System; |
throw;
|throw exception;
|throw new exception;
区别示例:
1 | using System; |