C#初学者必看:从类与对象到封装属性的完整实战指南
今天这篇是 C# 初学者每日分享系列的第 13 篇。如果你是从前面几篇一路跟过来的应该已经见过变量、判断、循环、方法、数组这些基础语法了如果今天才点开也没关系这一篇我会从类与对象最基础的概念讲起一步一步带你把代码写出来。我见过很多初学者学到这一关就卡住不是因为语法记不住而是不太理解为什么要搞出“类”这么个东西。这一期就用一个特别常见的场景——管理一批学生的信息——把类、属性、构造方法、封装、对象集合这些知识点串起来。适合刚学完基础语法、想彻底搞清面向对象的朋友看完之后至少能自己写出一个带校验、能统计、可以复用的控制台小程序。1. 这一期要解决的其实是“怎么描述现实世界”1.1 变量和数组能存数据但存不住“规则”刚开始学 C# 的时候我们习惯用变量和数组存数据。比如要描述一个学生你会写string name 小明; int age 18; double score 90;这没问题。但如果你要处理三个、五个、甚至几十个学生代码很快就变成一堆平行数组一个数组存姓名一个数组存年龄一个数组存成绩。数据少的时候还能硬写数据一多数组下标一旦对不上程序就乱套了。更要命的是数组没有“规则”。你可以在成绩数组里塞一个负数可以在年龄数组里写一个 200 岁编译器不会拦你程序也不会立刻报错。等到你统计平均值、找最高分的时候这些脏数据就会把结果带偏。类要解决的核心问题就是让“数据”和“操作规则”长在一起。1.2 类和对象的关系就像菜谱和菜类是一个模板对象是根据模板造出来的具体东西。拿做菜打比方类就是菜谱上面写好了需要哪些原料、先放什么后放什么对象就是你照着菜谱真正做出来的那道菜。同一个菜谱你可以做十次得到十盘菜它们有相同的结构但具体状态可能不同。写成 C# 代码就是这样public class Student { public string Name; public int Age; public double Score; }当你执行Student stu new Student();时stu就是一个具体的对象。这个概念初学者一定要想明白Student本身不占“人”的名额new Student()之后才真的有一个对象可以存放数据。2. 搭建类的三大块基础结构字段、属性、构造方法2.1 字段和属性怎么选别一上来就 public很多初学面向对象的人看完上面的代码会问public string Name;不就能用了吗为什么还要写属性搞出一堆get和set字段就是你声明在类里的变量它可以直接被外部读写。属性看起来像字段但底层其实是两个方法get负责读取set负责写入。你在外面写stu.Name 小红;实际上调用的是set方法你写Console.WriteLine(stu.Name);调用的是get方法。区别在于属性可以在set里加入校验逻辑。如果你直接暴露 public 字段等于把家门钥匙直接递给所有人属性相当于在门口装了一个保安进来之前先检查一下。真正写项目的时候公共字段是一个很容易制造隐患的设计我建议你从第一天起就养成用属性的习惯。2.2 构造方法到底在什么时候执行构造方法是对象创建时自动执行的方法名字和类名相同没有返回值。你写new Student(小红, 18, 92)的时候new后面的括号就是在调用构造方法。public class Student { public string Name; public int Age; public double Score; public Student(string name, int age, double score) { Name name; Age age; Score score; } }构造方法最大的价值是让对象一出生就处于“合法状态”。如果你不允许一个学生没有姓名那就不要在对象创建后再让外部慢慢赋值而是强制要求创建的时候必须传姓名。这就是为什么建议新手尽量显式写构造方法而不是默默依赖系统给的默认构造方法。2.3 自动属性、只读属性和计算属性如果一个属性不需要额外校验C# 提供“自动属性”简化代码public string Name { get; set; }它看似没有字段实际上编译器在后台帮你生成了字段。还有一个常见技巧是只读属性比如学号一旦确定就不该改public string StudentNo { get; } public Student(string studentNo, string name) { StudentNo studentNo; Name name; }只读属性只能在构造方法中赋值之后外部无法修改。另外你还可以写“计算属性”它没有set只有get返回值是计算出来的比如“是否及格”public bool IsPass Score 60;这类属性让类的表达能力更强调用方只需要关心业务含义不需要自己写判断逻辑。3. 封装不是让所有东西 private而是要留好入口3.1 public、private、protected、internal 到底怎么选封装的字面意思是“隐藏内部细节”但新手很容易走向两个极端要么所有字段全public要么所有成员全private让外部完全无法使用。正确的做法是数据尽量不公开行为按需公开。访问修饰符谁能访问初级阶段的定位public所有地方对外提供服务的方法private仅同一个类内部内部实现细节、私有字段protected本类和子类设计继承体系时使用internal同一个程序集内程序集内部通用能力你只需要记住一条主线默认先private确有必要对外开放的方法再用public。protected和internal不是初学者的重点但看到时要认识它们别在代码里一出现就懵。3.2 什么时候才需要用到 protected 和 internalprotected是给“继承”准备的关键词。如果父类某个方法只想让子类用不向外部开放就标记为protected。比如动物类里有一个Eat方法子类Dog需要调用但外部不需要直接让动物吃东西这时候protected就很合适。internal表示同一个程序集内部可以访问。初学阶段你写的代码基本都在同一个项目里public和internal差别不大但以后做项目拆多个项目时你会慢慢体会它控制“内部共享”的作用。我的建议是现在不要在protected和internal上花太多时间先能用好public、private就够了。等学到继承的时候再返回来看protected会顺畅很多。3.3 属性校验让非法数据进不了对象封装一个很典型的价值就是属性校验。直接给定成绩赋值可能会写入 -5 分通过属性设置就能在入口处拦住private double _score; public double Score { get { return _score; } set { if (value 0 || value 100) throw new ArgumentOutOfRangeException(成绩必须在0到100之间); _score value; } }这段代码的效果是任何地方写stu.Score 120程序会直接抛异常而不是默默存下一个错误数据。很多人觉得抛异常太粗暴但比起“存了坏数据直到最后才发现”程序早一点停住反而更容易排查。记得一个原则发现问题的时间越早修复成本越低。4. 完整实操写一个学生信息管理器理论说得再多不如亲手跑一遍。下面我带你写一个很小的控制台程序不需要数据库不需要界面只把类和对象用起来。4.1 先定清楚需求再决定类的边界动手写代码前先列需求能添加学生包含姓名、年龄、成绩能显示所有学生信息能计算全班平均分能找出成绩最高的学生非法数据不能进入系统我决定拆成两个类Student负责单条学生数据本身的存储和计算StudentService负责管理多个学生的集合。这样职责分开Student不需要知道“全班平均分”怎么算StudentService也不需要关心某个学生成绩是否合法因为创建学生时已经校验过了。这种拆分方式在以后写更大项目时特别重要。4.2 Student 类实现属性校验和计算逻辑using System; public class Student { private string _name; private int _age; private double _score; public string Name { get { return _name; } set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(姓名不能为空); _name value; } } public int Age { get { return _age; } set { if (value 0 || value 120) throw new ArgumentOutOfRangeException(年龄必须在0到120之间); _age value; } } public double Score { get { return _score; } set { if (value 0 || value 100) throw new ArgumentOutOfRangeException(成绩必须在0到100之间); _score value; } } public Student(string name, int age, double score) { Name name; Age age; Score score; } public string GetLevel() { if (Score 85) return 优秀; if (Score 60) return 及格; return 待努力; } public override string ToString() { return ${Name}{Age}岁成绩{Score}分{GetLevel()}; } }这里有个很关键的细节构造方法里给Name、Age、Score赋值表面上是在直接赋值实际上调用的还是属性的set方法。也就是说就算有人在创建对象时传一个负年龄也会被拦下来。这就是把校验写在属性里的好处无论从哪个路径赋值都要过同一道安检。4.3 StudentService 类实现用 List 管理对象using System; using System.Collections.Generic; using System.Linq; public class StudentService { private ListStudent _students new ListStudent(); public void AddStudent(Student student) { if (student null) throw new ArgumentNullException(nameof(student)); _students.Add(student); Console.WriteLine($添加成功{student.Name}); } public void ShowAll() { if (_students.Count 0) { Console.WriteLine(当前还没有学生数据。); return; } Console.WriteLine(学生列表); foreach (Student student in _students) { Console.WriteLine(student); } } public void ShowAverageAndTop() { if (_students.Count 0) { Console.WriteLine(没有数据无法统计。); return; } double average _students.Average(s s.Score); Student top _students.OrderByDescending(s s.Score).First(); Console.WriteLine($平均分{average:F1}); Console.WriteLine($最高分{top.Name}{top.Score}分); } }这里Average、OrderByDescending是 LINQ 的写法。如果你还没系统学过不用怕先把它当成两个现成工具一个求平均一个按成绩从高到低排序。s s.Score读作“取一个学生 s 的 Score 属性”。另外注意_students使用了private。外部不能直接拿到这个列表乱改只能通过服务类提供的方法操作。很多新手会忍不住把列表写成 public然后在外面对列表进行各种操作结果数据校验、统计逻辑全部被绕开。这是封装的大忌。4.4 Main 方法串联跑通最小的完整程序using System; class Program { static void Main() { StudentService service new StudentService(); service.AddStudent(new Student(小张, 18, 92.5)); service.AddStudent(new Student(小李, 17, 58)); service.AddStudent(new Student(小周, 18, 76)); service.ShowAll(); service.ShowAverageAndTop(); try { service.AddStudent(new Student(小赵, 18, -5)); } catch (ArgumentException ex) { Console.WriteLine($校验生效{ex.Message}); } } }运行结果大致是添加成功小张 添加成功小李 添加成功小周 学生列表 小张18岁成绩92.5分优秀 小李17岁成绩58分待努力 小周18岁成绩76分及格 平均分75.5 最高分小张92.5分 校验生效成绩必须在0到100之间注意最后一行虽然创建Student(小赵, 18, -5)时抛了异常但前面的数据没有任何影响。程序没有崩溃因为try-catch把异常接住了。这种“非法数据被拦截正常流程继续走”的感觉就是封装和异常处理合作的典型效果。5. 新手最容易踩的四个坑我一个个说5.1 引用类型复制后两个变量指向同一个对象C# 里类属于引用类型。用Student a new Student(小张, 18, 90); Student b a;之后a和b指向的是同一个对象。接着执行b.Name 小李;你再看a.Name会发现它也变成了“小李”。很多新手把对象赋值当成了复制等数据莫名其妙变了排查半天才发现是两个变量指向同一个内存地址。要避免这个问题就要记住引用类型变量存的是“地址”而不是“内容”。真需要独立副本时通常要自己写一个复制类对象的方法或者用记录类型提供的复制功能而不是靠等号。5.2 foreach 里不能 Add 和 Remove只能改元素内部foreach循环看着很方便但它有一个硬限制遍历期间不能修改集合的结构也就是说不能往集合里加元素也不能从集合里删除元素。一旦你这么做程序会抛InvalidOperationException。注意修改集合元素对象的属性比如把每个学生的成绩加 5 分这是允许的但_students.Add(...)或_students.Remove(...)是不允许的。如果你需要在遍历中删除符合条件的元素推荐用for倒序遍历或者先筛选出要删除的项循环结束后再删。// 错误示例 foreach (Student student in _students) { if (student.Score 60) _students.Remove(student); // 会抛异常 } // 正确思路 ListStudent toRemove _students.Where(s s.Score 60).ToList(); foreach (Student student in toRemove) { _students.Remove(student); }5.3 null 进入方法之前就要拦住null是 C# 里非常常见的坑。比如你调用service.AddStudent(null)如果方法内部没有检查后面一旦执行_students.Add(student)或者访问student.Name就会直接抛出NullReferenceException。我见过很多初学者一看到空引用异常就慌其实解决方法很简单在所有接收对象参数的方法入口处先判断是否为 null。if (student null) throw new ArgumentNullException(nameof(student));这一行代码帮你在源头拦截问题。虽然现在 C# 有可空引用类型等特性能够在编译期给提示但“参数先判空”依然是很多项目和团队约定俗成的习惯值得养成。5.4 不会用调试器就像蒙着眼睛写代码写类和对象之后代码里的“套路”越来越多你更需要学会用调试器。在 IDE 里把自己怀疑有问题的那一行代码前面点一个断点程序运行到这里会暂停。此时你可以把鼠标悬停在变量上查看对象的属性值也可以打开“局部变量”窗口一层一层展开对象内部结构看_students集合里到底装了几个对象。很多人遇到 bug 第一反应是到处加Console.WriteLine这没有错但调试器更直接。它能让你看到代码执行到某一刻时所有变量的真实状态。学会打断点、单步执行、查看调用堆栈是学 C# 过程中性价比最高的一笔投资。6. 今天的练习和我攒下的几点心得6.1 今天的三道练习题从易到难第一题把本文的Student类加一个只读属性StudentNo在构造方法里初始化。这题主要是训练只读属性的用法巩固构造方法的知识。第二题给StudentService增加一个RemoveStudentByName(string name)方法删除指定姓名的学生。删除前要判断学生是否存在删除后把结果打印出来。这题需要你处理集合删除和条件判断正好练到上一节提到的坑。第三题自己写一个Book类和一个BookService类模拟图书馆藏书管理。Book包含书名、作者、价格价格必须大于 0BookService能添加、显示、按价格排序。这题要求你完全脱离示例独立从需求出发设计类是真正检验你是否理解“类与对象”的一关。6.2 我给初学者的三点实操建议第一不要背概念而是背最小例子。很多初学者喜欢背“类是对象的模板对象是类的实例”背得很顺写代码却不知道怎么下手。我建议你把本文最精简的那个Student类抄下来然后反复改属性、加方法跑通一次比看十遍理论都有用。第二先写能运行的程序再谈设计。新手很容易陷入“设计癖”一个类还没写几行就开始纠结是不是不够解耦、不够优雅。我的经验是第一版允许很粗糙能跑通就是胜利然后再去想如何校验、如何封装、如何拆分职责。第三异常信息要认真读。你写的代码一旦爆出红字不要急着问别人先把异常类型、异常信息、出错行号这三样东西看清楚。很多时候问题已经写得很直白比如“成绩必须在0到100之间”你需要的是顺着线索查下去而不是直接把整段代码删掉重写。最后再说一个我自己常用的笨办法每次学一个新的 C# 概念先别急着背定义打开编译器敲一个十几行的最小例子跑通之后再往上加需求。类与对象也不例外。你亲手写过、改过、报过错之后再回头看这些概念会觉得它们本来就是那么回事。每一步都算数但前提是每一步都要真的动手去写。

