新闻详情

新闻详情

首页 / 资讯中心 / 详情

C#面向对象入门:类与对象、封装、继承和多态全解析

发布时间:2026/10/1 18:27:58来源:尧图网络
C#面向对象入门:类与对象、封装、继承和多态全解析
不绕弯子直接开门见山说一件事C#这门语言不管你是打算做上位机、写Web后端、搞Unity游戏脚本还是用WinForm/MAUI做桌面工具面向对象都是你跨不过去的门槛。很多人刚学C#时跟着教程敲了不少代码class、new、public、private这些关键字也见过但真到自己写一个小项目时脑子里还是一团浆糊——对象到底是啥为什么要这么写为什么那个方法前面要加一个static加了之后又为什么不能直接调实例变量这篇文章就用最直白的方式把这些东西彻底捋清楚。不堆术语不放长篇大论的理论尽量用场景、类比和身边的例子来讲配图的部分我直接用文字描述出画面感你照着在纸上画一画理解会快很多。适合刚学完C#基本语法但还没真正“开窍”的读者也适合那些想回头巩固基础、应付面试基础题的朋友。1. 为什么C#的第一课必须学面向对象1.1 C#这个语言“天生”就是面向对象的很多编程语言是“兼收并蓄”的比如Python既可以写面向过程的脚本也可以用class组织代码C语言则基本是面向过程的思路数据放在结构体里操作数据的函数单独写在外面。但C#不一样你在C#里写的任何一行有意义的代码几乎都在类里面。这句话听起来有点绝对但事实就是C#的入口点Main方法都必须在某个类里面。也就是说你连一个最简单的“Hello World”都绕不开类的概念。这就逼着所有学C#的人必须从一开始就理解“类”这个东西。我记得自己刚入行时带过一个新人他之前是写单片机的习惯把功能拆成一堆函数然后从上往下调用。到了C#项目里他还是这么干写了个Utility.cs文件里面放了七八十个静态方法所有业务逻辑全靠在各个窗体事件里调这些静态方法。结果项目做到一半就撑不住了——一个界面里几十个控件的事件互相调用改一个地方的逻辑牵扯出一堆问题。这就是没有面向对象思想的典型症状。C#提供类、继承、接口、属性这些工具不是为了让你多打几个字而是逼你用更合理的结构去组织代码。1.2 面向对象到底解决了什么问题一句话概括面向对象让代码更贴近人对真实世界的认知方式。你看现实世界里的东西都有两个维度一个是“它是什么样”一个是“它能干什么”。比如一辆汽车它有颜色、品牌、排量、当前车速这些状态它也能启动、加速、刹车、转弯这些行为。你用C#来描述一辆车绝不会写两个单独的数组一个存车的属性一个存车的功能。你会建一个Car类把状态和行为都装进去。这就是面向对象的核心逻辑把相关的数据和操作数据的方法打包到一起。这样带来的好处非常直接代码结构清晰看到Car类就知道车有哪些数据和动作。复用方便建了一个Car类就能new出无数辆“车”。改动影响范围可控车的内部怎么实现刹车外部调用者不需要关心。后面讲到的封装、继承、多态本质上都是为了强化这三个好处。2. 用“图纸和房子”理解类与对象2.1 类是图纸对象是照着图纸盖出来的房子初学者最容易懵的一对概念就是“类”和“对象”到底什么关系。我建议你脑子里永远记住这个类比类就是建筑设计图纸对象就是按这张图纸盖出来的实实在在的房子。图纸长什么样图纸上写着“客厅面积20平米”“卧室朝南”“层高3米”但这些描述本身不占地方你没法住进一张图纸里。可当工人照着图纸把房子盖起来就出现了一个个具体的、能住人的房子。每套房子都遵守图纸上的设计但每套房子的实际状态可能不同——比如这套房子里住了人那套房子空着。对应到代码里// 这是图纸定义了房子有什么属性、能干什么 public class House { public string Color { get; set; } // 房子的颜色 public int Floors { get; set; } // 房子的层数 public void ShowInfo() { Console.WriteLine($这是一栋{Color}的{Floors}层房子); } } // 这是照着图纸盖出来的两栋具体房子 House myHouse new House(); myHouse.Color 白色; myHouse.Floors 3; House yourHouse new House(); yourHouse.Color 红色; yourHouse.Floors 5;House这个类本身只是描述只有用到new关键字时才真正在内存里创建了一个对象。你可以new出任意多个对象它们互不干扰。myHouse的颜色是白色yourHouse的颜色是红色两者井水不犯河水。2.2 字段、属性、方法各司其职一个类里最常见的就是三样东西字段、属性、方法。很多新手分不清字段和属性以为都能存数据随便用就行。这里给你一个最实用的理解方式字段field类内部用来保存数据的“私有笔记”通常用private修饰不对外开放。属性property对外暴露的“窗口”外面通过这个窗口读写数据。窗口后面可以加逻辑比如校验数据合不合理。方法method类的行为动作描述这个类“能做什么”。看一个例子public class Student { // 字段外部不应该直接访问所以是private private int age; // 属性外部通过Age这个窗口来读写age字段 public int Age { get { return age; } set { if (value 0 || value 150) { throw new ArgumentException(年龄不合法); } age value; } } // 方法对象能做的动作 public void Introduce() { Console.WriteLine($我{age}岁); } }注意看属性的set里加了校验。如果外部直接操作字段这种校验根本没地方写。这就是为什么要用属性包装字段——它让你在“数据进出”的关卡上多了一道防线。如果你在开发中见过{ get; set; }这样简写的自动属性其实就是编译器帮你隐藏了一个匿名字段本质逻辑和上面一样只是代码写少了。3. 封装把自己的秘密藏起来3.1 你不需要知道ATM机内部怎么数钱封装这个词听起来很高深实际上你每天都在用。你去ATM机取钱只需要插入银行卡、输入密码、按几个按钮钱就出来了。你不需要知道ATM机内部怎么验证磁条、怎么联网、怎么数钞。ATM机把内部的复杂逻辑藏起来只给你留几个按钮——这就是封装。面向对象里的封装就是把类内部的状态字段和具体实现细节藏起来只留下必要的访问入口属性和方法给外部。为什么要这么做因为如果不藏起来别人就能随意改你内部的数据程序很容易被搞乱。举个例子public class BankAccount { private decimal balance; // 余额外部不能直接改 public void Deposit(decimal amount) { if (amount 0) { throw new ArgumentException(存款金额必须大于0); } balance amount; } public void Withdraw(decimal amount) { if (amount 0 || amount balance) { throw new ArgumentException(取款金额不正确或余额不足); } balance - amount; } public decimal GetBalance() { return balance; } }如果balance是public的字段外部代码可以随便写account.balance -10000账户直接变成负数还绕过了校验逻辑。用私有字段加方法访问就保证了数据永远在可控范围内被修改。3.2 封装不是不让你用字段而是让你用对工具有些人学了封装之后矫枉过正把所有字段全写成private然后老老实实给每个字段配get/set方法。这么干没问题但代码会非常啰嗦。C#里给你准备了自动属性大多数场景下够用public string Name { get; set; }这行代码等于帮你声明了一个私有字段并提供标准的读写接口。如果后面业务逻辑变了比如Name不允许为空了你只需要把它改成完整属性写法在set里加校验就行——外部调用的代码一行都不用改。这就是“封装保护内部实现变化”的实战意义内部怎么改对外接口不变调用方就无感知。很多老项目改来改去调用方最怕的就是内部字段被直接暴露一旦改字段名整个项目到处报错。4. 继承站在别人的肩膀上写代码4.1 子类就是“更具体的父类”继承解决的核心问题是代码复用。你写了一个Animal类有Eat()和Sleep()方法。现在要写Dog类如果从零写你得把Eat和Sleep再抄一遍。但有了继承Dog只需要声明“我是Animal的子类”就自动拥有了Animal的所有公开成员public class Animal { public string Name { get; set; } public void Eat() { Console.WriteLine(${Name}正在吃东西); } public void Sleep() { Console.WriteLine(${Name}正在睡觉); } } public class Dog : Animal { public void Bark() { Console.WriteLine(${Name}汪汪叫); } }创建Dog对象时你既能调用Bark()也能调用继承来的Eat()和Sleep()。Dog自己只需要关注“狗特有”的行为公共的东西交给父类管。这里有个理解要点继承表达的是“is-a”是一个的关系。狗是一个动物所以Dog继承Animal合理。但“狗”和“猫”如果强行让一个继承另一个就会闹出笑话。新手设计继承时多问自己一句子类真的“是一个”父类吗如果不是就不该用继承。4.2 base关键字调用父类的“隐藏技能”子类里写构造函数时经常需要先让父类初始化一部分数据。这时候用base关键字。比如public class Animal { public string Name { get; set; } public Animal(string name) { Name name; } } public class Dog : Animal { public string Breed { get; set; } // 狗的品种 public Dog(string name, string breed) : base(name) { Breed breed; } }Dog的构造函数接收两个参数名字name通过base(name)传给父类的构造函数去处理自己只管品种。这样做避免了子类重复写父类的初始化逻辑。新手常在这个地方报错父类定义了带参数的构造函数子类不写构造函数或者不调用base编译器就会提示找不到父类的无参构造函数。解决办法就是上面这种写法子类构造函数后面加: base(参数)。4.3 继承滥用会让代码变成“豆腐渣工程”继承虽好但千万不能乱用。我之前见过有人把User类写成这样User : Person : Animal : Object理由是想让所有实体类都能复用一些基础属性。结果后面加需求时动一下最底层的Animal类所有相关类的行为都变了改出无数bug。组合优先于继承是业界比较公认的经验。能用“有一个”的关系就别用“是一个”。比如车有一个发动机这应该是Car类里有个Engine类型的属性而不是让Car继承Engine。继承只适用于真正稳定、层级清晰的“is-a”场景。5. 多态同一个动作不同的表现5.1 virtual/override让子类“有自己的想法”多态是面向对象里最能体现“灵活”的一个特性也是很多初学者觉得最难的部分。其实理解起来并不复杂同一个方法名在不同子类里有不同的实现。场景很常见动物都会叫但狗叫是“汪汪”猫叫是“喵喵”。父类Animal里可以定义一个虚方法MakeSound()子类重写它public class Animal { public virtual void MakeSound() { Console.WriteLine(动物发出声音); } } public class Dog : Animal { public override void MakeSound() { Console.WriteLine(汪汪汪); } } public class Cat : Animal { public override void MakeSound() { Console.WriteLine(喵喵喵); } }注意这里的两个关键字virtual告诉编译器这个方法允许子类重写。override子类明确表示我要覆盖父类的这个实现。调用的时候就能看出多态的威力Animal a1 new Dog(); Animal a2 new Cat(); a1.MakeSound(); // 输出汪汪汪 a2.MakeSound(); // 输出喵喵喵变量类型都是Animal但实际执行时调用的却是真实对象Dog/Cat里重写后的方法。这就是多态——同一句代码面对不同的实际对象产生不同的行为。5.2 里氏替换爸爸能做的事儿子都能做上面这个例子涉及一个重要的设计原则里氏替换原则。通俗地说就是“凡是能用父类的地方一定能换成子类”。你定义一个方法参数是Animal类型传Dog进去没问题传Cat进去也没问题因为子类完全具备父类的所有能力。但反过来不行你定义参数是Dog就不能传Animal因为Animal不一定会Bark。这个特性在实际开发中有个很常见的好处——面向接口/父类编程。你看这段代码public void AnimalPerform(Animal animal) { animal.MakeSound(); }这个方法不用关心传入的到底是Dog还是Cat只要传进来的是Animal就行。以后再加一个Pig类继承Animal并重写MakeSound这个方法一行都不用改就能支持猪叫。这就是多态带来的扩展性。很多大型框架里的“插件机制”本质上就是这种思路。5.3 抽象类和接口两种“规则制定者”除了普通的虚方法重写C#里还有两种更“强制”的多态设计抽象类和接口。抽象类用abstract修饰特点是不能直接new只能被继承。抽象类里可以有抽象方法——只有签名没有实现子类必须override。也可以有普通方法——子类可以直接用。public abstract class Shape { public abstract double GetArea(); // 子类必须实现 public void Print() { Console.WriteLine($面积是{GetArea()}); } } public class Circle : Shape { public double Radius { get; set; } public override double GetArea() { return Math.PI * Radius * Radius; } }接口用interface声明里面只能定义方法、属性、事件的签名不能有任何实现。一个类可以实现多个接口但只能继承一个类。接口强调“能做什么”比如IFlyable表示“能飞”ISwimmable表示“能游”一个类可以既会飞又会游那就同时实现这两个接口。老开发界有一句经验能用接口就别用抽象类能用组合就别用继承。接口的耦合度更低改起来影响面小。不过对初学者来说先把抽象类的语法搞熟再过度到接口会更顺。6. 一个完整案例把面向对象串起来6.1 需求模拟写一个迷你“动物园管理系统”很多零散的知识点单独看都懂一组合就懵。这里用一个很小的案例把类、对象、封装、继承、多态全部串一遍代码不长但包含了最核心的设计套路。需求管理一家动物园里的动物能展示所有动物的叫声。设计思路定义一个抽象父类Animal包含属性Name、方法MakeSound()。让Dog、Cat、Duck继承Animal各自重写MakeSound()。在主程序里创建这些动物对象放到一个ListAnimal里然后循环调用MakeSound()。代码实现public abstract class Animal { public string Name { get; set; } public Animal(string name) { Name name; } public abstract void MakeSound(); } public class Dog : Animal { public Dog(string name) : base(name) { } public override void MakeSound() { Console.WriteLine(${Name}汪汪汪); } } public class Cat : Animal { public Cat(string name) : base(name) { } public override void MakeSound() { Console.WriteLine(${Name}喵喵喵); } } public class Duck : Animal { public Duck(string name) : base(name) { } public override void MakeSound() { Console.WriteLine(${Name}嘎嘎嘎); } }主程序ListAnimal zoo new ListAnimal { new Dog(旺财), new Cat(咪咪), new Duck(唐老鸭) }; foreach (Animal animal in zoo) { animal.MakeSound(); }输出结果旺财汪汪汪 咪咪喵喵喵 唐老鸭嘎嘎嘎这段代码为什么“面向对象”你可以感受几个关键点数据和行为绑在一起每个动物的名字和叫声都在各自的类里没有散落在外面的函数。公共代码上提Name属性和构造函数在父类里统一处理子类省了重复代码。扩展极其方便要加一个Pig只需要新建类继承Animal重写MakeSound主程序里的循环一行都不用动。这种“开闭原则”对扩展开放对修改关闭的效果就是面向对象设计追求的核心目标之一。新手写代码时多想想新需求来了我是要改老代码还是只加新类如果是后者说明你的设计方向对了。6.2 新手写类时最容易踩的坑结合我这些年看代码的经验新手写类最容易犯这几个错第一个坑一个类塞太多职责。有人把数据库操作、业务逻辑、界面显示全写在一个类里动不动几百上千行。这叫“上帝类”看着好像方便其实改一行都可能牵连一片。好的类应该职责单一数据库操作的类只管数据读写业务逻辑的类只管规则处理界面层只管展示。第二个坑到处用static当“万能药”。初学者发现用static方法不用new对象调用很省事于是一言不合就static。但static方法的本质是“和具体对象无关”你在static方法里没法访问实例字段代码很快就会绕回面向过程的写法。static该用的地方是工具类、常量、单例模式业务对象尽量别用static。第三个坑构造函数里做太多复杂操作。构造函数是负责“把对象初始化到一个可用状态”不是让你去查数据库、发网络请求、加载大文件的。构造时逻辑越多测试和复用就越麻烦。常见的做法是构造函数只做简单的赋值和校验复杂逻辑放到专门的方法里。7. 面向对象在实际项目里长什么样7.1 从“能跑”到“能改”中间隔着一个面向对象很多初学者会有个疑问我写个几十行的小工具不用类也行为什么要学面向对象这个问题很真实。确实程序规模小的时候面向过程完全够用。但软件开发的规律是需求一定会变程序一定会长大。你今天写的小工具三个月后可能加了十几个功能变成了几千行代码。面向过程的代码在规模变大后最典型的问题是“数据到处飘”——全局变量、互相调用的函数、理不清的依赖关系。而面向对象把数据和操作打包相当于给代码划出了一块块边界清晰的“地盘”改起来风险可控得多。我面试过很多候选人做算法题的时候代码写得飞快但一问项目架构就说不清楚自己写的模块之间怎么交互。这种时候我基本能判断他平时写代码是“堆功能”不是“做设计”。7.2 几个直观的场景对比这里列几个我实际见过的开发场景同样是实现一个功能面向过程和面向对象的差异一目了然场景面向过程写法面向对象写法报表导出一个大方法里写Excel导出、PDF导出各自的逻辑用if判断格式定义一个IReportExporter接口ExcelReport和PdfReport各实现一个类调用时按需传入多设备通信用switch根据设备类型写不同的协议解析代码抽象一个Device类每个设备继承并实现自己的Parse方法界面表单校验在按钮点击事件里写一长串if/else把校验逻辑封装成Validator类不同的表单用不同的Validator实例表格右边的写法代价是前期多建几个类多写几行代码。但收益体现在后面加新设备类型、新导出格式时基本不用动已有代码只加新类然后在创建的地方加一行配置。这种“动静分离”的体验一旦你尝到甜头就不想再回到大if/else堆逻辑的日子了。7.3 学面向对象的建议路线如果你现在还是个小萌新我给你一个建议的学习路线照着走能少走弯路第一步把类、对象、字段、属性、方法这几个基础概念彻底搞清楚会用new创建对象能分清实例成员和静态成员。第二步把封装做到位——坚持所有字段用private修饰通过属性和方法来访问养成习惯后再谈其它。第三步练习继承但每次写继承前都问问自己这是不是真正的“is-a”关系。同时把base、virtual、override的语法搞熟。第四步学习接口和抽象类理解“面向抽象编程”是怎么回事尝试把已有的代码里那些if/else用多态替换掉。这个顺序是我带过的不少新人走下来最顺的路线。不要一上来就看什么设计模式基础不牢看模式只会觉得满脑子浆糊。等你能用类把一个小需求做得结构清晰再接触SOLID原则、工厂模式、依赖注入这些东西才会真正有“原来还可以这样写”的顿悟感。回到最开始那个问题为什么C#第一课就要面向对象因为不是C#选择面向对象而是从C#这门语言的设计之初面向对象就是它组织代码的基本方式。你理解了这个基础后面再看上位机开发里的串口类封装、Unity里的组件系统、Web开发里的控制器和模型都会觉得顺理成章。最后分享一个我个人的习惯学任何一个面向对象的概念时别先纠结语法细节先在纸上画一个图——类是一个方框里面上面写数据下面写方法对象是方框的具体实例用实线指向类。然后想象你要描述的现实事物怎么放进方框里事物之间的关系怎么用箭头表示。把图画明白了代码怎么写都是水到渠成的事。这个习惯帮我度过了刚接触C#那段最迷茫的时期。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

