一、深入理解方法
一个C#方法由多个部分组成,它们共同构成了所谓的**方法签名(Method Signature)**和方法体。
csharp
// [1] [2] [3] [4] [5]
public static int Add(int firstNumber, int secondNumber)
{ // <-- [6] 方法体的开始
// [7] 方法体 (Method Body)
int sum = firstNumber + secondNumber;
return sum; // [8] 返回语句
} // <-- 方法体的结束
-
访问修饰符(Access Modifier) -
public决定了谁可以看到和调用这个方法。常见的有:public:完全公开,任何代码都可以访问。private:私有,只有在同一个类内部的代码才能访问。(默认)protected:受保护,类内部及其子类可以访问。internal:内部,同一个程序集(Project)内的代码可以访问。
-
可选修饰符(Optional Modifiers) -
static定义方法的行为特性。例如:static:静态方法,属于类本身,通过类名调用(如Math.Max()),而不是通过类的实例。abstract:抽象方法,只有声明,没有实现,必须在子类中被重写。virtual:虚拟方法,可以被子类重写(override)。async:异步方法,用于异步编程。
-
返回类型(Return Type) -
int方法执行完毕后,返回给调用者的值的类型。如果方法不返回任何值,则使用void关键字。 -
方法名(Method Name) -
Add方法的唯一标识符。 -
参数列表(Parameter List) -
(int firstNumber, int secondNumber)方法接收的输入数据。每个参数都包含类型 和名称 。如果没有参数,则使用空括号()。 -
方法体(Method Body) -
{...}包含在大括号内的代码块,是方法的具体实现逻辑。 -
返回语句(Return Statement) -
return sum;如果返回类型不是void,方法体中必须包含return语句,用于返回一个符合返回类型的值,并终止方法的执行。
二、参数的传递
参数的传递主要分为两大类:值类型 和引用类型的传递。
1. 按值传递
当传递一个值类型(如 int, double, bool, char, struct)的参数时,方法接收到的是该变量值的【副本】。
这意味着在方法内部对参数的任何修改,都不会影响到方法外部的原始变量。
csharp
void Increment(int number)
{
number = number + 10;
Console.WriteLine($"方法内部: {number}"); // 输出: 方法内部: 15
}
int myValue = 5;
Increment(myValue);
Console.WriteLine($"方法外部: {myValue}"); // 输出: 方法外部: 5 <-- 关键!原始值未改变
内存图解 :调用Increment(myValue)时,系统在内存中为参数number开辟了一块新的空间 ,并将myValue的值5复制了过去。方法内部操作的完全是这个副本。
2. 按引用传递
当传递一个引用类型(如 class实例, string, array, List)的参数时,方法接收到的是指向内存中同一个对象的【引用的副本】(可以理解为地址的副本)。
这意味着方法内部的参数和外部的变量,都指向同一个内存地址上的对象。因此:
- 修改对象的内容 :方法内部对该对象属性的修改,会反映在方法外部。
- 让参数指向新对象 :如果在方法内部将参数重新赋值为一个
new的对象,这只会改变参数这个"引用的副本",使其指向新对象,而不会影响外部的原始变量。
csharp
void ModifyObject(MyNumber num)
{
// 修改了所指向对象的内容
num.Value = 99;
}
void ChangeReference(MyNumber num)
{
// 让参数指向一个全新的对象
num = new MyNumber { Value = 1000 };
}
MyNumber myObject = new MyNumber { Value = 10 };
// 场景1: 修改内容
ModifyObject(myObject);
Console.WriteLine($"修改内容后: {myObject.Value}"); // 输出: 99 <-- 外部受影响!
// 场景2: 改变引用
ChangeReference(myObject);
Console.WriteLine($"改变引用后: {myObject.Value}"); // 输出: 99 <-- 外部不受影响!
public class MyNumber { public int Value { get; set; } }
3. 用 ref, out, in 掌控传递行为
C#提供了三个关键字,允许我们精确控制参数的传递方式,甚至可以改变值类型的默认行为。
-
ref关键字 :强制 按引用传递。即使是值类型,也会传递变量的引用。这意味着方法内部的修改将直接影响 原始变量。ref参数在传入前必须被初始化。csharpvoid Swap(ref int a, ref int b) { int temp = a; a = b; b = temp; } int x = 5, y = 10; Swap(ref x, ref y); // 必须使用 ref 关键字调用 Console.WriteLine($"x: {x}, y: {y}"); // 输出: x: 10, y: 5 -
out关键字 :与ref类似,也是按引用传递。但它的核心意图是从方法中输出一个值 。out参数在传入前无需 初始化,但在方法返回前必须在方法内部为其赋值。csharpbool TryParseToPositiveInt(string input, out int result) { if (int.TryParse(input, out int parsedValue) && parsedValue > 0) { result = parsedValue; // 必须赋值 return true; } result = 0; // 即使失败,也必须赋值 return false; } if (TryParseToPositiveInt("123", out int number)) { Console.WriteLine(number); // 输出:123 } -
in关键字 :按引用传递,但是只读的。方法内部不能修改该参数。它主要用于传递大型结构体(struct),既可以避免复制大对象带来的性能开销,又能保证数据不会被意外修改。
三、 方法返回值
方法不仅要接收数据,还要能产出结果。如何高效、清晰地返回信息是一门艺术。
1. 单一返回值的经典模式
这是最常见的情况,使用 return 关键字返回一个值。
csharp
double CalculateCircleArea(double radius)
{
return Math.PI * radius * radius;
}
2. 返回多个值的现代方案
现实中,一个方法常常需要返回多个信息(例如:一个结果值 + 一个状态码)。
-
传统方式:
out参数 如上文的TryParse示例,这是长久以来的标准做法。 -
现代方式:元组(Tuples) 元组提供了一种轻量级的方式来封装和返回多个值,而无需创建专门的类或结构体。
csharp(bool, int, string) ProcessData(string data) { // ... 一些处理逻辑 ... bool success = true; int recordsAffected = 10; string message = "Operation successful."; return (success, recordsAffected, message); } // 调用并解构元组 var (isSuccess, count, msg) = ProcessData("some data"); if (isSuccess) { Console.WriteLine($"Success! Records: {count}, Message: {msg}"); }元组的可读性远超
out参数,是现代C#中返回多个值的首选。 -
其他方式:自定义返回对象 当返回的数据具有复杂的结构和明确的业务含义时,最佳实践是定义一个专门的类或结构体来承载它们。
csharppublic class ProcessResult { public bool IsSuccess { get; set; } public int RecordsAffected { get; set; } public string Message { get; set; } } ProcessResult ProcessDataStructured(string data) { return new ProcessResult { IsSuccess = true, RecordsAffected = 10, Message = "Success" }; }
四、方法的"变体"与"语法糖"
1. 方法重载(Method Overloading)
允许在同一个类中定义多个同名但参数列表不同(参数个数、类型或顺序不同)的方法。编译器会根据你调用时提供的参数,自动选择匹配的方法。
csharp
public class Logger
{
public void Log(string message) { /* ... */ }
public void Log(string message, int level) { /* ... */ }
public void Log(Exception ex) { /* ... */ }
}
方法重载是实现多态性的一种方式,它提供了统一的API入口,同时又能处理不同类型的数据。
2. 可选参数与命名参数
-
可选参数(Optional Parameters):允许为方法的参数指定默认值。如果调用者不提供该参数,则使用默认值。
csharpvoid SendMessage(string message, string recipient, int priority = 1) { // ... } SendMessage("Hello", "UserA"); // priority 会使用默认值 1 SendMessage("Urgent", "Admin", 5); // priority 被指定为 5 -
命名参数(Named Arguments):调用方法时,可以显式地为参数指定名称,而不必遵循其在参数列表中的顺序。这对于有多个可选参数的方法尤其有用,可以极大提高代码可读性。
csharpSendMessage(recipient: "CEO", message: "Project finished!"); // 顺序无关,可读性强
3. params 关键字 - 可变数量的参数
允许方法接受任意数量的特定类型参数。params参数必须是参数列表中的最后一个,并且只能有一个。
csharp
public int SumAll(params int[] numbers)
{
int total = 0;
foreach (int n in numbers)
{
total += n;
}
return total;
}
int sum1 = SumAll(1, 2, 3);
int sum2 = SumAll(5, 10, 15, 20, 25);
4. 表达式体方法
对于只包含单个表达式的方法,可以使用Lambda箭头 => 来提供一种更紧凑的语法。
csharp
// 传统写法
public string GetFullName(string firstName, string lastName)
{
return $"{firstName} {lastName}";
}
// 表达式体写法
public string GetFullName(string firstName, string lastName) => $"{firstName} {lastName}";
5. 局部函数
可以在一个方法的内部定义另一个方法。局部函数只能在其所在的"父"方法内部被调用。这对于封装那些只被一个方法使用的辅助逻辑非常有用,可以避免用私有方法污染类的作用域。
csharp
void ProcessOrder(Order order)
{
// ... 一些逻辑 ...
Validate(order.Id);
// ... 另一些逻辑 ...
// 定义一个只在此处使用的局部函数
void Validate(int orderId)
{
if (orderId <= 0) throw new ArgumentException("Invalid Order ID");
// ... 更多验证逻辑 ...
}
}
internal class Order
{
public int Id { get; set; }
}
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文!