菜鸟教程 -- 学的不仅是技术,更是梦想!

C# 教程
(追記) (追記ここまで)

C# 方法(Methods)

方法(Method)是将一组相关语句组织在一起、用来执行特定任务的代码块。它是 C# 中实现代码复用逻辑封装的核心机制。每一个 C# 程序至少有一个 Main 方法作为程序入口。

使用方法需要两步:定义方法调用方法


1. 定义方法

C# 中定义方法的基本语法如下:

<访问修饰符> <返回类型> <方法名>(<参数列表>)
{
 方法体
}

方法由以下几个元素组成:

  • 访问修饰符(Access Modifier):控制方法的可见性,如 publicprivateprotectedinternal
  • 返回类型(Return Type):方法返回值的数据类型。如果方法不返回任何值,使用 void
  • 方法名(Method Name):方法的唯一标识符,采用 PascalCase 命名规范(如 FindMaxGetUserName)。
  • 参数列表(Parameter List):方法接收的输入,用圆括号括起来。参数是可选的,一个方法可以没有参数。
  • 方法体(Method Body):包含实现功能的代码,用花括号 {} 包围。

下图展示了一个方法的完整结构:

C# 方法结构解析 public int FindMax ( int num1, int num2 ) 访问修饰符 public / private 控制可见性 返回类型 int / void 返回值的数据类型 方法名 PascalCase 唯一标识符 参数列表 类型 + 名称 多个用逗号分隔 { 方法体 — 实现功能的代码 } 方法体(Method Body) 完整方法签名: public int FindMax(int a, int b) 修饰符 → 返回类型 → 方法名 → 参数列表

1.1 第一个方法示例

下面定义了一个 FindMax 方法,接收两个整数并返回其中较大的一个:

实例

using System;

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
}
}
}

方法的调用流程:

方法调用流程 调用方 FindMax(100, 200) 实参 方法接收 num1=100, num2=200 执行方法体 比较并返回结果 返回 返回值 result = 200 1. 调用方法,传递实参 → 2. 形参接收值 → 3. 执行方法体 → 4. 返回结果给调用方

跨类调用:只要方法声明为 public,就可以从其他类中通过创建实例来调用。这是面向对象编程中代码复用的基础。


2. 参数传递方式

C# 提供三种向方法传递参数的方式,它们的核心区别在于是否共享内存:

三种参数传递方式对比 按值传递(默认) 调用方 a = 100 复制 方法内 x = 100 各自独立的内存空间 修改 x 不影响 a swap(a, b) 后 a、b 不变 无需额外关键字 void Swap(int x, int y) 引用传递(ref) 调用方 a = 200 同一地址 方法内 ref x = 200 共享同一块内存 修改 x 会改变 a swap(ref a, ref b) 后交换成功 需要 ref 关键字 void Swap(ref int x, ref int y) 输出参数(out) 调用方 a = 5 ← 输出 方法内 out x = 5 方法必须为 out 参数赋值 调用前无需初始化 可返回多个值 需要 out 关键字 void Calc(out int x, out int y)
方式 关键字 行为 典型场景
按值传递 (无) 复制参数值,方法内修改不影响原变量 大多数方法调用(默认)
按引用传递 ref 传递变量的引用,方法内修改会改变原变量 需要修改外部变量(如交换值)
输出参数 out 类似 ref,但方法必须赋值,调用前无需初始化 方法需要返回多个值

2.1 按值传递(默认)

按值传递是默认方式。实参的值会被复制给形参,方法内对形参的修改不会影响原始变量:

实例

using System;

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:

实例

using System;

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 不同的是,调用前变量无需初始化,但方法内部必须为其赋值:

实例

using System;

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)!$$:

实例

using System;

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# 允许为参数设置默认值,调用时可以省略这些参数。配合命名参数,还可以跳过某些参数只指定后面的:

实例

using System;

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)

同一个类中可以有多个同名方法,只要参数列表不同(参数类型、数量或顺序不同):

实例

using System;

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+)
方法重载 同名不同参数 编译器根据参数列表自动选择
AI 思考中...

