news 2026/5/21 2:16:46

C#学习笔记-入门篇

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C#学习笔记-入门篇

本人是java后端出生,但是公司是csharp技术栈,所以开始学习csharp的相关知识,如果你也是java出生的话思维应该和我差不多,所以希望这篇笔记能够对有相似需求的朋友有所帮助

笔记大纲是参照b站的一个视频,不过我没有去仔细看,如果你喜欢看视频学习的话也可以去看该视频进行csharp的相关学习

面向Java程序员的C#基础课程——入门篇https://www.bilibili.com/video/BV1ZsxmeHEW8?spm_id_from=333.788.videopod.sections&vd_source=668b15b6e26adc9a2edc438f4b6926b1

1. 基础类型、字面量与常量

1.1 核心概念

  • ​int​ / long​:整数,区别主要是范围(32 位 vs 64 位)。

  • ​float​ / double​ / decimal​:小数类型。

    • ​double​:通用默认选择。
    • ​decimal​:金额优先。
  • ​const​:编译期常量,必须声明时赋值,后续不可改。

1.2 常用写法

  • ​L​:long​ 字面量后缀。
  • ​F​:float​ 字面量后缀。
  • ​M​:decimal​ 字面量后缀。
  • 小数字面量默认是 double​。
  • 数字分隔符 _​ 只影响可读性,不影响值。

1.3 关键代码

// 常见基础类型 int age = 28; long population = 1_412_000_000L; // L 表示 long float temperature = 36.5F; // F 表示 float double pi = 3.14; // 小数默认 double decimal salary = 12345.67M; // M 表示 decimal char grade = 'A'; string userName = "Alice"; bool isOnline = true; // 常量:声明后不可修改 const string appName = "CSharp Study";

1.4 易错点

  • ​double​ 转 int​ 需要强转,会丢失小数。
  • ​char​ 用单引号,string​ 用双引号。

1.5 类型别名与本质

  • C# 关键字类型大多是 .NET 类型别名:int​ = System.Int32​、long​ = System.Int64​、string​ = System.String​。
  • 代码风格上通常优先写关键字(int​、string​),可读性更统一。
  • ​int​/long​/bool​ 是值类型(struct​),string​ 是引用类型(class​)。
// 别名与完整类型名是等价的 int a = 10; System.Int32 b = 20; string name1 = "Alice"; System.String name2 = "Bob";

2. 常见运算符

2.1 核心概念

  • 算术:+ - * / %​
  • 比较:== != > >= < <=​
  • 逻辑:&& || !​
  • 条件:?:​
  • 空合并:??​
  • 自增自减:++ --​

2.2 关键代码

int a = 20; int b = 6; // 算术运算 int add = a + b; int div = a / b; // 整数除法,结果为 3 int mod = a % b; // 逻辑判断 bool pass = a > 10 && b < 10; // 前置/后置自增 int n = 5; Console.WriteLine(n++); // 先输出 5,再自增 Console.WriteLine(++n); // 先自增,再输出 // 空合并:左边为 null 时使用右边 string? nickName = null; string displayName = nickName ?? "匿名用户";

2.3 易错点

  • ​int / int​ 是整数除法。
  • C# 不支持 20 <= x <= 25​,要写成 x >= 20 && x <= 25​。
  • ​x++​/--x​ 行为与 Java 一样:后置先用后变,前置先变后用。

3. 类型转换、装箱与拆箱

3.1 核心概念

  • 隐式转换:安全范围自动转换(如 int -> long​)。
  • 显式转换:需强转,可能丢失信息。
  • ​Parse​:失败抛异常。
  • ​TryParse​:失败不抛异常,返回 false​。
  • ​Convert.ToInt32(null)​:返回 0​。
  • 装箱:值类型 -> object​;拆箱:object​ -> 值类型。

3.2 关键代码

