C# 方法
方法是组织代码的基本单位——把一段逻辑封装成可复用的块,起个名字,需要时调用。这一篇我们系统讲 C# 方法的声明语法、参数类型、重载、递归、本地函数、Lambda。掌握这些,你写的代码就开始有"工程感"了。
一、方法的基本声明
方法声明的语法:[修饰符] 返回类型 名字(参数列表) { 方法体 }。
// 方法声明:[修饰符] 返回类型 名字(参数列表) { body }
public int Add(int a, int b)
{
return a + b;
}
// void:无返回值
public void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
// 表达式体方法(C# 6+ 单行简洁写法)
public int Square(int x) => x * x;
public void Print(string s) => Console.WriteLine(s);
// 调用
int sum = Add(3, 5);
Greet("小明");几个要点:
- 返回类型:有返回值写具体类型,无返回值写
void。 - 表达式体方法:单行方法可用
=>简写,等价于return 表达式;。 - 方法名:PascalCase 大驼峰,动词开头(如
GetUser、Calculate)。 - static:静态方法属于类本身,直接
ClassName.Method()调用;实例方法属于对象,需new对象后调用。
二、参数传递:值参数、ref、out
这是 C# 方法最容易让初学者困惑的部分。默认情况下,C# 按值传递(复制一份)。但可以用 ref/out 改成"传引用":
// ref:双向传递(调用前必须赋值)
public void Swap(ref int a, ref int b)
{
int t = a;
a = b;
b = t;
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // 2, 1
// out:用于"返回多个值"(调用前无需赋值)
public bool TryParseInt(string s, out int result)
{
return int.TryParse(s, out result);
}
if (TryParseInt("42", out int n))
Console.WriteLine($"成功: {n}");
// out 不需要预先赋值,但方法内必须赋值
string s = "abc";
int n;
bool ok = int.TryParse(s, out n);
// ok = false, n = 0三者区别:
- 值参数(默认):复制一份。方法内改不影响外部。值类型复制值,引用类型复制引用(共享对象)。
ref:传引用。调用前必须赋值。方法内的修改对外可见。常用于值类型的双向传递。out:也是传引用。调用前无需赋值,方法内必须赋值。常用于"返回多个值"。
C# 7+ 还引入了 in 关键字:只读引用传递(性能优化,主要用于 struct)。
三、params 可变参数 + 可选参数 + 命名参数
// params:可变参数(参数数量不固定)
public int Sum(params int[] nums)
{
int total = 0;
foreach (var n in nums)
total += n;
return total;
}
Console.WriteLine(Sum(1, 2, 3)); // 6
Console.WriteLine(Sum(1, 2, 3, 4, 5)); // 15
Console.WriteLine(Sum()); // 0
Console.WriteLine(Sum(new int[] {10, 20})); // 也行
// 可选参数:有默认值,调用时可省略
public void Log(string msg, string level = "INFO")
{
Console.WriteLine($"[{level}] {msg}");
}
Log("启动"); // [INFO] 启动
Log("错误", "ERROR"); // [ERROR] 错误
// 命名参数:可以乱序传
Log(level: "WARN", msg: "警告");这是 C# 参数系统的灵活性所在:
params:接收任意个参数,本质是数组。一个方法只能有一个 params,且必须是最后一个。- 可选参数:有默认值,调用时可省略。
- 命名参数:可以乱序传,可读性极强。
Log(level: "ERROR", msg: "错")。
实际开发中,命名参数 + 可选参数是写清晰 API 调用的利器,尤其是参数很多时。
四、方法重载(Overloading)
同一个方法名,参数列表不同,叫重载。编译器根据调用时传的参数自动选:
// 方法重载:同名,参数列表不同
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public int Add(int a, int b, int c) => a + b + c;
public string Add(string a, string b) => a + b;
Console.WriteLine(Add(1, 2)); // 3 (int 版)
Console.WriteLine(Add(1.5, 2.5)); // 4 (double 版)
Console.WriteLine(Add(1, 2, 3)); // 6 (三参版)
Console.WriteLine(Add("Hello ", "C#")); // "Hello C#"
// 注意:返回值不同不算重载,参数列表必须不同
// ❌ public double Add(int a, int b) - 编译报错(和 int 版冲突)规则:参数个数、类型、顺序不同都可以构成重载;仅返回值不同不算重载。重载让 API 简洁——一个 Add 名字搞定各种类型加法,而不是 AddInt、AddDouble、AddString 一堆名字。
五、递归(Recursion)
方法调用自己叫递归。经典例子:阶乘、斐波那契、树的遍历:
// 递归:方法调用自己
// 经典:阶乘
int Factorial(int n)
{
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
Console.WriteLine(Factorial(5)); // 120
// 斐波那契数列
int Fib(int n)
{
if (n < 2) return n;
return Fib(n - 1) + Fib(n - 2);
}
Console.WriteLine(Fib(10)); // 55
// 注意:递归深度太大会栈溢出
// 大数据量应改用迭代或记忆化递归的两要素:基准情形(终止) + 递归情形(向基准推进)。注意递归深度大时会栈溢出(StackOverflowException),大数据场景应改用迭代。
六、本地函数(Local Function)
C# 7+ 引入,在方法内部定义函数。适合"只想在某个方法内用"的小辅助:
// 本地函数:方法内部定义的函数(C# 7+)
public void Process(int[] nums)
{
// 本地函数:只在外层方法内可见
bool IsValid(int n) => n > 0 && n < 100;
int sum = 0;
foreach (var n in nums)
{
if (IsValid(n))
sum += n;
}
Console.WriteLine(sum);
}
// 经典用法:递归 + 缓存
public int Fibonacci(int n)
{
return Fib(n);
int Fib(int x) // 本地函数,可访问外层参数
{
if (x < 2) return x;
return Fib(x - 1) + Fib(x - 2);
}
}本地函数比 Lambda 优势:
- 支持
ref/out/params(Lambda 不支持部分)。 - 支持递归(Lambda 递归需自己引用自己)。
- 性能略好(避免委托分配)。
- 可见性范围明确(只在外层方法内)。
七、Lambda 表达式
Lambda 是匿名方法,配合 LINQ 用得最多。语法 参数 => 表达式:
// Lambda 表达式:匿名方法
Func<int, int> square = x => x * x;
Console.WriteLine(square(5)); // 25
// 多参数
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 7
// 无参数
Action greet = () => Console.WriteLine("Hi");
greet();
// 语句 Lambda(多行)
Func<int, int, int> safeDiv = (a, b) =>
{
if (b == 0) throw new DivideByZeroException();
return a / b;
};
// 配合 LINQ(最常见)
int[] nums = { 1, 2, 3, 4, 5 };
var evens = nums.Where(n => n % 2 == 0);
var doubled = nums.Select(n => n * 2);几个常用委托类型:
Func<T, TResult>:有返回值。Func<int, int>接受 int 返回 int。Action<T>:无返回值。Action<string>接受 string 无返回。Predicate<T>:返回 bool。Predicate<int>等价Func<int, bool>。
八、扩展方法(Extension Method)
给已有类型"添加"方法,而不修改其源码——LINQ 就是这么实现的:
// 定义扩展方法:静态类 + 静态方法 + this 参数
public static class StringExtensions
{
public static bool IsEmail(this string s)
{
return s.Contains("@") && s.Contains(".");
}
}
// 使用
string email = "test@example.com";
bool ok = email.IsEmail(); // 像实例方法一样调用
// LINQ 的 Where/Select 都是扩展方法
int[] nums = { 1, 2, 3, 4 };
var evens = nums.Where(n => n % 2 == 0);
// 本质:Enumerable.Where(nums, n => n % 2 == 0)扩展方法不修改原类型,只是"看起来像"实例方法。这是 C# 设计的优雅之处。
九、异步方法(async/await 预览)
异步方法是现代 C# 的核心特性,我们后面会专门讲。这里只看一眼:
public async Task<string> FetchAsync(string url)
{
using var client = new HttpClient();
string data = await client.GetStringAsync(url);
return data;
}
// async 标记异步方法,返回 Task 或 Task<T>
// await 表示"等这个异步操作完成,不阻塞线程"十、方法设计原则
- 单一职责:一个方法只做一件事,名字能描述它干啥。
- 短小精悍:超过 50 行考虑拆分。
- 参数别太多:超过 5 个考虑封装成对象。
- 避免输出参数:多返回值用元组
(int x, int y)而不是 out。 - 避免副作用:对参数的修改要明确(用 ref/out 或文档说明)。
- 命名清晰:动词开头,如
GetUserById、CalculateTotal。
小结
方法是程序里"组织代码"的核心单元。记住 C# 特有的几个特性:ref/out(传引用)、params(可变参数)、命名参数(可读性)、本地函数(辅助函数)、Lambda(配合 LINQ)、扩展方法(优雅扩展类型)。下一篇我们看数组——最基础的数据结构。
← 上一篇 C# 条件与循环
下一篇 C# 数组 →