CSharp(十八) 方法(Method)详解
目录
1. 方法的基本概念
方法(Method) 是一段可重用的代码块,用于执行特定的操作或计算。方法是 C# 中最基本的代码组织单元,属于类(class)或结构体(struct)的成员。
方法的核心作用:
- 代码复用:避免重复编写相同逻辑
- 模块化:将复杂问题拆解为多个小方法
- 封装:隐藏实现细节,对外暴露统一接口
- 可维护性:修改一处,处处生效
2. 方法的定义
2.1 基本语法
// [访问修饰符] [其他修饰符] 返回类型 方法名([参数列表])
// {
// // 方法体
// [return 返回值;]
// }
2.2 完整组成要素
| 要素 | 说明 | 是否必须 |
|---|---|---|
| 访问修饰符 | public / private / protected / internal / protected internal / private protected | 可选(默认 private) |
| 其他修饰符 | static / abstract / virtual / override / sealed / async / partial / extern | 可选 |
| 返回类型 | 任意数据类型、void、Task 等 | 必须 |
| 方法名 | 符合 C# 命名规范(PascalCase) | 必须 |
| 参数列表 | 圆括号包围的逗号分隔参数 | 必须(无参时为空括号) |
| 方法体 | 花括号包围的代码块 | 必须(抽象方法和分部方法除外) |
2.3 基本示例
public class Calculator
{
// 无参数、无返回值的方法
public void SayHello()
{
Console.WriteLine("Hello, C#!");
}
// 有参数、有返回值的方法
public int Add(int a, int b)
{
return a + b;
}
// 静态方法(属于类本身,不依赖实例)
public static double PI()
{
return 3.141592653589793;
}
}
2.4 表达式体方法(C# 6.0 / 7.0+)
对于只有一条语句的简单方法,可以使用表达式体语法:
// 表达式体方法(无返回值)
public void Print(string msg) => Console.WriteLine(msg);
// 表达式体方法(有返回值)
public int Multiply(int a, int b) => a * b;
// 静态表达式体方法
public static double Square(double x) => x * x;
3. 方法的参数
3.1 值参数(Value Parameter)— 默认方式
按值传递,方法内修改不影响原变量(对于引用类型则是复制引用地址)。
public void Increment(int num)
{
num++; // 不影响调用处的原变量
Console.WriteLine($"方法内部: {num}");
}
// 调用
int value = 10;
Increment(value); // 输出: 方法内部: 11
Console.WriteLine(value); // 输出: 10(原值不变)
对于引用类型(如 class),复制的是引用,因此修改对象的属性会影响原对象:
public class Person
{
public string Name { get; set; }
}
public void ChangeName(Person p)
{
p.Name = "李四"; // 会修改原对象的属性
p = new Person { Name = "王五" }; // 不会影响原引用
}
3.2 引用参数(ref Parameter)
使用 ref 关键字,传递的是变量的引用,方法内修改会影响原变量。
要求:变量在传入前必须先初始化。
public void Swap(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
// 调用
int a = 10, b = 20;
Swap(ref a, ref b);
Console.WriteLine($"a={a}, b={b}"); // 输出: a=20, b=10
3.3 输出参数(out Parameter)
使用 out 关键字,用于从方法返回多个值。
特点:
- 变量传入前可以不初始化
- 方法内必须给 out 参数赋值
public bool TryParseInt(string input, out int result)
{
// out 参数在方法返回前必须被赋值
return int.TryParse(input, out result);
}
// 调用
if (TryParseInt("123", out int number))
{
Console.WriteLine($"解析成功: {number}"); // 输出: 解析成功: 123
}
C# 7.0+ 内联声明:可以在调用时直接声明 out 变量。
// 内联声明 out 变量
TryParseInt("456", out int num);
Console.WriteLine(num); // 456
3.4 参数数组(params Parameter)
使用 params 关键字,允许传递可变数量的实参。
限制:
- 必须是参数列表中的最后一个参数
- 只能有一个 params 参数
- 必须是一维数组类型
public int Sum(params int[] numbers)
{
int total = 0;
foreach (int n in numbers)
{
total += n;
}
return total;
}
// 调用方式
int result1 = Sum(1, 2, 3); // 6
int result2 = Sum(1, 2, 3, 4, 5, 6, 7); // 28
int result3 = Sum(new int[] { 10, 20, 30 }); // 60(也可以传数组)
int result4 = Sum(); // 0(可以不传参数)
3.5 可选参数(Optional Parameter)
为参数指定默认值,调用时可以省略该参数。
限制:可选参数必须出现在所有必需参数之后。
public string Greet(string name, string greeting = "你好", string punctuation = "!")
{
return $"{greeting},{name}{punctuation}";
}
// 调用
Console.WriteLine(Greet("小明")); // 你好,小明!
Console.WriteLine(Greet("小明", "早上好")); // 早上好,小明!
Console.WriteLine(Greet("小明", "Good morning", ".")); // Good morning,小明.
// 命名参数,可以跳过中间的可选参数
Console.WriteLine(Greet("小明", punctuation: "。")); // 你好,小明。
3.6 命名参数(Named Parameter)
调用时通过参数名指定值,不依赖参数位置顺序。
public void CreateUser(string name, int age, string email, bool isActive = true)
{
Console.WriteLine($"姓名:{name}, 年龄:{age}, 邮箱:{email}, 激活:{isActive}");
}
// 使用命名参数,可以不按顺序
CreateUser(age: 25, name: "张三", email: "zhangsan@example.com");
CreateUser("李四", email: "lisi@test.com", age: 30, isActive: false);
3.7 in 参数(只读引用参数,C# 7.2+)
使用 in 关键字,以引用方式传递但不可修改。适用于大型值类型的传递以提升性能。
public struct LargeStruct
{
public int A, B, C, D, E, F, G, H;
}
// in 参数:按引用传递但不可修改
public int Sum(in LargeStruct data)
{
// data.A = 10; // 编译错误!不能修改 in 参数
return data.A + data.B + data.C + data.D;
}
// 调用(in 在调用处可省略)
LargeStruct ls = new LargeStruct { A = 1, B = 2, C = 3, D = 4 };
int result = Sum(ls); // in 可省略
int result2 = Sum(in ls); // 或显式使用 in
3.8 各参数类型对比总结
| 参数类型 | 关键字 | 传入前初始化 | 方法内必须赋值 | 方法内修改影响外部 |
|---|---|---|---|---|
| 值参数(默认) | 无 | 是 | 否 | 否(值类型)/ 部分(引用类型属性) |
| 引用参数 | ref |
是 | 否 | 是 |
| 输出参数 | out |
否 | 是 | 是 |
| 参数数组 | params |
否 | 否 | 否 |
| 可选参数 | 默认值 | 否(可省略) | 否 | 否 |
| in 参数 | in |
是 | 否 | 否(只读) |
4. 方法的返回值
4.1 void — 无返回值
方法执行操作但不返回任何数据。
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
// 可以省略 return 语句
// 或者使用 return; (仅跳出方法)
}
4.2 返回基本类型
public int Add(int a, int b)
{
return a + b;
}
public bool IsEven(int number)
{
return number % 2 == 0;
}
public string GetFullName(string firstName, string lastName)
{
return $"{lastName}{firstName}";
}
4.3 返回自定义类型 / 集合
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
public Student FindTopStudent(List<Student> students)
{
if (students == null || students.Count == 0)
return null; // 可以返回 null
var top = students[0];
foreach (var s in students)
{
if (s.Score > top.Score)
top = s;
}
return top;
}
public List<Student> GetPassedStudents(List<Student> students, int passLine = 60)
{
return students.Where(s => s.Score >= passLine).ToList();
}
4.4 返回多个值 — Tuple(元组)
C# 7.0+ 元组(推荐):
// 命名元组
public (int Min, int Max, double Average) Analyze(int[] numbers)
{
if (numbers == null || numbers.Length == 0)
throw new ArgumentException("数组不能为空");
int min = numbers[0], max = numbers[0];
double sum = 0;
foreach (int n in numbers)
{
if (n < min) min = n;
if (n > max) max = n;
sum += n;
}
return (min, max, sum / numbers.Length);
}
// 调用与解构
int[] data = { 3, 1, 7, 5, 9, 2 };
var result = Analyze(data);
Console.WriteLine($"最小值:{result.Min}, 最大值:{result.Max}, 平均值:{result.Average:F2}");
// 直接解构
var (min, max, avg) = Analyze(data);
Console.WriteLine($"min={min}, max={max}, avg={avg:F2}");
System.Tuple(旧版):
public Tuple<int, int, double> AnalyzeOld(int[] numbers)
{
// ... 计算逻辑
return Tuple.Create(min, max, average);
}
// 调用
var result = AnalyzeOld(data);
Console.WriteLine($"Item1:{result.Item1}, Item2:{result.Item2}, Item3:{result.Item3}");
4.5 返回多个值 — out 参数
public bool Divide(int dividend, int divisor, out int quotient, out int remainder)
{
if (divisor == 0)
{
quotient = 0;
remainder = 0;
return false;
}
quotient = dividend / divisor;
remainder = dividend % divisor;
return true;
}
// 调用
if (Divide(17, 5, out int q, out int r))
{
Console.WriteLine($"商={q}, 余数={r}"); // 商=3, 余数=2
}
4.6 返回 IEnumerable — 迭代器方法(yield return)
public IEnumerable<int> GetFibonacci(int count)
{
int a = 0, b = 1;
for (int i = 0; i < count; i++)
{
yield return a;
int temp = a + b;
a = b;
b = temp;
}
}
// 调用(延迟执行)
foreach (int num in GetFibonacci(10))
{
Console.Write($"{num} "); // 0 1 1 2 3 5 8 13 21 34
}
4.7 返回 async Task — 异步方法
public async Task<string> DownloadContentAsync(string url)
{
using var client = new HttpClient();
string content = await client.GetStringAsync(url);
return content;
}
// 无返回值的异步方法
public async Task ProcessDataAsync()
{
await Task.Delay(1000); // 模拟耗时操作
Console.WriteLine("处理完成");
}
// 返回 Task<T>
public async Task<int> CalculateAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(500);
return 42;
});
}
4.8 提前返回与 guard clause
public decimal CalculateDiscount(User user, decimal amount)
{
// Guard clauses — 提前返回异常情况
if (user == null)
throw new ArgumentNullException(nameof(user));
if (amount <= 0)
return 0;
// 业务逻辑
if (user.IsVIP)
return amount * 0.2m;
if (user.YearsOfMembership > 5)
return amount * 0.1m;
return 0;
}
5. 方法重载(Overloading)
5.1 重载的概念
方法重载 是指同一个类中可以定义多个名称相同但参数列表不同(参数数量、类型或顺序不同)的方法。
规则:
| 可以作为重载区分依据 | 不可以作为重载区分依据 |
|---|---|
| 参数数量不同 | 仅返回值类型不同 |
| 参数类型不同 | 仅参数名不同 |
| 参数类型顺序不同(建议避免) | 仅修饰符不同(ref/out) |
| - | params 与普通数组参数 |
5.2 基本重载示例
public class Formatter
{
// 无参数
public string Format()
{
return "默认格式化";
}
// 重载:参数数量不同
public string Format(string text)
{
return $"【格式化】{text}";
}
// 重载:参数类型不同
public string Format(int number)
{
return $"数字格式化: {number:N0}";
}
// 重载:参数类型不同
public string Format(DateTime date)
{
return $"日期格式化: {date:yyyy年MM月dd日}";
}
// 重载:两个不同类型参数
public string Format(string text, int repeat)
{
return string.Concat(Enumerable.Repeat(text, repeat));
}
// 重载:参数类型顺序不同(不推荐,容易产生歧义)
public string Format(int repeat, string text)
{
return string.Concat(Enumerable.Repeat(text, repeat));
}
}
5.3 构造函数重载
public class Rectangle
{
public double Width { get; }
public double Height { get; }
// 默认构造函数
public Rectangle() : this(1, 1) // 调用下面的构造函数
{
}
// 正方形构造函数
public Rectangle(double side) : this(side, side)
{
}
// 主构造函数
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public double Area() => Width * Height;
}
// 使用
var r1 = new Rectangle(); // 1 x 1
var r2 = new Rectangle(5); // 5 x 5
var r3 = new Rectangle(4, 6); // 4 x 6
5.4 运算符重载
public readonly struct Complex : IEquatable<Complex>
{
public double Real { get; }
public double Imaginary { get; }
public Complex(double real, double imaginary)
{
Real = real;
Imaginary = imaginary;
}
// 重载 + 运算符
public static Complex operator +(Complex a, Complex b)
=> new Complex(a.Real + b.Real, a.Imaginary + b.Imaginary);
// 重载 - 运算符
public static Complex operator -(Complex a, Complex b)
=> new Complex(a.Real - b.Real, a.Imaginary - b.Imaginary);
// 重载 * 运算符
public static Complex operator *(Complex a, Complex b)
=> new Complex(
a.Real * b.Real - a.Imaginary * b.Imaginary,
a.Real * b.Imaginary + a.Imaginary * b.Real);
// 重载 == 和 != 运算符
public static bool operator ==(Complex a, Complex b) => a.Equals(b);
public static bool operator !=(Complex a, Complex b) => !a.Equals(b);
// 隐式转换重载
public static implicit operator Complex(double real) => new Complex(real, 0);
// 重写 Equals 和 GetHashCode(与运算符重载配套)
public override bool Equals(object obj) => obj is Complex c && Equals(c);
public bool Equals(Complex other) => Real == other.Real && Imaginary == other.Imaginary;
public override int GetHashCode() => HashCode.Combine(Real, Imaginary);
public override string ToString() => $"({Real} + {Imaginary}i)";
}
// 使用
var c1 = new Complex(3, 4);
var c2 = new Complex(1, 2);
Complex c3 = c1 + c2; // (4 + 6i)
Complex c4 = 5; // 隐式转换 (5 + 0i)
5.5 泛型方法与重载
public class Repository
{
// 泛型方法
public T GetById<T>(int id) where T : class
{
Console.WriteLine($"获取 {typeof(T).Name},ID={id}");
return default; // 实际中会查询数据库
}
// 重载泛型方法(不同参数数量)
public T GetById<T>(int id, bool includeDeleted) where T : class
{
Console.WriteLine($"获取 {typeof(T).Name},ID={id},包含已删除:{includeDeleted}");
return default;
}
public string GetById(string entityName, int id)
{
return $"实体:{entityName}, ID:{id}";
}
}
6. 具体使用示例
6.1 综合示例:学生管理系统
using System;
using System.Collections.Generic;
using System.Linq;
namespace StudentManagement
{
// 学生实体类
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public List<double> Scores { get; set; } = new List<double>();
public override string ToString()
=> $"学号:{Id}, 姓名:{Name}, 年龄:{Age}";
}
// 学生管理服务
public class StudentManager
{
private readonly List<Student> _students = new List<Student>();
private int _nextId = 1;
// ========== 添加学生(重载)==========
// 重载1:最少信息
public Student AddStudent(string name, int age)
{
return AddStudent(name, age, Array.Empty<double>());
}
// 重载2:包含成绩
public Student AddStudent(string name, int age, params double[] scores)
{
var student = new Student
{
Id = _nextId++,
Name = name,
Age = age,
Scores = scores.ToList()
};
_students.Add(student);
return student;
}
// 重载3:通过已有 Student 对象添加
public Student AddStudent(Student student)
{
if (student == null)
throw new ArgumentNullException(nameof(student));
student.Id = _nextId++;
_students.Add(student);
return student;
}
// ========== 查找学生 ==========
// 返回单个值 + out 参数
public bool TryFindStudent(int id, out Student found)
{
found = _students.FirstOrDefault(s => s.Id == id);
return found != null;
}
// 使用 Tuple 返回多项统计
public (Student TopStudent, double Average, int Count) GetStatistics()
{
if (_students.Count == 0)
return (null, 0, 0);
var allScores = _students.SelectMany(s => s.Scores).ToList();
double avg = allScores.Count > 0 ? allScores.Average() : 0;
Student top = _students
.Where(s => s.Scores.Count > 0)
.OrderByDescending(s => s.Scores.Max())
.FirstOrDefault();
return (top, avg, _students.Count);
}
// 使用 ref 参数修改数据
public bool UpdateAge(int id, ref int newAge)
{
if (TryFindStudent(id, out var student))
{
student.Age = newAge;
return true;
}
return false;
}
// params 批量删除
public int RemoveStudents(params int[] ids)
{
return _students.RemoveAll(s => ids.Contains(s.Id));
}
// 可选参数 + 可空值类型
public List<Student> Filter(
int? minAge = null,
int? maxAge = null,
string nameContains = null,
int limit = 10)
{
var query = _students.AsEnumerable();
if (minAge.HasValue)
query = query.Where(s => s.Age >= minAge.Value);
if (maxAge.HasValue)
query = query.Where(s => s.Age <= maxAge.Value);
if (!string.IsNullOrEmpty(nameContains))
query = query.Where(s => s.Name.Contains(nameContains));
return query.Take(limit).ToList();
}
// 迭代器方法
public IEnumerable<Student> GetAllStudents()
{
foreach (var student in _students)
{
yield return student;
}
}
// 异步方法
public async Task<List<Student>> FilterAsync(int? minAge = null)
{
await Task.Delay(10); // 模拟异步操作
return Filter(minAge: minAge);
}
}
// ========== 使用示例 ==========
class Program
{
static async Task Main(string[] args)
{
var manager = new StudentManager();
// 使用不同的 AddStudent 重载
manager.AddStudent("张三", 20);
manager.AddStudent("李四", 22, 85, 92, 78, 95);
manager.AddStudent("王五", 19, 90, 88, 76);
manager.AddStudent("赵六", 21, 70, 65, 80);
// 使用 TryFindStudent(含 out 参数)
if (manager.TryFindStudent(2, out var student))
{
Console.WriteLine($"找到学生: {student}");
}
// 使用 Tuple 统计
var (topStudent, average, totalCount) = manager.GetStatistics();
Console.WriteLine($"第一名: {topStudent?.Name}, 平均分: {average:F2}, 总人数: {totalCount}");
// 使用 ref 参数
int newAge = 23;
manager.UpdateAge(1, ref newAge);
// 使用可选参数过滤
var teens = manager.Filter(minAge: 13, maxAge: 20);
Console.WriteLine($"\n青少年组({teens.Count}人):");
foreach (var s in teens)
Console.WriteLine($" {s}");
// 使用命名参数
var namedFilter = manager.Filter(nameContains: "张", limit: 5);
// 使用异步方法
var allStudents = await manager.FilterAsync();
// 使用 params 删除
int removed = manager.RemoveStudents(3, 4);
Console.WriteLine($"\n已删除 {removed} 名学生");
}
}
}
6.2 扩展方法(Extension Method)
扩展方法允许向已有类型"添加"方法,而无需修改原始类型。
// 扩展方法必须定义在静态类中
public static class StringExtensions
{
// 为 string 类型添加扩展方法
// 第一个参数用 this 修饰,表示扩展的目标类型
public static bool IsEmail(this string str)
{
if (string.IsNullOrWhiteSpace(str))
return false;
return str.Contains('@') && str.Contains('.');
}
public static string Truncate(this string str, int maxLength)
{
if (string.IsNullOrEmpty(str)) return str;
return str.Length <= maxLength ? str : str[..maxLength] + "...";
}
public static string Reverse(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
char[] chars = str.ToCharArray();
Array.Reverse(chars);
return new string(chars);
}
// 泛型扩展方法
public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
=> source == null || !source.Any();
}
// 使用扩展方法
public static class ExtensionMethodDemo
{
public static void Run()
{
string email = "user@example.com";
Console.WriteLine(email.IsEmail()); // True
string longText = "这是一段很长的文字需要截断显示";
Console.WriteLine(longText.Truncate(8)); // 这是一段很长的文字...
string name = "Hello";
Console.WriteLine(name.Reverse()); // olleH
List<int> emptyList = new List<int>();
Console.WriteLine(emptyList.IsNullOrEmpty()); // True
}
}
6.3 本地函数(Local Function,C# 7.0+)
方法内部可以定义局部函数,只在该方法内可见。
public class QuickSort
{
public static int[] Sort(int[] array)
{
if (array == null || array.Length <= 1)
return array?.ToArray() ?? Array.Empty<int>();
var result = array.ToArray();
// 本地函数
void QuickSortRecursive(int[] arr, int left, int right)
{
if (left >= right) return;
int Partition(int[] a, int l, int r)
{
int pivot = a[r];
int i = l - 1;
for (int j = l; j < r; j++)
{
if (a[j] < pivot)
{
i++;
(a[i], a[j]) = (a[j], a[i]);
}
}
(a[i + 1], a[r]) = (a[r], a[i + 1]);
return i + 1;
}
int pivotIndex = Partition(arr, left, right);
QuickSortRecursive(arr, left, pivotIndex - 1);
QuickSortRecursive(arr, pivotIndex + 1, right);
}
QuickSortRecursive(result, 0, result.Length - 1);
return result;
}
}
// 使用
int[] numbers = { 9, 3, 7, 1, 5, 4, 8, 2, 6 };
int[] sorted = QuickSort.Sort(numbers);
Console.WriteLine(string.Join(", ", sorted)); // 1, 2, 3, 4, 5, 6, 7, 8, 9
6.4 匿名方法与 Lambda 表达式
public class LambdaDemo
{
public static void Run()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 使用具名方法
bool IsEven(int n) => n % 2 == 0;
var evens1 = numbers.FindAll(IsEven);
// 使用匿名方法(C# 2.0)
var evens2 = numbers.FindAll(delegate(int n) { return n % 2 == 0; });
// 使用 Lambda 表达式(C# 3.0+)
var evens3 = numbers.FindAll(n => n % 2 == 0);
// Lambda 表达式多语句
Func<int, int, int> complexCalc = (x, y) =>
{
int intermediate = x * x + y * y;
return (int)Math.Sqrt(intermediate);
};
Console.WriteLine(complexCalc(3, 4)); // 5
}
}
6.5 虚方法、抽象方法与接口方法
// ========== 基类 ==========
public abstract class Shape
{
// 抽象方法(必须在派生类中实现)
public abstract double Area();
// 虚方法(有默认实现,可被重写)
public virtual string Description()
{
return $"这是一个形状,面积: {Area():F2}";
}
// 普通方法(不能被重写)
public void PrintInfo()
{
Console.WriteLine(Description());
}
}
// ========== 派生类 ==========
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius) => Radius = radius;
// 实现抽象方法
public override double Area() => Math.PI * Radius * Radius;
// 重写虚方法
public override string Description()
=> $"圆形(半径={Radius}),面积: {Area():F2}";
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
// 实现抽象方法
public override double Area() => Width * Height;
// 重写虚方法
public override string Description()
=> $"矩形({Width}x{Height}),面积: {Area():F2}";
}
// 使用示例(多态)
Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };
foreach (var shape in shapes)
{
shape.PrintInfo();
}
// 输出:
// 圆形(半径=5),面积: 78.54
// 矩形(4x6),面积: 24.00
总结
| 概念 | 关键点 |
|---|---|
| 方法定义 | 访问修饰符 + 返回类型 + 方法名 + 参数 + 方法体 |
| 值参数 | 默认传递方式,值类型复制值,引用类型复制引用 |
| ref 参数 | 双向传递,传入前必须初始化 |
| out 参数 | 输出传递,传入前可不初始化,方法内必须赋值 |
| params 参数 | 可变数量参数,必须是最后一个参数 |
| 可选参数 | 提供默认值,必须放在必需参数之后 |
| 命名参数 | 按名称传参,忽略位置顺序 |
| 返回值 | void、基本类型、自定义类型、Tuple、IEnumerable、Task 等 |
| 方法重载 | 同名方法,参数列表不同(数量/类型/顺序) |
| 扩展方法 | 静态类中的静态方法,第一个参数用 this 修饰 |
| 本地函数 | 方法内部定义的函数,只在该方法内可见 |
| 表达式体 | => 简化单语句方法 |