// 隐式转换 int x = 100; long y = x; // 显式转换 int z = (int)19.99; // 结果 19 // Parse:失败会异常 int p = int.Parse("123"); // TryParse:失败返回 false,不抛异常 bool ok = int.TryParse("10A", out int value); // ok=false, value=0 // Convert:null 转 int 返回 0 string? emptyValue = null; int converted = Convert.ToInt32(emptyValue); // 装箱与拆箱 object boxed = 300; // 装箱 int unboxed = (int)boxed; // 拆箱

3.3 易错点

  • ​int.TryParse​ 不能只写一个参数,必须有 out​。
  • 拆箱类型必须精确匹配,否则 InvalidCastException​。

3.4 补充:什么时候会自动装箱

  • 值类型在“需要按对象使用”时会自动装箱(如赋给 object​ 或接口变量)。
  • 装箱后是新对象;再转回值类型时是拆箱。
int n = 123; object obj = n; // 自动装箱:值类型 -> 引用对象 int n2 = (int)obj; // 拆箱:引用对象 -> 值类型

4. 字符串

4.1 核心概念

  • 字符串是不可变对象。
  • 常用操作:Trim​、Contains​、StartsWith​、EndsWith​、Substring​、Replace​、Split​。
  • 空判断:IsNullOrEmpty​、IsNullOrWhiteSpace​。
  • ​@​ 逐字字符串:反斜杠不转义。

4.2 关键代码

string text = " Hello CSharp "; // 去掉首尾空格 string trimmed = text.Trim(); // 查询 bool hasSharp = text.Contains("Sharp"); bool starts = text.StartsWith(" He"); // 开头匹配包含空格 bool ends = text.EndsWith(" "); // 结尾匹配包含空格 // 截取与替换 string code = "ORD-2026-0001"; string year = code.Substring(4, 4); string replaced = code.Replace("-", "/"); // 分割 string[] tags = "dotnet,csharp,backend".Split(','); // 空值判断 bool e1 = string.IsNullOrEmpty(""); bool e2 = string.IsNullOrWhiteSpace(" "); // 路径字符串两种写法 string path1 = "D:\\zzb_workspace\\project"; string path2 = @"D:\zzb_workspace\project";

4.3 易错点

  • ​StartsWith​ / EndsWith​ 是字面匹配,空格也算字符。
  • 逐字字符串里写 "​ 需要写成 ""​。

5. 条件语句、模式匹配与循环语句

5.1 条件语句

  • ​if / else if / else​:按条件顺序判断。
  • ​switch​ 表达式:输入值映射输出结果。

5.2 模式匹配

  • ​is​:判断类型并声明变量。
  • ​switch + when​:先类型匹配,再附加条件过滤。

5.3 循环语句

  • ​for​:已知次数。
  • ​while​:先判断再执行。
  • ​do-while​:至少执行一次。
  • ​foreach​:遍历集合。
  • ​continue​:跳过本次;break​:结束循环。

5.4 关键代码

// if / else if / else if (score >= 90) { Console.WriteLine("A"); } else if (score >= 80) { Console.WriteLine("B"); } else { Console.WriteLine("C/D"); } // switch 表达式 string season = month switch { 12 or 1 or 2 => "冬", 3 or 4 or 5 => "春", _ => "未知" // 兜底分支 }; // is 模式匹配 object value = "Hello"; if (value is string s && s.Length > 3) { Console.WriteLine(s); } // switch + when 模式匹配 object data = 42; string result = data switch { int n when n > 0 => "正整数", // int 且 > 0 int => "整数(非正)", // 其他 int string text when text.Length == 0 => "空字符串", string => "非空字符串", null => "null", _ => "其他类型" }; // 循环 for (int i = 1; i <= 10; i++) { } while (count > 0) { count--; } do { number++; } while (number < 0); foreach (var name in names) { Console.WriteLine(name); }

5.5 易错点

  • ​switch​ / switch 表达式​ 按顺序匹配,命中第一条就结束。
  • ​do-while​ 会先执行一次再判断条件。
  • ​var​ 需要右侧是可推导类型的完整表达式(如 new[] { ... }​)。

