Skip to Content
Week 01Day 5 - 方法与参数

Day 5 - 方法与参数

建议用时:180-210 分钟

你将学会什么

  • 方法是什么,为什么不能把代码一直堆在 Program.cs
  • 方法名、参数、返回值、方法体分别是什么
  • 形参和实参有什么区别
  • void 表示什么,什么时候用
  • return 是返回结果还是提前结束
  • 局部变量和作用域是什么
  • 方法重载是什么,什么时候有用
  • 如何把输入、校验、计算、输出拆成多个方法

本页只讲 C# 基础。今天的目标不是把代码拆得很多,而是让每个方法只负责一件清楚的事。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

从 Day 4 走到 Day 5

Day 4 的小票程序已经能完成输入、计算和输出,但所有代码都挤在一起。Day 5 不增加新的业务功能,只做一件事:给已有代码分组并起名字。

Day 4:decimal total = price * count; Day 5:把这行计算装进 CalculateTotal 方法

所以今天不是重新学一套 C#。你会继续使用 Day 4 的变量、判断和计算,只是把它们整理成有名字的动作。

今天的最低通过线

第一次学习只要求做到:

  • 看见 CalculateTotal(10m, 2),知道这是“调用方法”。
  • 看见 decimal CalculateTotal(...),知道这是“定义方法”。
  • 知道参数是方法需要的数据,返回值是方法交回的结果。
  • 能独立写出一个接收两个数字并返回相加结果的方法。

out、方法重载和复杂输入方法属于第二遍内容。第一次看不懂,可以先跳过,不影响完成今天的核心练习。

第一部分:先学原理和最小知识

这一部分先解决“这是什么、为什么要这样写”。每看到一个短例子就亲手敲一遍并运行,不要先背完整程序。

1. 方法到底是什么

方法就是一段有名字、可以重复使用的代码

没有方法时,如果要打印两个标题,只能重复写:

Console.WriteLine("==== 商品小票 ===="); Console.WriteLine("==== 库存报表 ====");

把“打印标题”这件事写成方法后,先写方法定义,再调用它:

void PrintTitle(string title) { Console.WriteLine("==== " + title + " ===="); } PrintTitle("商品小票"); PrintTitle("库存报表");

按从上往下阅读的顺序看:

第 1 步:定义 PrintTitle,说明这个方法怎么工作 第 2 步:调用 PrintTitle,把“商品小票”传进去 第 3 步:再次调用 PrintTitle,把“库存报表”传进去

从今天开始,本课程的入门示例统一采用“先定义方法,后调用方法”。C# 编译器虽然也允许某些方法写在调用位置后面,但那是语法允许的排版,不适合作为第一次学习方法时的阅读顺序。

2. 一个方法由哪几部分组成

先只看定义,不急着调用:

decimal CalculateTotal(decimal price, int count) { return price * count; }

它由 4 个部分组成:

部分这里是什么用人话解释
返回类型decimal方法做完后会交回一个小数
方法名CalculateTotal这个动作叫“计算总价”
参数列表decimal price, int count方法工作前需要单价和数量
方法体{ ... }方法真正要执行的代码

定义方法时,末尾不要写分号:

decimal CalculateTotal(decimal price, int count) { return price * count; }

调用方法时,才在末尾写分号:

decimal total = CalculateTotal(19.9m, 3);

3. 方法有几种

第一次学习方法,不要先背十几个名字。只问两个问题:

  1. 这个方法需要外面给它数据吗?
  2. 这个方法做完后需要交回结果吗?

组合起来就是 4 种基础方法:

种类有参数吗有返回值吗适合做什么
第 1 种没有没有固定打印一句话
第 2 种没有根据传入内容执行动作
第 3 种没有取得一个固定或内部产生的结果
第 4 种根据传入数据计算并交回结果

第 1 种:无参数、无返回值

void SayHello() { Console.WriteLine("你好,开始学习方法"); } SayHello();

() 里面是空的,表示不需要参数;void 表示不返回结果。

第 2 种:有参数、无返回值

void PrintTitle(string title) { Console.WriteLine("==== " + title + " ===="); } PrintTitle("商品小票");