相关新闻

mergerfs link-cow 选项深度解析:硬链接文件的写时复制(CoW)语义

mergerfs link-cow 选项深度解析:硬链接文件的写时复制(CoW)语义

存储 【免费下载链接】mergerfs a featureful union filesystem 项目地址: https://gitcode.com/gh_mirrors/me/mergerfs 点击查看 免费下载 本篇聚焦 mergerfs 的 link-cow 配置项:它让"对硬链接文件打开写"这一操作自动、原子地断裂链接&am…

2026/10/10 2:03:50 阅读更多 →
Midway Hooks 一体化开发指南:用 React Hooks 语法编写全栈应用

Midway Hooks 一体化开发指南:用 React Hooks 语法编写全栈应用

后端微服务云原生 【免费下载链接】midway 🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate w…

2026/10/10 2:03:50 阅读更多 →
6 天 1000 星就是风口?警惕技能包赛道的刷星与注水

6 天 1000 星就是风口?警惕技能包赛道的刷星与注水

6 天 1000 星就是风口?警惕技能包赛道的刷星与注水 【免费下载链接】replica-skill Eleven free Claude skills that clone any app: reverse-engineer it, rebuild it, test it for bugs, then fix what its users hate. Free, MIT. 项目地址: https://gitcode.c…

2026/10/10 2:02:49 阅读更多 →