6. 异常

6.1 核心概念

  • ​try​:放可能抛异常的代码。
  • ​catch​:捕获并处理异常。
  • ​finally​:无论是否异常,通常都会执行(常用于资源清理)。
  • ​throw​:主动抛出异常。
  • ​throw;​:在 catch​ 中重新抛出原异常(保留原始堆栈)。
  • ​catch (...) when (...)​:异常过滤,满足条件才进入该 catch​。
  • 自定义异常:用于表达业务错误,通常包含错误码等业务字段。

6.2 关键代码

try { // 可能抛异常的代码 int x = 10; int y = 0; int result = x / y; // DivideByZeroException } catch (DivideByZeroException ex) { // 捕获特定异常 Console.WriteLine(ex.GetType().Name); } catch (Exception ex) { // 兜底异常分支 Console.WriteLine(ex.GetType().Name); } finally { // 无论是否异常都会执行 Console.WriteLine("释放资源"); } // 主动抛异常 if (age < 18) { throw new ArgumentOutOfRangeException(nameof(age), "年龄必须 >= 18"); } // 异常过滤:先匹配异常类型,再判断 when 条件 try { throw new InvalidOperationException("状态非法"); } catch (InvalidOperationException ex) when (ex.Message.Contains("状态")) { Console.WriteLine("命中过滤条件"); } // 重新抛出原异常(保留原始堆栈) try { int.Parse("abc"); } catch { throw; } // 自定义异常:携带业务错误码 internal class BusinessException : Exception { public string ErrorCode { get; } public BusinessException(string errorCode, string message) : base(message) { ErrorCode = errorCode; } } // 使用自定义异常 if (amount <= 0) { throw new BusinessException("ORDER_AMOUNT_INVALID", "订单金额必须大于 0"); }

6.3 易错点

  • ​throw;​ 和 throw ex;​ 不同:前者保留原始堆栈,后者会重置堆栈起点。
  • 不要用异常做普通流程控制(例如把 TryParse​ 场景硬写成 Parse + catch​)。
  • 先写具体异常 catch​,再写 catch (Exception)​ 兜底。
  • 自定义异常建议继承 Exception​,并提供必要构造函数与业务字段(如 ErrorCode​)。

7. 枚举类(Enum)

7.1 核心概念

  • 普通枚举:表示一组固定、互斥的状态(如订单状态)。
  • ​Flags​ 枚举:表示可组合能力/权限(可同时拥有多个值)。
  • 枚举本质有底层整数值,可以与 int​ 转换。

7.2 常用写法

  • 显式赋值:Pending = 1, Paid = 2 ...​,避免隐式值漂移。
  • 字符串转枚举:优先 Enum.TryParse​,避免异常。
  • 枚举遍历:Enum.GetValues<TEnum>()​。
  • ​Flags​ 权限组合:|​(增加)、& ~​(移除)、HasFlag​(判断)。

7.3 关键代码

// 普通枚举:固定状态 internal enum OrderStatus { Pending = 1, Paid = 2, Shipped = 3, Completed = 4, Cancelled = 5 } // Flags 枚举:可组合权限 [Flags] internal enum UserPermission { None = 0, Read = 1, Write = 2, Delete = 4, Admin = 8 } OrderStatus status = OrderStatus.Paid; int numeric = (int)status; // 枚举转数字 // switch 匹配枚举 string desc = status switch { OrderStatus.Pending => "待支付", OrderStatus.Paid => "已支付", _ => "其他状态" }; // 字符串转枚举(推荐 TryParse) bool ok = Enum.TryParse("Shipped", out OrderStatus parsedStatus); // Flags 组合与判断 UserPermission permission = UserPermission.Read | UserPermission.Write; bool canRead = permission.HasFlag(UserPermission.Read); permission |= UserPermission.Delete; // 增加权限 permission &= ~UserPermission.Write; // 移除权限

7.4 易错点

  • ​Flags​ 枚举值建议使用 2 的幂(1、2、4、8...),否则组合会冲突。
  • ​Enum.Parse​ 失败会抛异常,输入不可靠时用 Enum.TryParse​。
  • ​var x = { ... }​ 不是完整表达式;用 new[] { ... }​ 或显式类型。
  • ​Enum.GetValues(Priority)​ 这种写法会报错;用 Enum.GetValues<Priority>()​ 或 Enum.GetValues(typeof(Priority))​。
  • ​~Delete​ 只是“按位取反”的掩码,不等于“全部权限减 Delete”;应与 All​ 组合使用。
[Flags] internal enum UserPermission { None = 0, Read = 1, Write = 2, Delete = 4, Admin = 8, All = Read | Write | Delete | Admin } // 推荐:在已定义权限范围内移除 Delete UserPermission permission = UserPermission.All & ~UserPermission.Delete;

8. 一维数组和二维数组

8.1 核心概念

  • 一维数组:同类型元素的线性集合,索引从 0​ 开始。
  • 二维数组:表格结构,索引写法是 [行, 列]​。
  • 数组长度固定:创建后长度不可变。

8.2 常用写法

  • 一维数组声明:int[] nums = new int[3];​
  • 一维数组初始化:int[] nums = { 10, 20, 30 };​
  • 二维数组声明:int[,] matrix = new int[2,3];​
  • 行列获取:GetLength(0)​ 取行数,GetLength(1)​ 取列数。

8.3 关键代码

// 一维数组 int[] scores = { 85, 92, 78 }; Console.WriteLine(scores[0]); // 访问第 1 个元素(索引 0) scores[2] = 88; // 修改元素 for (int i = 0; i < scores.Length; i++) { Console.WriteLine($"scores[{i}] = {scores[i]}"); } foreach (int score in scores) { Console.WriteLine(score); // 直接遍历元素 } Array.Sort(scores); Console.WriteLine(string.Join(", ", scores)); // 二维数组(2 行 3 列) int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } }; Console.WriteLine(matrix[1, 2]); // 第 2 行第 3 列 => 6 Console.WriteLine(matrix.GetLength(0)); // 行数 = 2 Console.WriteLine(matrix.GetLength(1)); // 列数 = 3 for (int row = 0; row < matrix.GetLength(0); row++) { for (int col = 0; col < matrix.GetLength(1); col++) { Console.Write($"{matrix[row, col]} "); } Console.WriteLine(); }

8.4 易错点

  • 数组下标从 0​ 开始,arr[arr.Length]​ 会越界。
  • ​for​ 循环边界要写 < Length​,不要写 <= Length​。
  • 二维数组用 [row, col]​,不是 [row][col]​。
  • ​int[,]​(矩形二维数组)和 int[][]​(交错数组)不是同一种类型。

8.5 int[,]​ 与 int[][]​ 对比

  • ​int[,]​:矩形二维数组,行列规则,所有行列长度固定。

  • ​int[][]​:交错数组(数组的数组),每一行是独立的一维数组,长度可不同。

  • 选择建议:

    • 数据天然是规则表格(如 3x4 成绩表)用 int[,]​。
    • 每行长度不一致(如每个班人数不同)用 int[][]​。
// 矩形二维数组:2 行 3 列(固定) int[,] table = { { 1, 2, 3 }, { 4, 5, 6 } }; Console.WriteLine(table[1, 2]); // 6 // 交错数组:每行长度可不同 int[][] jagged = { new[] { 10, 20 }, new[] { 30, 40, 50 }, new[] { 60 } }; Console.WriteLine(jagged[1][2]); // 50

8.6 补充:数组初始化新语法(C# 12)

  • ​int[] ages = [35, 20, 22, 18];​ 是 C# 12 的集合表达式写法。
  • 对数组来说,它等价于 int[] ages = new[] { 35, 20, 22, 18 };​。
// C# 12 集合表达式 int[] ages1 = [35, 20, 22, 18]; // 传统等价写法 int[] ages2 = new[] { 35, 20, 22, 18 };

9. 交错数组

9.1 核心概念

  • 交错数组写法是 T[][]​,本质是“数组里的每个元素仍是一个一维数组”。
  • 每一行长度可以不同,适合不规则数据。
  • 访问语法是 arr[row][col]​。

9.2 常用写法

  • 声明并初始化:int[][] data = { new[] {1,2}, new[] {3,4,5} };​
  • 先声明行数再逐行赋值:int[][] data = new int[3][];​
  • 行长度获取:data[row].Length​

9.3 关键代码

// 交错数组:每行长度可不同 int[][] scoresByClass = { new[] { 90, 85, 88 }, new[] { 76, 92 }, new[] { 100, 98, 95, 93 } }; Console.WriteLine(scoresByClass[2][1]); // 第3行第2列 => 98 // 双层循环遍历 for (int row = 0; row < scoresByClass.Length; row++) { for (int col = 0; col < scoresByClass[row].Length; col++) { Console.Write($"{scoresByClass[row][col]} "); } Console.WriteLine(); } // 先建行,再给每行分配不同长度 int[][] data = new int[3][]; data[0] = new[] { 1, 2 }; data[1] = new[] { 3, 4, 5 }; data[2] = new[] { 6 }; // 与二维数组对比 int[,] rectangle = { { 1, 2, 3 }, { 4, 5, 6 } };

9.4 易错点

  • 交错数组访问写法是 arr[i][j]​,不是 arr[i, j]​。
  • ​new int[3][]​ 只创建“行容器”,每一行默认是 null​,使用前必须初始化。
  • 行长度不一致时,内层循环边界必须用 arr[row].Length​。
  • ​int[][]​(交错数组)和 int[,]​(矩形二维数组)是不同类型,不能直接互换。

9.5 练习补充与写法优化

  • 外层长度 teams.Length​ 表示“组数”,内层长度 teams[i].Length​ 表示“该组人数”,两者不要混用。
  • 交错数组遍历时,推荐把外层下标打印出来,调试更直观。
  • 访问元素前可做空值判断,避免某一行未初始化导致 NullReferenceException​。
for (int groupIndex = 0; groupIndex < teams.Length; groupIndex++) { // 某一行可能还没初始化,先判空更安全 if (teams[groupIndex] is null) { Console.WriteLine($"第 {groupIndex} 组未初始化"); continue; } Console.WriteLine($"第 {groupIndex} 组人数 = {teams[groupIndex].Length}"); for (int memberIndex = 0; memberIndex < teams[groupIndex].Length; memberIndex++) { Console.WriteLine($"teams[{groupIndex}][{memberIndex}] = {teams[groupIndex][memberIndex]}"); } }

9.6 交错数组与 C# 12 集合表达式

  • ​[]​ 写法是 C# 12 集合表达式,可用于初始化交错数组。
  • 与传统 new[] { ... }​ 写法等价,选团队统一风格即可。
// C# 12 写法 int[][] a = [ [1, 2], [3, 4, 5], [6] ]; // 传统写法 int[][] b = { new[] { 1, 2 }, new[] { 3, 4, 5 }, new[] { 6 } };

10. 顶级语句和函数

10.1 核心概念

  • 顶级语句(Top-level statements)是省略显式 Program.Main​ 的入口写法。
  • 编译器会自动生成入口方法并执行文件中的顶级代码。
  • ​args​ 在顶级语句中可直接使用,本质仍对应入口参数。

10.2 与非顶层写法对应关系

  • 顶级语句:省略 class Program​ 与 static void Main(string[] args)​ 样板。
  • 非顶层写法:显式声明 Program.Main​,结构更清晰,适合教学和较大项目。
  • 两者本质都是 C# 程序入口,能力等价。

10.3 函数组织方式

  • 局部函数:定义在当前顶级流程里,作用域局限在当前流程。
  • ​static​ 局部函数:不能捕获外层变量,只依赖参数和静态成员。
  • 类型静态方法:定义在 class​ 中,通过 类名.方法名​ 调用。

10.4 委托、Func、Action、lambda

  • 委托可理解为“函数类型”,可把函数赋值给变量再调用。
  • 普通函数本身不能像普通值那样直接传递,通常需要先绑定到委托变量(方法组或 lambda)后再传递。
  • ​Func<T1, T2, TResult>​:前面是参数类型,最后是返回值类型。
  • ​Action<T1, T2, ...>​:只有参数类型,无返回值(void​)。
  • lambda(=>​)是创建匿名函数的简写形式,常用于给委托赋行为。

10.5 函数可配置的含义

  • 同一段流程代码不变,通过替换委托变量中的函数行为,实现不同结果。
static int Calc(int a, int b, Func<int, int, int> op) { return op(a, b); } int r1 = Calc(10, 3, (x, y) => x + y); // 加法 int r2 = Calc(10, 3, (x, y) => x - y); // 减法 int r3 = Calc(10, 3, (x, y) => x * y); // 乘法

10.6 委托的典型应用场景

  • 回调通知:将“处理完成后要做什么”作为参数传入。
  • 策略切换:同一流程中按需切换算法(加减乘除、不同计费规则)。
  • 事件处理:按钮点击、消息到达等场景本质是委托回调。
  • 集合处理:Where​、Select​、OrderBy​ 等 LINQ API 通过委托接收筛选/映射规则。
// 回调:下载完成后执行回调逻辑 static void Download(string url, Action<string> onCompleted) { // ... 省略下载流程 onCompleted($"下载完成: {url}"); } // 策略:把算法作为参数传入 static int Calc(int a, int b, Func<int, int, int> op) { return op(a, b); } Download("https://example.com/a.zip", msg => Console.WriteLine(msg)); int total = Calc(10, 3, (x, y) => x * y);

10.7 关键代码

// 顶级语句入口(省略 Program/Main) Console.WriteLine($"args.Length = {args.Length}"); int a = 12; int b = 5; Console.WriteLine(Add(a, b)); Console.WriteLine(Square(a)); // 委托 + lambda Func<int, int, int> max = (x, y) => x > y ? x : y; Action<string> log = msg => Console.WriteLine($"[LOG] {msg}"); Console.WriteLine(max(a, b)); log("lambda 调用完成"); // 局部函数(可访问当前流程变量) int Square(int value) { return value * value; } // static 局部函数(不能捕获外层变量) static int Add(int left, int right) { return left + right; } // 类型静态方法 internal static class MathHelper { public static int Multiply(int left, int right) => left * right; }

10.8 易错点

  • 顶级语句项目中通常只保留一个入口文件,避免入口冲突。
  • 局部函数与类型方法概念不同:局部函数无 public/private/internal​ 修饰。
  • ​static​ 局部函数不能访问外层变量,误访问会编译报错。
  • 项目中已有非顶层 Main​ 时,再加入顶级语句可能产生“多个入口点”错误。

11. 常见参数传递、ref 和 out

11.1 核心概念

  • C# 默认按值传递参数。
  • 值类型按值传递:方法内改参数副本,不影响调用方变量。
  • 引用类型按值传递:可改对象内容,但重新 new​ 只改到局部副本引用。
  • ​ref​:按引用传递,调用方变量必须先初始化,方法内可读可写。
  • ​out​:按引用传递,调用方可不初始化,方法内必须赋值后返回。

11.2 常用写法

  • ​void Increase(ref int x)​
  • ​bool TryParseAge(string input, out int age)​
  • ​int Sum(params int[] nums)​(可变参数)

11.3 关键代码

int n = 10; ChangeValue(n); Console.WriteLine(n); // 10,值类型按值传递不变 Student stu = new Student { Name = "Alice" }; ChangeStudentName(stu); Console.WriteLine(stu.Name); // Bob,对象内容被修改 ReassignStudent(stu); Console.WriteLine(stu.Name); // 仍是 Bob,引用副本被替换不影响外部 int score = 60; AddBonus(ref score, 20); Console.WriteLine(score); // 80,ref 修改了调用方变量 bool ok = TryParseAge("18", out int age); Console.WriteLine($"ok={ok}, age={age}"); // 值类型按值传递 static void ChangeValue(int x) => x = 999; // 引用类型按值传递:改对象内容会生效 static void ChangeStudentName(Student s) => s.Name = "Bob"; // 引用类型按值传递:替换引用仅影响局部副本 static void ReassignStudent(Student s) => s = new Student { Name = "NewGuy" }; // ref:可读可写调用方变量 static void AddBonus(ref int value, int bonus) => value += bonus; // out:方法内必须赋值 static bool TryParseAge(string input, out int age) => int.TryParse(input, out age);

11.4 ref 与 out 对比

  • 调用前:ref​ 变量必须已赋值;out​ 可以不赋值。
  • 方法内:ref​ 可先读后写;out​ 在读取前必须先赋值。
  • 场景:ref​ 常用于“修改原变量”;out​ 常用于“返回多个结果/Try 模式”。

11.5 易错点

  • ​ref​/out​ 必须在“方法声明”和“调用处”同时写,缺一不可。
  • 不要把“引用类型按值传递”误解为“对象引用本身可自动被替换”。
  • ​out​ 参数若存在未赋值返回路径,会编译报错。
  • ​ref​ 和 out​ 不是重载区分依据(签名冲突风险需要注意)。

11.6 易混点澄清

  • 可用“值/地址”做直觉类比,但 C# 的 ref/out​ 是语言级安全语义,不是直接操作裸指针。

  • 引用类型默认传参是“引用的副本”:

    • 改对象内容:会影响外部(同一对象)。
    • 重新 new​:只改变方法内副本引用的指向,不影响外部变量指向。
  • ​ref​ 可理解为“把调用方变量本体交给方法”,因此可替换外部变量指向。

Person p = new Person { Name = "A" }; ChangeName(p); // 改内容:外部可见 Reassign(p); // 改副本引用:外部不可见 Replace(ref p); // 改变量本体:外部可见 static void ChangeName(Person x) => x.Name = "B"; static void Reassign(Person x) => x = new Person { Name = "C" }; static void Replace(ref Person x) => x = new Person { Name = "D" };

11.7 params 补充

  • ​params​ 用于“不确定数量、同类型参数”。
  • 调用方式支持“逗号展开”或“直接传数组”。
  • ​params​ 参数必须放在参数列表最后,且一个方法只能有一个 params​ 参数。
static int Sum(params int[] nums) { int s = 0; foreach (int n in nums) s += n; return s; } int a = Sum(1, 2, 3); // 逗号展开 int b = Sum(new[] { 4, 5, 6 }); // 直接传数组 int c = Sum(); // 允许 0 个参数,结果 0

12. 结构体

12.1 核心概念

  • ​struct​ 是值类型,赋值和传参默认走值语义(复制)。
  • 结构体适合小而简单的数据对象(坐标、尺寸、颜色、日期片段)。
  • 与 class​ 不同:class​ 是引用类型,变量保存对象引用。

12.2 常用写法

  • 定义结构体:struct Point { ... }​
  • 带参构造:public Point(int x, int y) { ... }​
  • 不可变结构体:readonly struct Size { ... }​
  • 需要修改调用方结构体时使用 ref​。

12.3 关键代码

Point p1 = new Point(3, 5); Point p2 = p1; // 值拷贝 p2.X = 100; Console.WriteLine(p1.X); // 3,原对象不受影响 Console.WriteLine(p2.X); // 100 MoveByValue(p1, 1, 1); Console.WriteLine(p1.X); // 3,按值传参未改外部 MoveByRef(ref p1, 1, 1); Console.WriteLine(p1.X); // 4,ref 改到了外部变量 readonly struct Size { public int Width { get; } public int Height { get; } public Size(int width, int height) { Width = width; Height = height; } } static void MoveByValue(Point p, int dx, int dy) { p.X += dx; p.Y += dy; } static void MoveByRef(ref Point p, int dx, int dy) { p.X += dx; p.Y += dy; }

12.4 struct 与 class 快速对比

  • 存储语义:struct​ 值语义;class​ 引用语义。

  • 赋值行为:struct​ 复制数据;class​ 复制引用。

  • 适用场景:

    • ​struct​:轻量、短生命周期、不可变小对象。
    • ​class​:较大对象、共享状态、继承多态场景。

12.5 易错点

  • 结构体赋值是复制,不是共享同一实例。
  • 结构体较大时频繁复制可能有性能开销。
  • ​readonly struct​ 内不应设计可变状态。
  • 结构体装箱到 object​ 时会产生额外开销。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 2:16:30

VCSA 8.0部署卡在初始化VCS服务、认证失败?NTP+DNS一招解决

部署 VCSA 8.0 时&#xff0c;很多运维人员会遇到安装向导长时间卡在初始化 VCS 服务阶段&#xff0c;查看部署日志持续报 authentication&#xff08;认证&#xff09;失败&#xff0c;无法完成部署。多数人会误以为是账号密码或权限问题&#xff0c;实际核心诱因是NTP 时间不…

作者头像 李华
网站建设 2026/5/21 2:16:08

OpenAI联合创始人加入Anthropic,新一轮AI人才争夺战打响?

Andrej Karpathy权力交接&#xff1a;职级降低背后的新使命周二晚间&#xff0c;Andrej Karpathy突然在X上自宣加入Anthropic。加入后&#xff0c;他将在该公司最核心的预训练团队&#xff0c;负责人Nicholas Joseph的指导下工作&#xff0c;工作于本周开始。令人惊讶的是&…

作者头像 李华
网站建设 2026/5/21 2:15:47

WGCLOUD支持自动发现设备吗

支持的 WGCLOUD监控系统的自动发现功能&#xff0c;可以自动扫描网络内的存活IP设备&#xff0c;如下图 此功能非常实用&#xff0c;可以扫描指定网络内的所有设备IP&#xff0c;包括打印机、交换机、防火墙、服务器、主机等等

作者头像 李华
网站建设 2026/5/21 2:14:50

半波整流电路:从原理到实践,掌握AC-DC转换基础

1. 项目概述&#xff1a;从交流到直流的第一步在电子电路的世界里&#xff0c;我们常常需要将交流电&#xff08;AC&#xff09;转换为直流电&#xff08;DC&#xff09;&#xff0c;这个过程我们称之为“整流”。而半波整流电路&#xff0c;可以说是所有整流电路中最基础、最经…

作者头像 李华
网站建设 2026/5/21 2:13:58

Java Snowy框架CI/CD云效自动化部署流程

文章目录从手动部署到自动化部署1. 为什么要引入 CI/CD2. 常见的 CI/CD 工具3. 不同工具的差异大吗&#xff1f;4. 阿里云云效流水线实践5. 如果要迁移到其他工具前端部署流水线1. 创建空白流水线2. 配置流水线源3. 配置构建阶段4. 配置主机部署5. 保存并运行后端部署流水线1. …

作者头像 李华
网站建设 2026/5/21 2:13:55

Linux符号链接原理与实战:从快捷方式到系统管理核心技能

1. 项目概述&#xff1a;符号链接&#xff0c;Linux文件系统的“快捷方式”在Linux世界里&#xff0c;文件系统就像一座巨大的图书馆&#xff0c;而符号链接&#xff08;Symbolic Link&#xff09;就是图书馆里那些指向其他书籍位置的“索引卡片”。你手里拿着一张卡片&#xf…

作者头像 李华