6 篇笔记 写笔记

  1. #0

    StoneBelieve

    756***[email protected]

    65

    局部变量是只能在函数内部访问到的,而字段可以在整个类中访问到,在外部声明可以在其他方法使用的时候进行调用,比如有另外一个方法想要调用max值即可直接使用;

    using System;
    namespace CalculatorApplication
    {
     class NumberManipulator
     {
     int max;
     public int FindMax(int num1, int num2)
     {
     /* 局部变量声明 */
     int result;
     if (num1 > num2)
     result = num1;
     else
     result = num2;
     return result;
     }
     public int getmax(int max)//该方法同样可以获取max中的值
     {
     return max;
     }
     static void Main(string[] args)
     {
     /* 局部变量定义 */
     int a = 100;
     int b = 200;
     NumberManipulator n = new NumberManipulator();
     //调用 FindMax 方法
     n.max = n.FindMax(a, b);
     Console.WriteLine("最大值是: {0}", n.max );
     Console.ReadLine();
     }
     }
    }

    StoneBelieve

    756***[email protected]

    9年前 (2017年11月19日)
  2. #0

    十一年话

    142***[email protected]

    参考地址

    159

    ref 和 out 的区别

    一个用关键字 ref 标示,一个用 out 标示。

    牵扯到数据是引用类型还是值类型。

    一般用这两个关键字你是想调用一个函数将某个值类型的数据通过一个函数后进行更改。传 out 定义的参数进去的时候这个参数在函数内部必须初始化。否则是不能进行编译的。ref 和 out 都是传递数据的地址,正因为传了地址,才能对源数据进行修改。

    一般情况下不加 ref 或者 out 的时候,传值类型的数据进去实际上传进去的是源数据的一个副本,也就是在内存中新开辟了一块空间,这里面存的值是与源数据相等的,这也就是为什么在传值类型数据的时候你如果不用 return 是无法修改原值的原因。但是你如果用了 ref,或者 out,这一切问题都解决了,因为他们传的是地址。

    out 比起 ref 来说,还有一个用法就是可以作为多返回值来用,都知道函数只能有一个返回值,C#里,如果你想让一个函数有多个返回值,那么OUT能很容易解决。

    十一年话

    142***[email protected]

    参考地址

    9年前 (2017年12月19日)
  3. #0
    134

    方法中参数的类型有三种

    in型参数

    int 型参数通过值传递的方式将数值传入方法中,即我们在Java中常见的方法。

    ref型参数

    该种类型的参数传递变量地址给方法(引用传递),传递前变量必须初始化。

    该类型与out型的区别在与:

    • 1).ref 型传递变量前,变量必须初始化,否则编译器会报错, 而 out 型则不需要初始化
    • 2).ref 型传递变量,数值可以传入方法中,而 out 型无法将数据传入方法中。换而言之,ref 型有进有出,out 型只出不进。

    out 型参数

    与 ref 型类似,仅用于传回结果。

    注意:

    1). out型数据在方法中必须要赋值,否则编译器会报错。

    eg:如下图若将代码中的sum1方法的方法体

    改为 a+=b; 则编译器会报错。原因:out 型只出不进,在没给 a 赋值前是不能使用的

    改为 b+=b+2; 编译器也会报错。原因:out 型数据在方法中必须要赋值。

    2). 重载方法时若两个方法的区别仅限于一个参数类型为ref 另一个方法中为out,编译器会报错

    eg:若将下面的代码中将方法名 vsum1 改为 sum(或者将方法名 sum 改为 sum1),编译器会报错。

    Error 1 Cannot define overloaded method ‘sum’ because it differs from another method only on ref and out 

    原因:参数类型区别仅限于 为 ref 与为 out 时,若重载对编译器而言两者的元数据表示完全相同。

    class C
    {
     //1. in型参数
     public void sum(int a, int b) {
     a += b;
     }
     //2. ref型参数
     public void sum(ref int a, int b)
     {
     a += b;
     }
     //3. out型参数
     public void sum1(out int a, int b)
     {
     a = b+2;
     }
     public static void Main(string[] args)
     {
     C c = new C();
     int a = 1, b = 2;
     c.sum(a,b);
     Console.WriteLine("a:{0}", a);
     a = 1; b = 2;
     c.sum(ref a, b);
     Console.WriteLine("ref a:{0}", a);
     a = 1; b = 2;
     c.sum1(out a, b);
     Console.WriteLine("out a:{0}", a);
     }
    }

    输出结果:

    从代码也可以看出,int 型参数为值传递,所以当将变量 a 传入方法时,变量 a 的值并不会发生变化。而 ref 型参数,由于是引用传递,将变量的值和地址都传入方法中故变量值改变。out 型无法将变量的值传入。但可以将变量的地址传入并为该地址上的变量赋值。

    8年前 (2018年03月20日)
  4. #0

    Solitary

    119***[email protected]

    26

    按值传递参数的问题:

    为什么即使在函数内改变了值,值也没有发生任何的变化呢?

    在交换之前,a 的值:100
    在交换之前,b 的值:200
    在交换之后,a 的值:100
    在交换之后,b 的值:200

    解析:

    调用一个方法时相当于复制一个对象到新分配的内存中,方法完毕对象也就del了,两个变量是不同的两个地址,a只不过是栈里的临时变量而已,和主函数里的变量没有关系,因此不会改变b的值。

    而指针和引用直接访问内存地址,故直接修改所指向的对象,两者指向同一变量。

    Solitary

    119***[email protected]

    8年前 (2018年07月31日)
  5. #0

    大軒

    big***[email protected]

    50

    扩展方法

    扩展方法可以实现在不需要修改目标类,也不需要继承目标类的情况下为其添加一个方法。

    规则:

    • 1、扩展类必须为静态类,扩展方法必须为静态方法。
    • 2、扩展方法的第1个形参开头必须使用 "this" 关键字然后再填写扩展的目标类。
    • 3、如果需要接收参数则从第2个参数开始算起,第1个参数在真正调用方法时是隐藏的。
    public static class ExtensionString
    {
     //向 String 类扩展一个统计单词数量的方法
     public static int CountWord(this String str)
     {
     return str.Split(' ').Length;
     }
    }
    class MainClass
    {
     public static void Main(string[] args)
     {
     Console.WriteLine("单词数量:" + "Hello World".CountWord()); //没有参数
     }
    }

    大軒

    big***[email protected]

    8年前 (2018年08月25日)
  6. #0

    对于复杂引用类型参数传递的控制

    所谓复杂,是参数是数组或集合类型,或者参数包含这些类型数据,这种情况下上面的方法不能保证参数数据不被修改,因为即使对象为只读的,但是对象中的数组或集合字段(属性)还是可以修改的。

    1、集合参数(包含集合字段的引用参数也一样)

    .net 4.5以前版本可以使用不包含修改集合元素方法的接口来代替具体集合类型。例如使用IEnumerable<T>接口代替List<T>。4.5版本可以直接使用IReadOnlyCollection接口或实现该接口的集合类型。

    2、数组参数

    没有好的办法保护数组类型参数不被修改,所以尽量避免使用数组类型作为方法参数,除非用到可选参数时候。

    更多内容参考:C# 中数组作为参数传递的问题

    8年前 (2018年08月29日)

点我分享笔记

  • 昵称 (必填)
  • 邮箱 (必填)
  • 引用地址

AltStyle によって変換されたページ (->オリジナル) /