最新新闻

同城跑腿系统:骑手端同步和下单收款怎么拆

同城跑腿系统:骑手端同步和下单收款怎么拆

同城跑腿系统联调时,常见做法是支付一通就对外宣称上线。更稳的做法是把「下单与订单状态」和「收款回调」拆阶段验收:前者不依赖真实通道,后者用沙箱 profile,避免支付未过却改订单写入口。结论 订单状态推进应由领域事件驱动&am…

2026/10/10 2:56:07 阅读更多 →
TwinCAT3.1.4026 C++使用入门

TwinCAT3.1.4026 C++使用入门

TwinCAT C使用入门1、准备工作1.1 软件环境1.2 硬件环境2、安装流程2.1 Visual Studio安装2.2 TwinCAT 安装3、配置签名3.1 TwinCAT证书制作3.2 Windows开启测试模式4、C项目设置4.1 项目编译4.2 模块加载执行1、准备工作 1.1 软件环境 TwinCAT Package Manager; …

2026/10/10 2:56:07 阅读更多 →
LikeShop 购物车模块深度优化:登录态合并、商品失效与批量结算二开

LikeShop 购物车模块深度优化:登录态合并、商品失效与批量结算二开

一、原生购物车体系数据表拆解1.1 购物车表(ls_cart)LikeShop 的购物车数据存储在 ls_cart 表中,表结构简洁,核心字段如下:字段名数据类型注释idint主键user_idint用户idgoods_idint商品idgoods_numsmallint购买数量it…

2026/10/10 2:56:07 阅读更多 →
Flink 2.3.0 从理论到实践 —— 第 4 章 状态管理(State)

Flink 2.3.0 从理论到实践 —— 第 4 章 状态管理(State)

Flink 2.3.0 从理论到实践 —— 第 4 章 状态管理(State) 课程定位:状态是 Flink 区别于普通流处理引擎的核心能力。本章深入 Keyed State 与 Operator State 的区别、三种状态后端(HashMap / ForSt / RocksDB)、状态 …

2026/10/10 2:56:07 阅读更多 →
1.两数之和 - 力扣(Leetcode)

1.两数之和 - 力扣(Leetcode)

题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 示例…

2026/10/10 2:56:07 阅读更多 →
基于PCA9422 PMIC与STM32F401RB的锂电池供电与低功耗电源管理实战

基于PCA9422 PMIC与STM32F401RB的锂电池供电与低功耗电源管理实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 2:55:07 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →