欢迎光临
我们一直在努力

C# 方法全解析:从“能跑就行“到“优雅精准“,你的代码封装能力够格了吗?

一句话概括: 方法是程序世界的"最小生产单元"——把重复的逻辑封起来,起个好名字,从此调用它的人不用再关心内部细节。这篇文章会带你从最基础的方法定义,一路打通到 ref/out、方法重载、递归、本地函数、扩展方法这些真正能拉开工程能力差距的知识点。


目录

  • 为什么你需要方法,而不是一堆复制粘贴的代码
  • 方法的解剖学:签名、参数、返回值
  • 参数传递的四种姿势:值传递、ref、out、in
  • 可选参数与 params:让调用方"少写几个字"
  • 方法重载:同名不同貌的艺术
  • 递归:方法调用自己,解决"自相似"问题
  • 本地函数与表达式主体方法:写更精炼的代码
  • 扩展方法:给别人的类"加装备"
  • 实战演练:搭建一个"购物车结算系统"
  • 高手都会踩的 6 个坑
  • 方法设计的最佳实践清单
  • 写在最后 & 课后练习

  • 一、为什么你需要方法,而不是一堆复制粘贴的代码

    先看一个真实的"翻车现场"。假设你要计算三个学生的平均分:

    int score1a = 88, score1b = 92, score1c = 75;
    double avg1 = (score1a + score1b + score1c) / 3.0;
    Console.WriteLine($"学生1平均分:{avg1}");

    int score2a = 60, score2b = 70, score2c = 80;
    double avg2 = (score2a + score2b + score2c) / 3.0;
    Console.WriteLine($"学生2平均分:{avg2}");

    int score3a = 95, score3b = 85, score3c = 90;
    double avg3 = (score3a + score3b + score3c) / 3.0;
    Console.WriteLine($"学生3平均分:{avg3}");

    这三段代码逻辑完全一样,只是数字不同。 如果哪天平均分的算法要改(比如加权平均),你得改三处,改漏一处就是一个 bug。

    用方法重写之后:

    static double CalculateAverage(int a, int b, int c) => (a + b + c) / 3.0;

    Console.WriteLine($"学生1平均分:{CalculateAverage(88, 92, 75)}");
    Console.WriteLine($"学生2平均分:{CalculateAverage(60, 70, 80)}");
    Console.WriteLine($"学生3平均分:{CalculateAverage(95, 85, 90)}");

    逻辑只写一次,改动只需要改一处。 这就是方法存在的第一重意义:消除重复(DRY 原则:Don’t Repeat Yourself)。而它的第二重、也是更重要的意义是——封装复杂度。调用 CalculateAverage 的人,完全不需要知道内部是怎么算的,只需要知道"给三个数,还我一个平均分"。这种"黑盒思维",正是软件工程能够支撑起大型系统的根基。


    二、方法的解剖学:签名、参数、返回值

    一个完整的方法长这样,我们把它"拆开"看:

    public static int Add(int a, int b)
    {
    return a + b;
    }
    // └┬┘ └┬─┘ └┬┘ └────┬────┘
    //访问修饰符 修饰符 返回值类型 方法名(参数列表)

    组成部分含义举例
    访问修饰符 谁能调用这个方法 public、private、protected、internal
    修饰符(可选) 附加行为标记 static(不依赖实例)、virtual(可被重写)、async(异步方法)
    返回值类型 方法执行完返回什么类型的数据 int、string、void(无返回值)、Task(异步)
    方法名 调用时使用的标识符 建议用动词或动词短语,如 CalculateTotal
    参数列表 调用时需要传入的数据 (int a, int b)
    方法体 具体的执行逻辑 { return a + b; }

    2.1 void:没有返回值的方法

    static void PrintWelcome(string userName)
    {
    Console.WriteLine($"欢迎回来,{userName}!");
    // 没有 return 语句,或用 return; 提前结束(不带值)
    }

    void 方法通常用于"做一件事"(打印、保存、发送),而不是"算出一个结果"。

    2.2 有返回值的方法:return 必须覆盖所有分支

    static string GetGrade(int score)
    {
    if (score >= 90) return "优秀";
    else if (score >= 60) return "及格";
    else return "不及格";
    // 编译器会检查:所有可能的执行路径都必须有 return,否则报错 CS0161
    }

    新手常见困惑: “为什么我明明写了 return,编译器还是报错说不是所有代码路径都返回值?” —— 通常是漏了 else 分支,或者 if 判断没有覆盖所有可能情况。养成"每个分支都显式 return"的习惯,能避免大量这类报错。


    三、参数传递的四种姿势:值传递、ref、out、in

    这一节是很多人似懂非懂、但面试必考的重灾区。我们用一个类比来讲清楚。

    3.1 值传递(默认方式):传的是"复印件"

    static void TryDouble(int number)
    {
    number *= 2; // 只修改了"复印件"
    Console.WriteLine($"方法内部:{number}");
    }

    int original = 5;
    TryDouble(original);
    Console.WriteLine($"方法外部:{original}");

    输出:

    方法内部:10
    方法外部:5 ← 原始值完全没变!

    原理: C# 默认按值传递——方法接收到的是实参的一份"拷贝",方法内部对参数的任何修改,都不会影响到调用方的原始变量。

    注意一个容易混淆的点: 如果参数是引用类型(比如对象、数组),传递的"复印件"其实是一份引用的拷贝,两个引用指向同一块内存——所以修改对象的属性会影响原对象,但把参数重新指向一个新对象则不会:

    static void ModifyList(List<int> list)
    {
    list.Add(99); // ✅ 会影响外部:操作的是同一块内存
    list = new List<int>(); // ❌ 不会影响外部:只是让"这份拷贝"指向了新对象
    }

    3.2 ref:真正的"原件直传",可读可写

    如果你确实需要在方法内部修改调用方的原始变量,用 ref:

    static void DoubleByRef(ref int number)
    {
    number *= 2; // 直接修改原始变量
    }

    int original = 5;
    DoubleByRef(ref original); // 调用时也必须写 ref
    Console.WriteLine(original); // 10 ← 真的被改了!

    关键规则: 使用 ref 前,调用方必须先给变量赋初值(因为 ref 既能读也能写,方法内部可能会先读取它)。

    3.3 out:专为"多返回值"而生

    C# 方法只能有一个 return 值,但业务中经常需要"返回多个结果",比如"尝试解析一个字符串,成功与否 + 解析出的值":

    static bool TryParseAge(string input, out int age)
    {
    if (int.TryParse(input, out age) && age >= 0 && age <= 150)
    return true;

    age = –1; // out 参数必须在方法内被赋值,哪怕是失败分支
    return false;
    }

    if (TryParseAge("25", out int result))
    Console.WriteLine($"解析成功:{result}");
    else
    Console.WriteLine("解析失败");

    out 与 ref 的核心区别:

    对比项refout
    调用前是否需要赋初值 ✅ 必须 ❌ 不需要
    方法内部是否必须赋值 可选(不强制) ✅ 必须(否则编译报错)
    语义 “我要读也要写” “我只关心你返回的值,不关心传进去时是什么”

    .NET 里最常见的 out 用法就是各种 TryXxx 方法:int.TryParse、Dictionary.TryGetValue、DateTime.TryParse——这是一种"不抛异常、用布尔值表示成功与否"的经典设计模式,比直接用 Parse(失败会抛异常)性能更好、更易于控制流程。

    3.4 in:只读引用,性能优化的"冷门利器"

    static decimal CalculateTax(in decimal amount)
    {
    // amount *= 1.1m; // ❌ 编译错误:in 参数是只读的,不能修改
    return amount * 0.06m;
    }

    in 关键字表示"按引用传递,但只读"——它的意义在于:对于体积较大的值类型(如自定义的大型 struct),可以避免值传递时的整体拷贝开销,同时又保证方法不会意外修改调用方的数据。日常业务开发中用得不多,但在高性能计算、游戏开发等场景中是重要的优化手段。

    3.5 四种传参方式速记表

    关键字传递方式是否要求预先赋值典型场景
    (默认) 值传递 不要求 绝大多数场景的默认选择
    ref 引用传递,可读写 ✅ 要求 需要在方法内修改调用方变量
    out 引用传递,只写 ❌ 不要求 需要"多返回值",如 TryParse 模式
    in 引用传递,只读 不要求 大型 struct 的性能优化

    四、可选参数与 params:让调用方"少写几个字"

    4.1 可选参数:给参数一个"默认值"

    static void SendMessage(string content, string channel = "站内信", bool urgent = false)
    {
    string prefix = urgent ? "【紧急】" : "";
    Console.WriteLine($"{prefix}通过[{channel}]发送:{content}");
    }

    SendMessage("系统维护通知"); // 用默认值
    SendMessage("服务器宕机", "短信", true); // 全部指定
    SendMessage("账单已生成", channel: "邮件"); // 具名参数,跳过中间的 urgent

    规则: 可选参数必须放在参数列表的最后,且一旦某个参数设了默认值,它后面的所有参数也必须有默认值。

    4.2 params:接收"数量不确定"的参数

    static int Sum(params int[] numbers)
    {
    int total = 0;
    foreach (int n in numbers) total += n;
    return total;
    }

    Console.WriteLine(Sum(1, 2)); // 3
    Console.WriteLine(Sum(1, 2, 3, 4, 5)); // 15
    Console.WriteLine(Sum()); // 0(也可以不传)

    Console.WriteLine 的多重载、string.Format、LINQ 的部分方法,底层都用到了 params——它让调用方可以像写"可变长参数函数"一样自然调用,而不用手动构造数组。

    规则: params 参数必须是方法参数列表中的最后一个,且一个方法只能有一个 params 参数。


    五、方法重载:同名不同貌的艺术

    5.1 什么是重载?

    重载(Overload):多个方法用相同的名字,但参数列表(类型、个数、顺序)不同。

    static int Add(int a, int b) => a + b;
    static double Add(double a, double b) => a + b;
    static int Add(int a, int b, int c) => a + b + c;
    static string Add(string a, string b) => a + b;

    Console.WriteLine(Add(1, 2)); // 调用第1个:3
    Console.WriteLine(Add(1.5, 2.5)); // 调用第2个:4.0
    Console.WriteLine(Add(1, 2, 3)); // 调用第3个:6
    Console.WriteLine(Add("Hello, ", "C#")); // 调用第4个:Hello, C#

    编译器是怎么决定调用哪一个的? 它会根据你传入的实参类型和数量,在编译期就静态匹配出最合适的重载版本——这个过程叫"重载决议(Overload Resolution)",发生在编译时,而不是运行时。

    5.2 重载的边界:返回值类型不算数

    static int GetValue() => 1;
    static double GetValue() => 1.0; // ❌ 编译错误!

    为什么? 因为方法调用时,调用方可能完全不关心返回值(比如 GetValue(); 这种写法不接收返回值),此时编译器无法仅凭返回值类型区分该调用哪一个——所以 C# 规定:重载的判断依据只能是参数列表,与返回值类型无关。

    5.3 重载 vs 可选参数:该用哪个?

    两者常常能实现相似的效果,但语义不同:

    // 方式一:可选参数
    static void Log(string message, string level = "Info") { ... }

    // 方式二:方法重载
    static void Log(string message) => Log(message, "Info");
    static void Log(string message, string level) { ... }

    一般建议: 参数含义相同、只是"要不要传"的场景,用可选参数更简洁;如果不同参数组合对应完全不同的处理逻辑,用重载更清晰。


    六、递归:方法调用自己,解决"自相似"问题

    6.1 核心思想

    递归 = 方法在自己的方法体内调用自己,通常用于解决"大问题可以拆解成结构相同的小问题"的场景。

    经典入门案例:计算阶乘 n! = n × (n-1) × (n-2) × … × 1

    static long Factorial(int n)
    {
    if (n <= 1) return 1; // 递归终止条件(Base Case),必不可少!
    return n * Factorial(n – 1); // 递归调用:把问题规模缩小 1
    }

    Console.WriteLine(Factorial(5)); // 5×4×3×2×1 = 120

    执行过程可视化(调用栈的展开与回收):

    Factorial(5)
    = 5 × Factorial(4)
    = 4 × Factorial(3)
    = 3 × Factorial(2)
    = 2 × Factorial(1)
    = 1 ← 触底,开始回溯

    回溯相乘:2×1=2 → 3×2=6 → 4×6=24 → 5×24=120

    6.2 递归的两大要素,缺一不可

  • 终止条件(Base Case):必须有一个"不再递归"的出口,否则会无限调用自己,最终抛出 StackOverflowException。
  • 递归推进:每一次递归调用,问题规模必须朝着终止条件"靠近"(比如 n 逐渐减小)。
  • // ❌ 危险示范:没有终止条件,或终止条件永远达不到
    static long BadFactorial(int n)
    {
    return n * BadFactorial(n – 1); // 永远不会停止,栈溢出!
    }

    6.3 什么时候该用递归?

    递归特别适合处理天然具有"自相似"结构的问题:

    • 树形结构遍历(文件夹及子文件夹、组织架构树、DOM 树)
    • 分治算法(快速排序、归并排序,参见我们之前讲排序算法的文章)
    • 数学定义本身就是递归的问题(阶乘、斐波那契数列、汉诺塔)

    实战案例:递归遍历文件夹

    static void ListAllFiles(string folderPath, int depth = 0)
    {
    string indent = new string(' ', depth * 2);

    foreach (string file in Directory.GetFiles(folderPath))
    Console.WriteLine($"{indent}📄 {Path.GetFileName(file)}");

    foreach (string subFolder in Directory.GetDirectories(folderPath))
    {
    Console.WriteLine($"{indent}📁 {Path.GetFileName(subFolder)}");
    ListAllFiles(subFolder, depth + 1); // 递归处理子文件夹
    }
    }

    性能提醒: 递归虽然优雅,但每次调用都会占用一份"调用栈"空间。对于深度可能极大的场景(比如处理几万层嵌套的数据),要么改写成迭代(用循环+自己维护的栈),要么注意语言是否支持尾递归优化(C# 编译器目前不保证尾递归优化,这点和 F# 不同,需要格外小心栈溢出风险)。


    七、本地函数与表达式主体方法:写更精炼的代码

    7.1 表达式主体方法(Expression-Bodied Method)

    如果方法体只有一行表达式,可以用 => 大幅简化:

    // 传统写法
    static int Square(int x)
    {
    return x * x;
    }

    // 表达式主体写法(等价,但更简洁)
    static int Square(int x) => x * x;

    这在前面的例子里我们已经多次用到(比如 CalculateAverage),它特别适合写简单的计算、转换、判断类方法。

    7.2 本地函数(Local Function):只属于当前方法的"私有小工具"

    有时候,某个辅助逻辑只在一个方法内部用得到,没必要单独定义成类的成员方法——这时可以用本地函数:

    static int CalculateFactorialSum(int n)
    {
    // 本地函数:只在 CalculateFactorialSum 内部可见,外部无法调用
    long Factorial(int x) => x <= 1 ? 1 : x * Factorial(x – 1);

    int sum = 0;
    for (int i = 1; i <= n; i++)
    sum += (int)Factorial(i);

    return sum;
    }

    本地函数的优势:

  • 作用域收敛——避免污染类的命名空间,也避免其他方法误用这个"仅供内部使用"的辅助逻辑。
  • 能直接访问外层方法的变量(闭包特性),不需要额外传参。
  • 相比 Lambda 表达式赋值给委托变量,性能开销更小(本地函数在大多数情况下不会产生额外的堆分配)。

  • 八、扩展方法:给别人的类"加装备"

    8.1 痛点场景

    假设你想给 string 类型加一个"判断是否是有效手机号"的方法,但 string 是 .NET 内置的密封类,你没有权限修改它的源代码。怎么办?

    8.2 扩展方法登场

    public static class StringExtensions
    {
    // 关键点:静态类、静态方法,第一个参数前加 this
    public static bool IsValidPhoneNumber(this string input)
    {
    return !string.IsNullOrEmpty(input)
    && input.Length == 11
    && input.All(char.IsDigit)
    && input.StartsWith("1");
    }
    }

    // 使用时,就像调用 string 的原生方法一样自然!
    string phone = "13812345678";
    Console.WriteLine(phone.IsValidPhoneNumber()); // True

    它是怎么做到的? this string input 这个语法糖告诉编译器:这个方法可以像"实例方法"一样,被挂载到 string 类型上直接调用。本质上编译器只是把 phone.IsValidPhoneNumber() 悄悄翻译成了 StringExtensions.IsValidPhoneNumber(phone)——没有修改 string 类本身,只是提供了一种更自然的调用语法。

    8.3 你每天都在用的扩展方法:LINQ

    List<int> numbers = new() { 1, 2, 3, 4, 5 };

    var evenNumbers = numbers.Where(n => n % 2 == 0); // Where 就是扩展方法!
    var doubled = numbers.Select(n => n * 2); // Select 也是!

    Where、Select、OrderBy 这些 LINQ 方法,全部是定义在 System.Linq.Enumerable 静态类中的扩展方法,专门扩展了 IEnumerable<T> 接口——理解了扩展方法,你才算真正理解了 LINQ 为什么能"长在"任何集合类型上。


    九、实战演练:搭建一个"购物车结算系统"

    综合运用前面所有知识点,我们来实现一个小型购物车结算功能。

    using System;
    using System.Collections.Generic;
    using System.Linq;

    // 扩展方法:给 decimal 增加"格式化为人民币"的能力
    public static class DecimalExtensions
    {
    public static string ToRMB(this decimal amount) => $"¥{amount:F2}";
    }

    public class CartItem
    {
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
    public int Quantity { get; set; }
    }

    class ShoppingCart
    {
    static void Main()
    {
    var items = new List<CartItem>
    {
    new CartItem { Name = "键盘", Price = 199.9m, Quantity = 1 },
    new CartItem { Name = "鼠标", Price = 89.5m, Quantity = 2 },
    new CartItem { Name = "耳机", Price = 299m, Quantity = 1 },
    };

    // 调用方法:可选参数 + params 演示优惠券叠加
    decimal total = Checkout(items, useMemberDiscount: true, "满300减30", "新人立减10");

    Console.WriteLine($"\\n最终应付金额:{total.ToRMB()}"); // 调用扩展方法

    // 递归演示:计算购物车中所有商品名称的字符总长度(示意用途)
    Console.WriteLine($"商品名称总字数:{TotalNameLength(items, 0)}");

    // out 演示:尝试应用库存校验
    if (TryValidateStock(items, out string errorMessage))
    Console.WriteLine("库存校验通过,可以下单!");
    else
    Console.WriteLine($"下单失败:{errorMessage}");
    }

    // 本地函数 + 可选参数 + params 综合应用
    static decimal Checkout(List<CartItem> items, bool useMemberDiscount = false, params string[] coupons)
    {
    // 本地函数:计算商品总价,只在 Checkout 内部使用
    decimal CalculateSubtotal() => items.Sum(i => i.Price * i.Quantity);

    decimal subtotal = CalculateSubtotal();
    Console.WriteLine($"商品小计:{subtotal.ToRMB()}");

    decimal discount = 0;

    if (useMemberDiscount)
    {
    discount += subtotal * 0.05m; // 会员95折
    Console.WriteLine($"会员折扣:-{(subtotal * 0.05m).ToRMB()}");
    }

    foreach (string coupon in coupons)
    {
    if (coupon.Contains("满300减30") && subtotal >= 300)
    {
    discount += 30;
    Console.WriteLine($"优惠券[{coupon}]:-¥30.00");
    }
    else if (coupon.Contains("新人立减10"))
    {
    discount += 10;
    Console.WriteLine($"优惠券[{coupon}]:-¥10.00");
    }
    }

    return subtotal – discount;
    }

    // 递归演示:累加所有商品名称长度
    static int TotalNameLength(List<CartItem> items, int index)
    {
    if (index >= items.Count) return 0; // 终止条件
    return items[index].Name.Length + TotalNameLength(items, index + 1);
    }

    // out 演示:TryXxx 模式做库存校验
    static bool TryValidateStock(List<CartItem> items, out string errorMessage)
    {
    foreach (var item in items)
    {
    if (item.Quantity <= 0)
    {
    errorMessage = $"{item.Name} 的购买数量必须大于 0";
    return false;
    }
    }
    errorMessage = string.Empty;
    return true;
    }
    }

    运行结果

    商品小计:¥778.90
    会员折扣:-¥38.95
    优惠券[满300减30]:-¥30.00
    优惠券[新人立减10]:-¥10.00

    最终应付金额:¥699.95
    商品名称总字数:6
    库存校验通过,可以下单!

    这个案例里,扩展方法负责"美化输出",本地函数负责"内部计算小工具",可选参数 + params负责"灵活接收优惠信息",递归负责"处理列表的自相似遍历",out 参数负责"多返回值的校验模式"——五个知识点在一个真实场景里各司其职,这正是方法设计的价值所在。


    十、高手都会踩的 6 个坑

    坑 1:ref/out 忘记在调用处也写关键字

    static void Increment(ref int x) => x++;

    int a = 5;
    Increment(a); // ❌ 编译错误:缺少 ref 关键字
    Increment(ref a); // ✅ 正确

    C# 故意要求调用方也显式写出 ref/out,就是为了让人一眼就能看出"这个调用可能会修改我的变量",避免隐式的"隔空修改"造成困惑。

    坑 2:递归忘记写终止条件

    前面已经强调过,这里再次提醒:每写一个递归方法,先写终止条件,再写递归逻辑,这是养成良好递归思维的第一步。

    坑 3:过度使用 out,导致方法签名混乱

    // ❌ 反面教材:3 个 out 参数,调用方根本看不懂在做什么
    static void ProcessOrder(int orderId, out bool success, out string message, out decimal amount) { ... }

    超过 1~2 个 out 参数时,更好的做法是定义一个专门的返回类型(类或元组):

    // ✅ 更清晰
    static (bool Success, string Message, decimal Amount) ProcessOrder(int orderId) { ... }

    var result = ProcessOrder(1001);
    if (result.Success) Console.WriteLine(result.Amount);

    C# 7.0 引入的元组返回值,在很多场景下比 out 参数更优雅、可读性更强。

    坑 4:值类型 vs 引用类型的传参误解

    static void AddItem(List<CartItem> cart, CartItem item)
    {
    cart.Add(item); // ✅ 影响外部:List是引用类型,操作的是同一个对象
    }

    static void ResetCart(List<CartItem> cart)
    {
    cart = new List<CartItem>(); // ❌ 不影响外部:只是让局部变量指向了新对象
    }

    如果想让 ResetCart 真正清空外部的购物车,要么用 cart.Clear()(修改对象内容),要么改用 ref List<CartItem> cart(修改引用本身)。

    坑 5:方法参数过多,暗示设计需要重构

    // ❌ 参数多到自己都数不清
    static void CreateUser(string name, int age, string email, string phone,
    string address, string city, string country, bool isVip) { ... }

    经验法则: 方法参数超过 4~5 个时,通常意味着这些参数天然属于同一个概念,应该封装成一个类或结构体:

    // ✅ 更清晰、更易扩展
    static void CreateUser(UserCreationRequest request) { ... }

    坑 6:滥用可选参数导致的"隐藏 bug"

    static void SendEmail(string to, string subject = "无主题", string body = "")
    {
    // ……
    }

    // 半年后,团队在 subject 和 body 之间插入了一个新的可选参数
    static void SendEmail(string to, string subject = "无主题", bool isHtml = false, string body = "")
    {
    // ……
    }

    // 原来的位置传参调用会静默出错!
    SendEmail("a@b.com", "标题", "正文内容"); // "正文内容" 现在被传给了 isHtml!!

    教训: 涉及多个可选参数的调用,尽量使用具名参数(SendEmail(to: "a@b.com", subject: "标题", body: "正文内容")),既能避免这种"参数错位"的隐藏 bug,也能大幅提升调用处的可读性。


    十一、方法设计的最佳实践清单

    ✅ 方法名用动词或动词短语,清晰表达"做什么":CalculateTotal、ValidateInput、SendNotification

    ✅ 一个方法只做一件事(单一职责原则)。如果方法名里出现了"And"(比如 SaveAndSendEmail),八成需要拆分成两个方法

    ✅ 参数数量控制在 4 个以内,超过就考虑封装成对象

    ✅ 优先使用具名参数调用带有多个可选参数的方法,提升可读性

    ✅ TryXxx + out 模式用于"可能失败但不想用异常控制流程"的场景

    ✅ 递归方法务必先写终止条件,并评估是否存在栈溢出风险

    ✅ 简单的单行逻辑,善用表达式主体方法(=>),让代码更紧凑

    ✅ 只在方法内部使用的辅助逻辑,用本地函数,避免污染类的公共接口


    十二、写在最后

    方法看似是 C# 入门阶段最"基础"的知识点,但它其实是贯穿整个编程生涯的核心设计单元。你写的每一个类、每一个模块,最终都是由无数个方法组合而成的——方法设计得好不好,直接决定了代码是"清晰易维护"还是"一团乱麻"。

    从今天起,写完一个方法,不妨问自己三个问题:

  • 这个方法名,能不能让同事一眼看懂它在做什么?
  • 参数列表是不是太长了,需不需要封装?
  • 有没有更简洁的写法(表达式主体、本地函数、元组返回值)能表达同样的逻辑?
  • 坚持这样的自我审视,你的代码质量会在不知不觉中上一个台阶。


    📝 课后练习

  • 把第九节"购物车结算系统"中的 TryValidateStock 方法,改造成返回元组 (bool IsValid, string ErrorMessage) 的版本,并对比两种写法的可读性。
  • 用递归实现一个"计算斐波那契数列第 n 项"的方法,并思考:为什么朴素递归实现在 n 较大时会非常慢?(提示:画出递归调用树,观察重复计算)
  • 写一个扩展方法 IsPalindrome,扩展 string 类型,判断一个字符串是否为回文(正读反读都一样,如"上海自来水来自海上"忽略标点后)。
  • 觉得有收获?欢迎点赞收藏,下次写方法时,不妨对照第十一节的清单自查一遍 😉

    赞(0)
    未经允许不得转载:171主机测评 » C# 方法全解析:从“能跑就行“到“优雅精准“,你的代码封装能力够格了吗?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址