方法需要一个 string 参数,但只负责输出,所以返回类型是 void

第 3 种:无参数、有返回值

string GetShopName() { return "小白商店"; } string shopName = GetShopName(); Console.WriteLine(shopName);

方法不需要参数,但会交回一个 string

第 4 种:有参数、有返回值

int Add(int firstNumber, int secondNumber) { return firstNumber + secondNumber; } int result = Add(2, 3); Console.WriteLine(result);

方法需要两个 int 参数,做完后也会交回一个 int

最常用的是第 2 种和第 4 种。今天只要能独立写出这两种,就已经达到最低通过线。

4. 定义和调用不是一件事

定义方法,是把规则写出来:

int Add(int firstNumber, int secondNumber) { return firstNumber + secondNumber; }

仅仅定义方法不会打印任何内容,也不会自动计算。程序执行到调用位置时,它才真正工作:

int result = Add(2, 3); Console.WriteLine(result);

可以记成:

定义方法 = 先写好一张做事说明书 调用方法 = 拿具体数据使用说明书

5. 参数是什么

参数是方法工作时需要的数据。

decimal CalculateTotal(decimal price, int count) { return price * count; } decimal total = CalculateTotal(100m, 3); Console.WriteLine(total);

这里有两组容易混淆的名字:

位置名字这里的内容
方法定义里形参pricecount
方法调用里实参100m3

调用时,数据按位置传进去:

第 1 个实参 100m -> 第 1 个形参 price 第 2 个实参 3 -> 第 2 个形参 count

所以参数的数量、顺序和类型都要对应。下面把顺序写反了,含义也会跟着变:

string BuildMessage(string name, int count) { return name + ",购买数量:" + count; } string message = BuildMessage("Keyboard", 2); Console.WriteLine(message);

6. 返回值是什么

有返回值的方法会把结果交回调用位置:

decimal CalculateTotal(decimal price, int count) { return price * count; } decimal total = CalculateTotal(50m, 4); Console.WriteLine($"总价: {total:F2}");

执行过程是:

price 得到 50m count 得到 4 计算 50m * 4 return 把 200m 交回去 total 接住 200m

返回类型和 return 后面的结果类型必须一致。方法写的是 decimal,就要交回 decimal

7. void 是什么

void 表示“只做动作,不交回结果”。

void PrintLine(string name, decimal price, int count) { Console.WriteLine($"{name} x {count} = {(price * count):F2}"); } PrintLine("Keyboard", 199m, 2);

不能用变量接住 void 方法:

// decimal total = PrintLine("Keyboard", 199m, 2); // 编译错误

因为 PrintLine 只负责显示,没有返回 decimal

8. return 有哪两种作用

在有返回值的方法里,return 交回结果并结束方法:

decimal CalculateTotal(decimal price, int count) { return price * count; } Console.WriteLine(CalculateTotal(10m, 2));

void 方法里,return; 可以提前结束方法:

void PrintName(string? name) { if (string.IsNullOrWhiteSpace(name)) { Console.WriteLine("名称不能为空"); return; } Console.WriteLine(name.Trim()); } PrintName(" Keyboard "); PrintName("");

9. 作用域是什么

方法里的参数和局部变量,通常只能在这个方法里面使用:

void PrintTotal() { decimal total = 100m; Console.WriteLine(total); } PrintTotal(); // Console.WriteLine(total); // 编译错误:这里看不到 total

total{ 开始存在,到对应的 } 结束。这样不同方法里的变量不会互相干扰。

10. 方法什么时候该拆

满足下面任意一种情况,就可以考虑拆方法:

情况示例
一段代码重复出现多次打印小票
一段代码有明确动作计算总价、读取数量
主流程太长输入、校验、计算、输出混在一起
需要单独测试规则折扣计算、库存判断

不要为了拆而拆。一个方法先只做一件清楚的事。

11. 方法重载是什么(第二遍选学)

重载就是方法名相同,但参数数量或参数类型不同。你之前一直在用的 Console.WriteLine 就有很多重载:

Console.WriteLine("Keyboard"); Console.WriteLine(199); Console.WriteLine(true);

输出:

Keyboard 199 True