OrgKernel部署指南:PostgreSQL/MySQL/SQLite三大数据库任选,10分钟完成安装配置 2026/10/1 21:03:54

OrgKernel部署指南:PostgreSQL/MySQL/SQLite三大数据库任选,10分钟完成安装配置

OrgKernel部署指南:PostgreSQL/MySQL/SQLite三大数据库任选,10分钟完成安装配置 【免费下载链接】OrgKernel Open-source trust layer for AI agents — cryptographic agent identity (Ed25519), instance-scoped execution tokens, SHA-256 hash-chain…

阅读更多 →
HarmonyOS 7 Spatial Recon Kit:3DGS 分块渲染与端侧重建状态治理【鸿蒙心迹】 2026/10/1 21:03:53

HarmonyOS 7 Spatial Recon Kit:3DGS 分块渲染与端侧重建状态治理【鸿蒙心迹】

我这次没有把重点放在“把一个 3DGS 模型显示出来”,而是把一次真实的空间重建任务拆成了可追踪、可恢复、可诊断的工程链路:任务有 ID,进度有来源,状态有边界,分块渲染有视口驱动,异常也能知道停在了哪一步…

阅读更多 →
VME RFM2G反射内存卡驱动装载、事件处理与避坑指南 2026/10/1 21:03:45

VME RFM2G反射内存卡驱动装载、事件处理与避坑指南

简介:面向VME总线与反射内存应用开发者的RFM2g驱动及接口包,围绕VME 2GHz反射内存板卡提供完整的驱动调用方案。该驱动支持设备打开与关闭、从反射内存读取数据块、向反射内存写入数据块、按字节、字或长字直接访问存储空间、向其它节点发送中断事件&…

阅读更多 →
Claude Opus 4.6实战记录:代码重构、多模型接入与Windows踩坑指南 2026/10/1 21:03:38

Claude Opus 4.6实战记录:代码重构、多模型接入与Windows踩坑指南

上周我盯着一条刷了快一天的报错日志反复核对,Claude Code 从 Opus 4.1 切到 Opus 4.6 之后第一次跑完整回归测试。本来只是想看看这次升级到底值不值,结果它在二十分钟内定位了三个我之前翻遍代码都没找到的隐患。那一刻我就知道,这篇 Claud…

阅读更多 →
新能源外贸整合营销怎么做?2026海外社媒多平台服务商推荐 2026/10/1 21:03:31

新能源外贸整合营销怎么做?2026海外社媒多平台服务商推荐

摘要:新能源出海进入深水区,B2B企业不再满足于单点获客,而是需要覆盖建站、内容、社媒、投放、私域与展会的全链路营销体系。本文从行业痛点切入,结合星谷云16年出海服务经验与六大AI智能体能力,梳理2026年海外多平台整…

阅读更多 →
vue 表格组件 vxe-table 实现选择单元格生成柱状图和折线图等图表 2026/10/1 21:03:31

vue 表格组件 vxe-table 实现选择单元格生成柱状图和折线图等图表

在数据分析场景中,用户常常希望选中表格中的一块数据区域后,直接生成可视化图表。vxe-table 结合官方图表插件 vxe-ui/plugin-render-echarts,可以轻松实现“选中区域 → 右键菜单 → 生成图表”的完整流程,底层基于 ECharts 渲染…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