C# 方法(Methods)
方法(Method)是将一组相关语句组织在一起、用来执行特定任务的代码块。它是 C# 中实现代码复用和逻辑封装的核心机制。每一个 C# 程序至少有一个 Main 方法作为程序入口。
使用方法需要两步:定义方法和调用方法。
1. 定义方法
C# 中定义方法的基本语法如下:
<访问修饰符> <返回类型> <方法名>(<参数列表>)
{
方法体
}
方法由以下几个元素组成:
- 访问修饰符(Access Modifier):控制方法的可见性,如
public、private、protected、internal。 - 返回类型(Return Type):方法返回值的数据类型。如果方法不返回任何值,使用
void。 - 方法名(Method Name):方法的唯一标识符,采用 PascalCase 命名规范(如
FindMax、GetUserName)。 - 参数列表(Parameter List):方法接收的输入,用圆括号括起来。参数是可选的,一个方法可以没有参数。
- 方法体(Method Body):包含实现功能的代码,用花括号
{}包围。
下图展示了一个方法的完整结构:
1.1 第一个方法示例
下面定义了一个 FindMax 方法,接收两个整数并返回其中较大的一个:
实例
namespace MethodDemo
{
class NumberHelper
{
// 定义方法:接收两个 int,返回较大的那个
public int FindMax(int num1, int num2)
{
if (num1 > num2)
return num1;
else
return num2;
}
static void Main(string[] args)
{
// 创建对象并调用方法
NumberHelper helper = new NumberHelper();
int result = helper.FindMax(100, 200);
Console.WriteLine($"最大值是:{result}"); // 最大值是:200
}
}
}
方法的调用流程:
跨类调用:只要方法声明为
public,就可以从其他类中通过创建实例来调用。这是面向对象编程中代码复用的基础。
2. 参数传递方式
C# 提供三种向方法传递参数的方式,它们的核心区别在于是否共享内存:
| 方式 | 关键字 | 行为 | 典型场景 |
|---|---|---|---|
| 按值传递 | (无) | 复制参数值,方法内修改不影响原变量 | 大多数方法调用(默认) |
| 按引用传递 | ref |
传递变量的引用,方法内修改会改变原变量 | 需要修改外部变量(如交换值) |
| 输出参数 | out |
类似 ref,但方法必须赋值,调用前无需初始化 | 方法需要返回多个值 |
2.1 按值传递(默认)
按值传递是默认方式。实参的值会被复制给形参,方法内对形参的修改不会影响原始变量:
实例
namespace ParameterDemo
{
class Program
{
// 按值传递:x 和 y 是 a、b 的副本
public void Swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
Console.WriteLine($" 方法内:x={x}, y={y}"); // 交换成功
}
static void Main(string[] args)
{
var p = new Program();
int a = 100, b = 200;
Console.WriteLine($"调用前:a={a}, b={b}");
p.Swap(a, b);
Console.WriteLine($"调用后:a={a}, b={b}"); // a、b 没有变化!
}
}
}
调用前:a=100, b=200 方法内:x=200, y=100 调用后:a=100, b=200
2.2 按引用传递(ref)
使用 ref 关键字传递变量的引用(内存地址),方法内对形参的修改会直接影响原始变量。注意调用时也必须加上 ref:
实例
namespace ParameterDemo
{
class Program
{
// 引用传递:x 和 y 直接引用 a、b 的内存
public void Swap(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
static void Main(string[] args)
{
var p = new Program();
int a = 100, b = 200;
Console.WriteLine($"调用前:a={a}, b={b}");
p.Swap(ref a, ref b); // 调用时也必须加 ref
Console.WriteLine($"调用后:a={a}, b={b}"); // 交换成功!
}
}
}
调用前:a=100, b=200 调用后:a=200, b=100
ref vs out 的区别:
ref要求变量在传递前必须已初始化;out则不要求,但方法内部必须为 out 参数赋值后才能使用。
2.3 输出参数(out)
out 参数让方法可以返回多个值。与 ref 不同的是,调用前变量无需初始化,但方法内部必须为其赋值:
实例
namespace ParameterDemo
{
class Program
{
// out 参数:方法负责赋值
public void GetValues(out int x, out int y)
{
Console.Write("请输入第一个值:");
x = Convert.ToInt32(Console.ReadLine());
Console.Write("请输入第二个值:");
y = Convert.ToInt32(Console.ReadLine());
// 注意:方法结束前必须为所有 out 参数赋值,否则编译错误
}
// 实用示例:TryParse 模式
public bool TryDivide(int a, int b, out double result)
{
if (b == 0)
{
result = 0;
return false; // 除数为零
}
result = (double)a / b;
return true;
}
static void Main(string[] args)
{
var p = new Program();
// out 参数无需初始化
int a, b;
p.GetValues(out a, out b);
Console.WriteLine($"a={a}, b={b}");
// TryParse 模式
if (p.TryDivide(10, 3, out double res))
Console.WriteLine($"10 / 3 = {res:F2}"); // 3.33
}
}
}
从 C# 7.0 起,可以在调用时内联声明 out 变量,使代码更简洁:
// C# 7.0+ 内联声明
if (int.TryParse("123", out int number))
Console.WriteLine($"解析成功:{number}");
3. 递归方法
递归(Recursion)是指方法调用自身。每个递归方法都需要两个要素:
- 基准条件(Base Case):递归终止的条件,防止无限递归
- 递归步骤(Recursive Step):将问题分解为更小的子问题
经典示例——计算阶乘 $$n! = n \times (n-1)!$$:
实例
namespace RecursionDemo
{
class Program
{
// 递归计算阶乘
public int Factorial(int n)
{
// 基准条件:0! = 1,1! = 1
if (n <= 1)
return 1;
// 递归步骤:n! = n × (n-1)!
return n * Factorial(n - 1);
}
static void Main(string[] args)
{
var p = new Program();
Console.WriteLine($"6! = {p.Factorial(6)}"); // 720
Console.WriteLine($"7! = {p.Factorial(7)}"); // 5040
Console.WriteLine($"8! = {p.Factorial(8)}"); // 40320
}
}
}
6! = 720 7! = 5040 8! = 40320
递归调用的展开过程:
Factorial(4) = 4 × Factorial(3) = 4 × 3 × Factorial(2) = 4 × 3 × 2 × Factorial(1) = 4 × 3 × 2 × 1 = 24
注意:递归深度过大时会导致
StackOverflowException。对于性能敏感的场景,考虑改用循环实现。
4. 方法的更多特性
4.1 默认参数与命名参数
C# 允许为参数设置默认值,调用时可以省略这些参数。配合命名参数,还可以跳过某些参数只指定后面的:
实例
namespace MethodFeatures
{
class Program
{
// 默认参数:power 默认为 2
static double Power(double baseNum, int power = 2)
{
double result = 1;
for (int i = 0; i < power; i++)
result *= baseNum;
return result;
}
// 多个默认参数
static void PrintInfo(string name, int age = 18, string city = "未知")
{
Console.WriteLine($"{name}, {age}岁, {city}");
}
static void Main(string[] args)
{
// 使用默认参数
Console.WriteLine(Power(3)); // 9.0(使用默认 power=2)
Console.WriteLine(Power(3, 3)); // 27.0
// 命名参数:跳过 age,只指定 city
PrintInfo("小明", city: "北京");
// 输出:小明, 18岁, 北京
// 命名参数可以不按顺序
PrintInfo(city: "上海", name: "小红", age: 25);
// 输出:小红, 25岁, 上海
}
}
}
4.2 表达式体方法
当方法体只有一条语句时,可以用 => 简化写法(C# 6.0+):
// 传统写法
public int Add(int a, int b)
{
return a + b;
}
// 表达式体写法(等价)
public int Add(int a, int b) => a + b;
4.3 方法重载(Overloading)
同一个类中可以有多个同名方法,只要参数列表不同(参数类型、数量或顺序不同):
实例
namespace MethodFeatures
{
class Calculator
{
// 重载 1:两个 int 相加
public int Add(int a, int b) => a + b;
// 重载 2:三个 int 相加
public int Add(int a, int b, int c) => a + b + c;
// 重载 3:两个 double 相加
public double Add(double a, double b) => a + b;
static void Main(string[] args)
{
var calc = new Calculator();
Console.WriteLine(calc.Add(1, 2)); // 3(调用重载 1)
Console.WriteLine(calc.Add(1, 2, 3)); // 6(调用重载 2)
Console.WriteLine(calc.Add(1.5, 2.5)); // 4.0(调用重载 3)
}
}
}
小结
| 特性 | 语法 | 说明 |
|---|---|---|
| 定义方法 | int Add(int a, int b) { ... } |
指定返回类型、方法名和参数 |
| 无返回值 | void DoSomething() { ... } |
void 表示不返回任何值 |
| 按值传递 | void Foo(int x) |
默认方式,修改形参不影响实参 |
| 引用传递 | void Foo(ref int x) |
共享内存,修改会反映到实参 |
| 输出参数 | void Foo(out int x) |
方法内必须赋值,可返回多个值 |
| 默认参数 | void Foo(int x = 10) |
调用时可省略,使用默认值 |
| 命名参数 | Foo(city: "北京") |
按名称指定参数,可不按顺序 |
| 表达式体 | int Add(int a, int b) => a + b; |
单行方法的简写(C# 6.0+) |
| 方法重载 | 同名不同参数 | 编译器根据参数列表自动选择 |