三行都调用 WriteLine,但传入的参数类型分别是 stringintbool。C# 会根据参数类型选择对应版本。第一次学习只要能认出来,不要求今天自己写重载。

方法命名怎么写

方法名要表达动作,通常用动词开头:

不清楚更清楚
DoItPrintReceipt
HandleReadPrice
CalcCalculateTotal
CheckIsValidName

能用一句话解释的方法,通常更容易维护。

方法常用写法速查

方法最常见的不是一种写法,而是下面几种模板。以后写代码时,先判断自己属于哪一种。

需求方法写法例子
只做动作,不返回结果void打印标题、打印小票
计算后返回结果decimalintstring计算总价、生成编号
判断是否满足条件bool判断名称是否有效
读取失败时返回空string?Product?读取必填文本、按 Id 查商品
尝试转换并返回真假bool TryXxx(..., out T value)TryReadIntTryReadDecimal
同名不同参数重载有折扣和无折扣的 CalculateTotal

模板 1:只做动作

void PrintTitle(string title) { Console.WriteLine("==== " + title + " ===="); }

这个方法只输出,不需要把结果交给外面,所以用 void

模板 2:返回计算结果

decimal CalculateTotal(decimal price, int count) { return price * count; }

这个方法的重点是“给我单价和数量,我交回总价”。

模板 3:返回真假

bool IsValidPrice(decimal price) { return price > 0; }

返回 bool 的方法通常用 IsHasCan 开头。

模板 4:失败时返回 null

string? ReadRequiredText(string title) { Console.Write(title); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { return null; } return input.Trim(); }

调用方看到 null,就知道读取失败或输入无效。

模板 5:TryXxxout

这是第二遍选学模板。第一次学习只要知道 TryParse 会用到 out,不要求你自己设计带 out 的方法。

bool TryReadDecimal(string title, out decimal value) { Console.Write(title); string? input = Console.ReadLine(); return decimal.TryParse(input, out value); }

这种写法的意思是:方法用返回值告诉你成功还是失败,用 out 把成功后的结果带出来。

今天写代码时按这个顺序

  1. 先写能运行的直线版代码。
  2. 找出重复或独立的动作。
  3. 给这段动作起一个清楚的方法名。
  4. 判断它需要哪些参数。
  5. 判断它是否要返回结果。
  6. 把方法调用放回主流程。
  7. 运行,确认拆方法前后结果一样。

第二部分:把知识组合成完整例子

前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要写一个小票程序,但不再把所有代码挤在一起,而是拆成几个方法。

先找两个动作,不要逐字符硬啃:

CalculateTotal -> 负责算 PrintReceipt -> 负责显示

代码先定义 CalculateTotal,再定义会使用它的 PrintReceipt,最后两行才真正调用 PrintReceipt。阅读顺序和今天的学习顺序一致。

最终你要写出这个程序

decimal CalculateTotal(decimal price, int count) { return price * count; } void PrintReceipt(string name, decimal price, int count) { decimal total = CalculateTotal(price, count); Console.WriteLine("----- 小票 -----"); Console.WriteLine($"商品: {name}"); Console.WriteLine($"单价: {price:F2}"); Console.WriteLine($"数量: {count}"); Console.WriteLine($"总价: {total:F2}"); } PrintReceipt("Keyboard", 199m, 2); PrintReceipt("Mouse", 59m, 3);

输出:

----- 小票 ----- 商品: Keyboard 单价: 199.00 数量: 2 总价: 398.00 ----- 小票 ----- 商品: Mouse 单价: 59.00 数量: 3 总价: 177.00

今天要理解的是:PrintReceipt 负责打印小票,CalculateTotal 负责计算总价。两个方法职责不同,代码就更容易读。

第三部分:跟着敲代码

从这里开始动手。每个例子都可以直接放进 Program.cs 运行。

每个例子按三遍练:第一遍照着敲;第二遍关掉代码只看例子标题;第三遍更换参数和返回值。三遍都能运行,才进入下一个例子。

动手前先做这 3 件事

  1. 打开控制台项目,确认 dotnet run 能运行。
  2. 每做一个例子,就把 Program.cs 旧内容替换掉。
  3. 每次只改一个参数,看方法返回或输出怎么变。

例子 1:第一个有返回值的方法

decimal CalculateTotal(decimal price, int count) { return price * count; } decimal total = CalculateTotal(19.9m, 3); Console.WriteLine($"总价: {total:F2}");

预期输出:

总价: 59.70

例子 2:同一个方法传不同参数

decimal CalculateTotal(decimal price, int count) { return price * count; } Console.WriteLine(CalculateTotal(10m, 2)); Console.WriteLine(CalculateTotal(20m, 3)); Console.WriteLine(CalculateTotal(5.5m, 4));

预期输出:

20 60 22.0

方法不关心你传的是哪件商品,只关心单价和数量。

例子 3:void 方法只负责输出

void PrintTitle(string title) { Console.WriteLine("==== " + title + " ===="); } PrintTitle("商品小票"); PrintTitle("库存报表");

预期输出:

==== 商品小票 ==== ==== 库存报表 ====

例子 4:返回 bool 的校验方法

bool IsValidName(string? name) { return !string.IsNullOrWhiteSpace(name); } Console.WriteLine(IsValidName("Keyboard")); Console.WriteLine(IsValidName("")); Console.WriteLine(IsValidName(" "));

预期输出:

True False False

返回 bool 的方法通常可以用 IsHasCan 开头。

例子 5(选学):用 out 读取整数

bool TryReadInt(string title, out int value) { Console.Write(title); string? input = Console.ReadLine(); return int.TryParse(input, out value); } if (TryReadInt("数量: ", out int count)) { Console.WriteLine($"数量是 {count}"); } else { Console.WriteLine("数量格式不正确"); }

输入 3 时输出:

数量: 3 数量是 3

输入 abc 时输出:

数量: abc 数量格式不正确

例子 6:把打印小票拆成方法

decimal CalculateTotal(decimal price, int count) { return price * count; } void PrintReceipt(string name, decimal price, int count) { decimal total = CalculateTotal(price, count); Console.WriteLine("----- 小票 -----"); Console.WriteLine($"商品: {name}"); Console.WriteLine($"单价: {price:F2}"); Console.WriteLine($"数量: {count}"); Console.WriteLine($"总价: {total:F2}"); } PrintReceipt("Keyboard", 199m, 2); PrintReceipt("Mouse", 59m, 3);

这里 PrintReceipt 调用了 CalculateTotal。一个方法可以调用另一个方法。

例子 7(选学):观察 WriteLine 的重载

Console.WriteLine("库存数量"); Console.WriteLine(20); Console.WriteLine(19.9m); Console.WriteLine(false);

预期输出:

库存数量 20 19.9 False

这里不是定义 4 个新方法,而是在调用 .NET 已经提供好的不同 WriteLine 重载。

常见错误和修法

错误为什么错修法
方法只写动作不写返回值调用方拿不到结果需要结果时写 return 和返回类型
参数名和变量名混乱不知道数据从哪里来参数表示传入数据,局部变量表示方法内部数据
方法里直接读写所有输入方法难复用把输入放在外面,方法只处理参数
忘记处理非法参数方法被错误数据调用在方法开头校验参数
方法太长一段代码做了太多事按读取、校验、计算、输出拆成多个方法

每日小测

先独立回答,再看参考答案。

1. 找调用和定义

下面哪一行是调用,哪一段是定义?

int Add(int a, int b) { return a + b; } int result = Add(2, 3);

2. 找形参和实参

在上面的代码中,ab 是什么?23 又是什么?

3. 预测返回值

Add(2, 3) 执行后,result 保存什么?

4. 找编译错误

下面代码为什么不能把结果放进 total

void PrintTotal(decimal price, int count) { Console.WriteLine(price * count); } decimal total = PrintTotal(10m, 2);

5. 判断职责

“读取商品名称、读取单价、计算总价、打印小票”全部放进一个 CalculateTotal 方法,好不好?为什么?

参考答案

  1. Add(2, 3) 是调用;带返回类型、方法名、参数和 { } 的部分是定义。
  2. ab 是形参;23 是调用时传入的实参。
  3. result 保存 5
  4. PrintTotal 的返回类型是 void,它只输出,没有交回 decimal。要么改成返回 decimal,要么不要用变量接结果。
  5. 不好。读取、计算、输出是不同职责。CalculateTotal 最好只接收单价和数量并返回总价。

如果第 1、3、4 题答错,先回看“方法结构拆开看”“调用和定义是两件事”“返回值是什么”。

上位机专项练习

方法可以把读取、换算、判断和显示拆成清楚的动作。下面继续坚持先定义方法,再调用方法。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:判断采集值是否报警

bool IsAlarm(double value, double limit) { return value >= limit; } bool result = IsAlarm(82.5, 80.0); Console.WriteLine(result);

运行结果或界面效果:

True

改动任务: 把 82.5 改成 79.9,再运行。

专项例子 2:换算百分比工程值

double ConvertToPercent(int rawValue, int maxRawValue) { return rawValue * 100.0 / maxRawValue; } double percent = ConvertToPercent(2048, 4095); Console.WriteLine($"{percent:F2}%");

运行结果或界面效果:

50.01%

改动任务: 用 0 和 4095 分别调用一次。

专项例子 3:打印设备状态

void PrintDeviceStatus(string name, bool isOnline) { string status = isOnline ? "在线" : "离线"; Console.WriteLine($"{name}: {status}"); } PrintDeviceStatus("PLC-01", true); PrintDeviceStatus("仪表-02", false);

运行结果或界面效果:

PLC-01: 在线 仪表-02: 离线

改动任务: 增加第三台设备并传入 true。

第四部分:作业完整答案

这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。

作业要求

把商品小票程序拆成方法,要求:

  1. 主流程继续负责读取和校验输入,这是 Day 4 已经学过的内容。
  2. CalculateTotal 接收单价和数量,返回总价。
  3. PrintReceipt 接收商品数据,只负责打印小票。
  4. 不要求自己编写带 out 的方法。

作业拆解

按这个顺序写:

  1. 先写 CalculateTotal 的定义,用固定数字测试它的返回值。
  2. 再写 PrintReceipt 的定义,确认它只输出、不返回结果。
  3. 然后写主流程,完成名称、单价和数量的读取与校验。
  4. 在主流程里调用两个已经写好的方法。
  5. 最后测试正常输入和错误输入。

完整代码

Console.Write("商品名称: "); string? nameText = Console.ReadLine(); if (string.IsNullOrWhiteSpace(nameText)) { Console.WriteLine("商品名称不能为空"); return; } Console.Write("单价: "); string? priceText = Console.ReadLine(); if (!decimal.TryParse(priceText, out decimal price)) { Console.WriteLine("单价必须是数字"); return; } Console.Write("数量: "); string? countText = Console.ReadLine(); if (!int.TryParse(countText, out int count)) { Console.WriteLine("数量必须是整数"); return; } if (price < 0 || count <= 0) { Console.WriteLine("单价或数量范围不正确"); return; } string name = nameText.Trim(); decimal CalculateTotal(decimal price, int count) { return price * count; } void PrintReceipt(string name, decimal price, int count, decimal total) { Console.WriteLine("----- 小票 -----"); Console.WriteLine($"商品: {name}"); Console.WriteLine($"单价: {price:F2}"); Console.WriteLine($"数量: {count}"); Console.WriteLine($"总价: {total:F2}"); } decimal total = CalculateTotal(price, count); PrintReceipt(name, price, count, total);

正确运行结果

正常输入:

商品名称: Keyboard 单价: 199 数量: 2 ----- 小票 ----- 商品: Keyboard 单价: 199.00 数量: 2 总价: 398.00

错误输入要分别测试:

商品名称为空:提示 商品名称不能为空 单价输入 abc:提示 单价必须是数字 数量输入 2.5:提示 数量必须是整数 数量输入 0:提示 单价或数量范围不正确

你要能说清楚这 7 件事

  • 方法名说明这段逻辑做什么。
  • 参数说明方法需要外部提供什么数据。
  • 返回值说明方法会交回什么结果。
  • void 方法只做动作,不返回结果。
  • return 可以返回结果,也可以提前结束。
  • 方法内部变量通常不能在方法外直接使用。
  • 重载是同名方法配不同参数,不是随便复制代码。