C#类常用知识总结Pro

1、委托和事件(C#)

委托(Delegate)是什么?

委托 = 能存储「方法」的「变量」

委托的作用

  1. 方法当参数传递

  2. 实现解耦(调用者不用知道具体是谁执行)

  3. 事件、回调、Lambda、匿名函数的基础

事件(Event)是什么?

事件 = 加了「安全限制」的特殊委托

事件本质就是委托,但更安全、更适合做消息通知

特性 普通委托 事件(Event)
外部能否赋值 可以直接覆盖 不允许外部赋值
外部能否触发 可以随便调用 只有类内部能触发
使用场景 通用方法容器 消息通知、按钮点击、状态变化
安全性

事件 = 被保护起来的委托,防止被乱用。

极简代码示例

复制代码
// 1. 先定义委托(事件依赖委托)
delegate void DoorBellHandler();
​
// 2. 定义一个类,里面包含「门铃事件」
class Room
{
    // 定义事件(关键字 event)
    public event DoorBellHandler DoorBell;
​
    // 只有类内部能触发事件
    public void Ring()
    {
        DoorBell?.Invoke(); // 触发:有人按门铃
    }
}
​
// 3. 使用事件
class Program
{
    static void Main()
    {
        Room room = new Room();
        
        // 订阅事件:听到门铃就执行 OpenDoor
        room.DoorBell += OpenDoor;
​
        // 触发事件
        room.Ring();
    }
​
    static void OpenDoor()
    {
        Console.WriteLine("开门!");
    }
}

输出:

复制代码
开门!

事件的特点

  1. 只能用 += / -= 订阅 / 取消,不能用 = 覆盖

  2. 外部不能直接触发事件,保证安全

  3. 专门用于模块间通信:按钮点击、消息到达、数据更新

一句话终极总结

  1. 委托:装方法的盒子,能存、能调用、能传递。

  2. 事件受安全保护的委托 ,专门用来做通知 / 消息 / 回调

2、叙述private、protected、public、internal修饰符

一句话速记

权限从严到松:private < protected < internal < public,控制类 / 成员能被谁访问。

逐个详解

  1. private(私有) :最严格,只有当前类内部能访问。

    用法:字段、私有工具方法默认都用它。

  2. protected(受保护) :当前类 + 子类(不管在哪个程序集) 可以访问。

    用法:要留给继承类重写 / 调用的成员。

  3. internal(内部)整个当前项目 (程序集) 都能访问,跨项目不行。

    用法:项目内部共用、不想对外暴露的类。

  4. public(公开):完全无限制,任何地方、跨项目都能访问。

    用法:对外暴露的接口、公共工具类。

快速对比表

修饰符 本类 子类 同项目 跨项目
private
protected
internal
public

补充小规则

  1. 类内部成员默认权限是 private

  2. 顶级 class 默认是 internal;

  3. protected internal 是组合权限:同项目 或 子类 都能访问。

3、CTS、CLS、CLR分别怎么解释

CLR 是运行容器,CTS 是类型统一规则,CLS 是跨语言兼容子集三者是.NET 底层核心架构。

1. CLR (Common Language Runtime 公共语言运行时)

  • 通俗说:.NET 的虚拟机,程序跑起来的「发动机」。

  • 作用:管理内存、垃圾回收 GC、线程调度、安全校验、JIT 编译 IL 转机器码。

  • 位置:所有 C#/VB.NET代码最终都交给 CLR 执行。

2. CTS (Common Type System 通用类型系统)

  • 通俗说:.NET 所有语言统一的「类型标准字典」。

  • 作用:规定所有数据类型(int、string、类、继承)怎么定义、怎么继承、怎么兼容。

  • 意义:C# 的 int 和 VB 的 Integer 在 CTS 里是同一个类型,底层互通。

3. CLS (Common Language Specification 公共语言规范)

  • 通俗说:跨语言开发的「最低兼容公约」。

  • 作用:是 CTS 的子集,限制语法和命名,保证 C# 写的类能被 VB/F# 无缝调用。

  • 场景:做类库给其他.NET 语言调用时,要遵守 CLS 规范。

极简对比记忆

  1. CLR:管运行(程序活着靠它)

  2. CTS:管类型(所有语言类型统一)

  3. CLS:管兼容(跨语言互相调用)

4、什么是装箱和拆箱?

装箱:值类型 转 引用类型 ;拆箱:引用类型转回值类型,会损耗性能。

1. 基础概念

C# 分两种类型:

  • 值类型:int、double、struct,存在栈内存

  • 引用类型:object、string、类,存在堆内存

装箱(Boxing)

隐式自动发生:把栈上的值类型,包装进堆里的 object 对象

复制代码
int a = 10;
object obj = a; // 自动装箱

过程:a 在栈 → 复制数据到堆新建 object → obj 指向堆地址

拆箱(Unboxing)

显式强制转换:把堆里 object,拆回栈里的值类型

复制代码
int b = (int)obj; // 强制拆箱

过程:检查 obj 类型匹配 → 复制堆数据回栈变量

2. 关键特点

  1. 装箱自动、拆箱必须强转

  2. 频繁转换会产生 GC 压力、拖慢性能

  3. 泛型 List<T>能避免装箱拆箱,这也是优先用泛型的原因

3. 快速记忆口诀

值变引用是装箱,引用变值是拆箱;

装箱自动拆箱强,频繁使用耗性能。

5、string str = null 和string str = "";区别?

null 是空引用(没指向任何内存);"" 是空字符串(有内存对象,里面没字符)。

1. 底层本质

  1. string str = null;
  • 栈里变量没指向堆任何地址

  • 不存在字符串对象

  • 调用 .Length.Trim() 直接报空指针异常

  1. string str = "";
  • 在堆里已有空字符串常量对象

  • 内存已分配,是合法实例

  • 可以正常调用所有字符串方法,不会报错

2. 实操对比代码

复制代码
string a = null;
string b = "";
​
//Console.WriteLine(a == null); // True
Console.WriteLine(b == null); // False
Console.WriteLine(b.Length);  // 0 正常运行
​
a.ToString(); // 直接抛 NullReferenceException

3. 开发实用判断

日常判空推荐写法(一步兼顾两者):

复制代码
// 同时判断 null 和空字符串
if(string.IsNullOrEmpty(str)) 
{
    // 为空逻辑
}
// 连空白空格也一起判
if(string.IsNullOrWhiteSpace(str))

4. 快速记忆

  • null = 空空如也,对象都没有

  • "" = 空盒子,盒子存在但里面没东西

6、.NET(dotnet)中,类(class)和结构体(struct)的区别?

class 是引用类型 存堆、支持继承;struct 是值类型存栈、不能继承、轻量小巧。

1. 内存存储

  • class 类 :引用类型,实例存在堆 Heap,栈只存地址

  • struct 结构体 :值类型,直接存在栈 Stack(或随宿主内嵌)

2. 继承特性

  • class:支持单类继承、多接口继承,可抽象、可虚方法重写

  • struct:不能继承类,只能实现接口;默认隐式密封

3. 默认构造

  • class:可自定义无参构造

  • struct:编译器强制自带无参构造,不能手动写无参构造新版.NET 放宽但仍有约束)

4. 赋值行为

  • class 赋值:拷贝引用地址,多个变量指向同一个对象

  • struct 赋值:完整拷贝整个数据,互不影响

5. 初始化与 null

  • class:变量可赋值null,代表无实例

  • struct:值类型不能为 null(可加?变成可空 struct)

6. 使用场景

  • class:业务复杂、需要继承、对象较大、需要多地方共享引用

  • struct:数据简单小巧(坐标、颜色、配置参数)、高频创建追求性能

快速对照表

特性 class 类 struct 结构体
类型 引用类型 值类型
内存 栈 / 内嵌
继承 支持类继承 不可继承类
赋值 引用拷贝 值完整拷贝
null 可以 null 默认不可 null
适用 复杂业务对象 轻量小型数据

7、枚举的作用是什么?

枚举就是把固定常量换成易懂名字,替代魔法数字,代码更干净、不易错。

1. 基础理解

不用枚举时:

复制代码
// 看不懂 1/2/3 代表啥,还容易写错
int status = 1; 

用枚举:

复制代码
enum OrderStatus
{
    WaitPay = 1,
    Shipping = 2,
    Finished = 3
}
OrderStatus status = OrderStatus.WaitPay;

一眼就懂语义。

2. 核心作用

  1. 语义清晰:用名字代替数字,可读性拉满

  2. 限制取值:只能选枚举里定义的值,避免乱传非法数字

  3. 统一规范:全项目共用一套固定状态,不会各写各的

  4. 便于维护:改一处枚举,全局生效

  5. 配合强类型:编译器校验,减少 Bug

3. 常用场景

订单状态、性别、权限级别、设备模式、开关类型这类固定有限选项,全都适合用枚举。

4. 小补充

默认底层是int,可以改类型;还能转数字、字符串互相解析。

8、内存泄露

内存泄露:不用的对象还被强引用挂着,GC 回收不掉,内存越用越多、程序越来越卡

1. 通俗理解

你租房子:搬走了但没退钥匙 → 房子一直占着不让别人用

代码里:对象已经没用了,还有地方偷偷引用它 → GC 清不掉,内存堆积。

2. C# 常见泄露原因

  1. 事件没解绑

    订阅事件

    复制代码
    +=

    后,退出不用了没

    复制代码
    -=

    ,对象被死死持有。

  2. 静态集合一直累加

    复制代码
    static List

    不停 Add,从不 Clear,对象永远被引用。

  3. 长生命周期持有短生命周期对象

    全局类引用窗口 / 临时控件,窗口销毁也释放不了。

  4. 非托管资源没释放

    文件流、数据库连接、串口、Bitmap 没

    复制代码
    Dispose()

    、没 using。

  5. 定时器 / 后台线程没停止

    线程死循环、Timer 不关,牢牢绑定对象。

3. 怎么避免

  1. 事件成对:+=订阅,关闭时必-=取消

  2. 非托管资源用 using 自动释放

  3. 静态容器定期清理、不要无限加数据

  4. 窗口 / 页面销毁时:停线程、关定时器、清空引用

  5. 用弱引用WeakReference存非必要对象

4. 快速判断现象

程序运行越久内存越高,重启就恢复 → 大概率内存泄漏。

事件导致内存泄漏 完整版对照代码

1. 泄漏版(错误写法)

核心问题:订阅事件后从不解绑,宿主销毁还被持有,GC 回收不掉

复制代码
// 发布者(生命周期很长)
public class LongLifePublisher
{
    public event Action UpdateEvent;
    public void Trigger() => UpdateEvent?.Invoke();
}

// 订阅者(短生命周期,用完要销毁)
public class ShortLifeSubscriber
{
    public void DoSomething()
    {
        Console.WriteLine("执行业务逻辑");
    }
}

// 调用测试
class Program
{
    static void Main()
    {
        var publisher = new LongLifePublisher();

        // 局部订阅者,本该用完就回收
        var sub = new ShortLifeSubscriber();
        publisher.UpdateEvent += sub.DoSomething; 

        // sub 离开作用域,手动置空
        sub = null;
        
        // GC强制回收
        GC.Collect();
        GC.WaitForPendingFinalizers();

        // 结果:sub 根本没被回收!事件还握着它的引用 → 内存泄漏
    }
}

2. 修复版(正确写法)

用完必须 -= 解绑事件

复制代码
class Program
{
    static void Main()
    {
        var publisher = new LongLifePublisher();
        var sub = new ShortLifeSubscriber();

        // 订阅
        publisher.UpdateEvent += sub.DoSomething;

        // 关键:不用了立刻解绑
        publisher.UpdateEvent -= sub.DoSomething;

        sub = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();

        // 现在:订阅者成功被GC回收,无泄漏
    }
}

3. 工业上位机常用补充(你工控开发必用)

窗口关闭时固定套路:

复制代码
protected override void OnClosed(EventArgs e)
{
    // 1. 解绑所有外部事件
    plc.DataReceive -= Plc_DataReceive;
    
    // 2. 释放串口/相机/流资源
    serialPort?.Close();
    camera?.Dispose();
    
    // 3. 停定时器、后台线程
    timer?.Stop();

    base.OnClosed(e);
}

4. 一句话记死

长对象绑短对象事件,不解绑必泄漏;退出页面全清理,内存稳得住

静态集合导致内存泄漏 + 修复代码

1. 泄漏原理

static 静态集合全局常驻内存,不断 Add 对象、从不清理;

临时对象用完还被集合强引用,GC 永远回收不掉,内存一路暴涨。

2. 泄漏错误代码

复制代码
// 全局静态列表:程序整个生命周期都存在
public static class GlobalContainer
{
    // 静态集合常驻内存
    public static List<UserInfo> UserList = new List<UserInfo>();
}

// 临时业务类
public class UserInfo
{
    public string Name { get; set; }
}

class Program
{
    static void Main()
    {
        for (int i = 0; i < 1000; i++)
        {
            // 创建大量临时对象
            var user = new UserInfo();
            // 加入静态集合
            GlobalContainer.UserList.Add(user);
        }

        // 循环结束,所有user局部变量失效
        // 但还卡在静态List里 → GC无法回收 → 内存泄漏
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

问题关键点

  1. static List 生命周期 = 整个程序

  2. 不断 Add 不 Remove/Clear

  3. 临时对象被静态集合永久持有,泄漏常驻


3. 修复正确写法

方案 1:用完主动清空 / 移除
复制代码
//业务结束后,不再使用立刻清理
GlobalContainer.UserList.Clear();
方案 2:按需复用,不要无限新增
复制代码
//复用旧对象,而非一直new添加
if(GlobalContainer.UserList.Count > 0)
{
    var user = GlobalContainer.UserList[0];
}
方案 3:弱引用存储(超大对象专用)
复制代码
//弱引用:GC想回收就能回收,不卡死内存
public static List<WeakReference<UserInfo>> WeakUserList 
    = new List<WeakReference<UserInfo>>();

//添加方式
WeakUserList.Add(new WeakReference<UserInfo>(new UserInfo()));

工控上位机额外重点(你高频踩坑)

  1. 设备连接、相机图像缓存不要塞静态 List 无限堆积;

  2. 日志缓存、报警列表一定要定时清理 / 限制最大条数

  3. 页面关闭时,清空绑定的静态数据源。

9、什么是SDK

SDK = 给开发者用的「工具包」,让你不用写底层代码,直接调用现成功能。


1. 通俗理解

你要做一个扫码支付的功能:

  • 自己从头写:加密、网络、安全、对接银行 → 累死

  • 用支付宝 SDK:直接调用 Pay() 一个方法就搞定

SDK = 别人帮你把复杂底层封好,你拿来直接用


2. 官方解释

SDK(Software Development Kit)

软件开发工具包

  • 包含:库文件(dll)+ 示例代码 + 文档 + 工具

  • 作用:快速实现某类功能(相机、PLC、串口、人脸识别、支付)

  • 你在 C# 里添加的NuGet 包、PLC 驱动、相机 SDK 都是 SDK


3. 你在工控 / 上位机里最常见的 SDK

  1. PLC SDK(欧姆龙、三菱、西门子、Modbus)

  2. 相机 SDK(海康威视、大恒、Basler)

  3. 串口 / 网口通讯 SDK

  4. 硬件传感器 SDK

这些都是厂商给你封装好的,你直接调用方法就能读写数据。


4. SDK 和 API 的简单区别

  • SDK:工具箱(一堆工具 + 方法 + 文档)

  • API :工具箱里的某个具体接口 / 方法

例子:

  • 海康 SDK = 整个相机工具包

  • Play()Capture() = 里面的 API 接口


5. 面试一句话背下来

SDK 是软件开发工具包,包含库、文档、示例,帮助开发者快速调用硬件或第三方功能,不用从零开发底层。


总结

  • SDK = 现成功能工具箱

  • 拿来直接用,不用造轮子

  • 工控里:PLC、相机、硬件驱动 几乎都是 SDK

10、什么是面向过程编程

面向过程:按步骤写代码,流程至上,跟着做事顺序一路往下执行

1. 通俗理解

像写菜谱:

第一步洗菜 → 第二步切菜 → 第三步下锅 → 第四步出锅

从头到尾一条线,按顺序执行,拆成一个个函数 / 步骤

2. 核心特点

  1. 关注怎么做流程,不是关注事物本身

  2. 代码由 全局变量 + 一堆函数 组成

  3. 数据和逻辑分开存放,不封装

  4. 结构线性,适合简单小程序

3. C# 极简例子(面向过程风格)

csharp

运行

复制代码
// 全局数据
static string name;
static int age;
​
// 函数:赋值
static void SetInfo(string n, int a)
{
    name = n;
    age = a;
}
​
// 函数:打印
static void ShowInfo()
{
    Console.WriteLine(name + " " + age);
}
​
// 按流程一步步调用
static void Main()
{
    SetInfo("张三", 20);
    ShowInfo();
}

4. 和面向对象对比

  • 面向过程:先流程后步骤,数据散着放

  • 面向对象:先封装成类,再调用对象行为

5. 记忆口诀

面向过程流水线,一步一步往前连;

数据逻辑各一边,简单小项目最方便。

11、什么是面向对象编程

面向对象编程 (OOP):把现实里的事物封装成类,用封装、继承、多态三大特性管理代码,复用性和扩展性更强。

1. 通俗理解

不按步骤写流程,而是先造模型

比如做学生管理:

先建「学生类」(包含姓名、年龄 + 学习行为),再 new 出一个个学生对象直接用,不用反复写重复逻辑。

2. 三大核心特性(必背)

  1. 封装:把数据和方法藏在类里,对外只暴露必要接口,保护内部数据安全。

  2. 继承:子类复用父类代码,不用重复写相同逻辑。

  3. 多态:同一个方法,不同子类能实现不一样的行为,灵活扩展。

3. 极简代码对比

复制代码
// 定义类:封装属性和行为
public class Student
{
    // 封装
    public string Name { get; set; }
    public void Study()
    {
        Console.WriteLine("正在学习");
    }
}

// 使用对象
Student s = new Student();
s.Name = "小李";
s.Study();

4. 和面向过程一眼分清

  • 面向过程:关注怎么做步骤,流水线执行

  • 面向对象:关注是谁、有什么能力,先建模再调用

5. 快速口诀

封装藏细节,继承省代码,多态更灵活;万物皆对象,项目好维护。

12、八大基本类型

C# 8 大基本值类型:整数 4 种 + 浮点 2 种 + 字符 1 种 + 布尔 1 种

1. 完整列表 + 占用字节

  1. byte :1 字节,0~255(无符号)

  2. short:2 字节,短整型

  3. int:4 字节,常用整型(默认)

  4. long:8 字节,长整型


  1. float:4 字节,单精度小数(后缀 f)

  2. double:8 字节,双精度小数(默认小数)


  1. char:2 字节,单个字符

  2. bool:1 字节,true/false 布尔

2. 补充重点

  • string 不是基本类型,是引用类型

  • decimal 财务专用,也不属于 8 大基础类型

  • 默认整数写数字是int;小数默认是double

3. 极简背诵口诀

字节 byte 长短 long,int 日常最常用;

float 单精 double 双,char 字符 bool 判空。

13、什么是值类型?什么是引用类型?

值类型存栈,拷贝就是复制本身;引用类型存堆,拷贝只复制地址。

一、值类型

  1. 存储位置:栈 (Stack)

  2. 赋值特点:互相独立,改一个不影响另一个

  3. 包含:基础数值 (int/double)、bool、char、struct、枚举

  4. 例子:

复制代码
int a = 10;
int b = a;
b = 20;
// a还是10,互不干扰

二、引用类型

  1. 存储位置:数据在堆 (Heap),栈里只存地址引用

  2. 赋值特点:多个变量指向同一个堆对象,一改全改

  3. 包含:class、string、数组、委托

  4. 例子:

复制代码
Person p1 = new Person();
p1.Name = "小明";
Person p2 = p1;  
p2.Name = "小红"; 
// p1.Name 也变成小红,因为指向同一个对象

三、快速对比记忆

对比 值类型 引用类型
内存 堆存数据,栈存引用
赋值 完全复制,互不影响 复制地址,共用对象
默认值 不能为 null 可以赋值 null
代表 int、struct、枚举 class、string、数组

补充彩蛋

string 看着像值类型(改值不互相影响),本质是引用类型,只是有字符串不可变特性。

14、什么是封装

封装:把数据和方法包进类里,隐藏内部细节,只暴露安全入口给外面用。

1. 通俗理解

像充电宝:

外壳包住内部电池、电路(隐藏细节)

只留充电口 / 按键给你用(暴露简单接口)

你不用管里面结构,安全又好用。

2. C# 代码体现

  • 字段用 private 藏起来

  • 属性 / 方法 public 对外开放

复制代码
public class Person
{
    // 隐藏内部数据
    private int _age;

    // 公开安全访问入口
    public int Age
    {
        get => _age;
        set
        {
            // 加校验,防止非法数据
            if(value > 0 && value < 150)
                _age = value;
        }
    }
}

3. 三大好处

  1. 安全:外部不能随便乱改内部字段,能加校验

  2. 简洁:内部逻辑藏起来,调用简单

  3. 易维护:内部改代码,外部不用动

速记口诀

数据私有藏内部,公开方法做门户;保护隐私防乱改,封装安全好维护。

15、什么是继承

继承就是子类直接复用父类代码,不用重复写,还能扩展新功能

通俗理解

父亲会:开车、赚钱

儿子继承父亲:天生就会开车、赚钱(直接复用)

儿子还能额外学:编程(自己扩展新能力)

C# 代码示例

复制代码
// 父类
public class Person
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("睡觉");
    }
}

// 子类 Student 继承 Person
public class Student : Person
{
    // 自己新增的方法
    public void Study()
    {
        Console.WriteLine("学习");
    }
}

调用:

复制代码
Student s = new Student();
s.Name = "小明"; 
s.Sleep();  // 直接继承父类,不用重写
s.Study();  // 自己独有

继承核心特点

  1. 减少重复代码,复用父类成员

  2. 单一继承:C# 类只能继承一个父类

  3. 可以用override重写父类方法改逻辑

口诀

父类通用写一套,子类继承直接套;

原有功能不用改,新增扩展更灵巧。

16、什么是多态

父类引用指向子类对象,同一个方法,不同子类执行不同逻辑,这就是多态。

通俗理解

同样是 "叫声":

猫叫喵喵、狗叫汪汪;

你统一喊【动物发声】,但猫和狗表现不一样 ------ 这就是多态。

C# 核心写法(背熟)

  1. 父类方法加 virtual

  2. 子类重写加 override

  3. 用父类变量装子类实例

代码示例

复制代码
//父类
public class Animal
{
    public virtual void Cry()
    {
        Console.WriteLine("动物叫");
    }
}

//子类1
public class Cat : Animal
{
    public override void Cry()
    {
        Console.WriteLine("喵喵");
    }
}

//子类2
public class Dog : Animal
{
    public override void Cry()
    {
        Console.WriteLine("汪汪");
    }
}

调用:

复制代码
Animal a1 = new Cat();
Animal a2 = new Dog();

a1.Cry(); //喵喵
a2.Cry(); //汪汪

三大好处

  1. 统一父类写法,不用写一堆 if 判断

  2. 新增子类不用改旧代码,扩展性强

  3. 项目结构更干净

速记口诀

虚方法父类打底,子类重写改逻辑;

父类引用装子类,运行自动看真身。

17、什么是重载?什么是重写?

重载:同一个类里,方法名一样、参数不一样;

重写:继承关系里,子类覆盖父类的虚方法。

一、重载(Overload)

  1. 位置:同一个类内部

  2. 规则:

  • 方法名完全相同

  • 参数个数 / 类型 / 顺序不同

  • 和返回值无关、不需要关键字

  1. 代码示例

csharp

运行

复制代码
public void Calc(int a) { }
public void Calc(int a, int b) { } // 参数不同:重载
public void Calc(double a) { }     // 类型不同:重载
  1. 特点:编译时就确定调用哪个

二、重写(Override)

  1. 位置:父子继承关系

  2. 规则:

  • 父类:virtual 虚方法

  • 子类:用 override 完全覆盖

  • 方法名、参数、返回值必须一模一样

  1. 代码示例

csharp

运行

复制代码
public class Father
{
    public virtual void Show(){}
}
public class Son : Father
{
    public override void Show(){} // 重写
}
  1. 特点:运行时看真实对象类型(多态核心)

三、一眼区分对照表

表格

对比 重载 Overload 重写 Override
关系 同类平级 父子继承
关键字 不用关键字 virtual + override
参数 必须不一样 必须完全一样
多态 编译多态 运行多态

口诀背

重载同类多名参;

重写继承盖父栏。

18、基本类型传值

C# 里基本值类型传参默认是值传递:传的是拷贝,方法里改参数,外面原值完全不变。

1. 简单例子

复制代码
void Change(int num)
{
    num = 999; // 改的是副本
}

int a = 10;
Change(a);
Console.WriteLine(a); // 输出还是 10

2. 原理

  • int、double、bool、struct 都是值类型

  • 传参时:复制一份副本进方法

  • 方法内部只改副本,外面原变量不受影响

3. 想改外面原值怎么办?

ref / out 变成引用传递:

复制代码
void ChangeRef(ref int num)
{
    num = 999;
}

int a = 10;
ChangeRef(ref a);
Console.WriteLine(a); // 输出 999

口诀记住

基本类型默认传值,修改互不影响;

加 ref 才改原变量。

19、什么是引用类型传值

引用类型传值:传的是地址的副本,地址不变能改对象内容,但不能换外面的引用本身

1. 通俗拆解

  1. 引用类型(class 实例)栈里存地址,堆里存真实对象;

  2. 传参时:把地址复制一份传给方法(传值);

  3. 两个变量地址一样,指向同一个堆对象。

2. 代码演示(能改对象内部)

复制代码
public class Person
{
    public string Name { get; set; }
}

void Test(Person p)
{
    p.Name = "李四"; // 修改堆里原对象内容
}

//调用
Person per = new Person(){Name="张三"};
Test(per);
Console.WriteLine(per.Name); // 输出:李四 ✅ 变了

3. 关键对比误区

方法里new 新对象,外面不会变:

复制代码
void TestNew(Person p)
{
    p = new Person(){Name="新的人"}; // 只改副本地址
}

Person per = new Person(){Name="张三"};
TestNew(per);
Console.WriteLine(per.Name); // 还是张三 ❌没变

4. 核心口诀

引用传值传地址副本:

对象字段全局生效;

引用指向外面不动。

补充

想直接换掉外面引用,要加 ref

复制代码
void TestRef(ref Person p)
{
    p = new Person();
}

20、什么是抽象类?什么是接口?

抽象类:半完整模板,有成员有实现,只能单继承;

接口:纯行为规范,全是定义无字段,能多实现。

一、抽象类 abstract class

  1. 特点
  • abstract 修饰,不能 new 实例

  • 可以有:属性、字段、已实现方法 + 抽象无体方法

  • 子类必须重写全部抽象方法

  • C# 只能继承一个抽象类

  • 有构造函数

  1. 极简示例
复制代码
abstract class Animal
{
    // 已有实现
    public void Sleep() => Console.WriteLine("睡觉");
    // 抽象方法:必须子类重写
    public abstract void Cry();
}

class Cat : Animal
{
    public override void Cry() => Console.WriteLine("喵喵");
}

二、接口 interface

  1. 特点
  • interface 定义,全是行为契约

  • 不能有字段、不能有构造函数

  • 默认成员都是 public

  • 一个类可以同时实现多个接口

  • 新版 C# 可加默认方法实现,但依然无状态

  1. 极简示例
复制代码
interface IRun
{
    void Run();
}

class Dog : IRun
{
    public void Run() => Console.WriteLine("狗狗奔跑");
}

三、核心对比(速背)

区别 抽象类 接口
内容 有字段、有实现、有抽象 只有方法 / 属性定义(无字段)
继承数量 只能继承 1 个 能实现多个
构造函数 没有
定位 父子「血缘模板」 能力「行为规范」

四、口诀

抽象类存状态,一单继承带实现;

接口定行为,多实现纯规范。

21、如何在同一个类中实现具有相同方法名的接口

同名接口方法冲突时,用显式接口实现,只能通过接口调用,隐藏冲突方法。

1. 场景:两个接口同名方法

复制代码
// 接口1
interface IA
{
    void Show();
}

// 接口2
interface IB
{
    void Show();
}

同一个类同时实现,两个Show重名冲突。

2. 普通写法会报错,显式实现解决

复制代码
public class Test : IA, IB
{
    // 显式实现 IA 的Show
    void IA.Show()
    {
        Console.WriteLine("IA 的 Show");
    }

    // 显式实现 IB 的Show
    void IB.Show()
    {
        Console.WriteLine("IB 的 Show");
    }
}

3. 调用方式(重点)

显式实现的方法不是 public,不能直接用类对象点出来:

复制代码
Test t = new Test();
// t.Show(); // 报错,访问不到

// 必须转成接口类型调用
IA a = t;
a.Show();   // IA 的 Show

IB b = t;
b.Show();   // IB 的 Show

4. 混合写法(一个公有、一个显式)

如果想留一个通用公开:

复制代码
public class Test : IA, IB
{
    // 公有统一实现
    public void Show()
    {
        Console.WriteLine("公共Show");
    }

    // 单独显式覆盖另一个
    void IB.Show()
    {
        Console.WriteLine("IB专属Show");
    }
}

速记口诀

接口同名有冲突,显式写明接口名

实例不能直接调,转成接口再调用。

22、什么构造函数?它有那些使用方法?那些不同的类型?

构造函数:创建对象new时自动执行的特殊方法,用来初始化赋值,名字和类名一模一样。

一、核心特点

  1. 方法名 = 类名

  2. 没有返回值

  3. new 类名() 自动调用

  4. 作用:初始化字段、属性

二、三大类型 + 用法(超好记)

1. 无参构造(默认构造)

没参数,不写编译器会自动给一个

复制代码
public class Person
{
    // 无参构造
    public Person()
    {
        Console.WriteLine("无参构造执行");
    }
}
//调用:Person p = new Person();

2. 有参构造

创建对象直接给成员赋值,不用挨个 set

复制代码
public class Person
{
    public string Name;
    //有参构造
    public Person(string name)
    {
        Name = name;
    }
}
//调用:Person p = new Person("张三");

3. 静态构造函数 static

只执行一次,程序第一次用到类自动触发,初始化静态成员

复制代码
public class Person
{
    public static string Type;
    //静态构造
    static Person()
    {
        Type = "人类";
    }
}

三、补充:构造函数重载 & 构造链

  1. 重载:一个类多个构造(参数不同)

  2. this 调用本类其他构造

复制代码
public Person() : this("默认名") 
{

}
public Person(string name)
{
    Name = name;
}

四、快速背诵总结

  1. 类型:无参、有参、静态三种

  2. 用途:实例初始化、静态数据初始化

  3. 规则:同名无返回,new 自动跑;静态只跑一次

23、try、catch、finally作用?

try 放可能报错代码,catch 捕获异常处理,finally 无论成败一定执行。

1. 各自作用

  • try:包裹容易崩溃、出异常的风险代码

  • catch:抓到异常,防止程序闪退,做报错提示 / 补救

  • finally :不管报错与否,必定执行,专门用来释放资源

2. 最简代码示例

复制代码
try
{
    int a = 1 / 0; // 一定会报错
}
catch (Exception ex)
{
    // 捕获错误,程序不崩
    Console.WriteLine("出错了:" + ex.Message);
}
finally
{
    // 必执行:关连接、关串口、释放设备
    Console.WriteLine("我一定会执行");
}

3. 工控 / 上位机重点用法

释放资源全写在 finally:

关闭串口、相机 Dispose、数据库连接、停止定时器。

4. 记忆口诀

try 冒险防崩溃,

catch 抓错救程序,

finally 收尾必执行,

资源释放放这里。

24、什么是泛型

泛型就是模板占位符,先不定死类型,用的时候再指定;一套代码适配所有类型,安全又不用重复写。

1. 通俗理解

不用泛型:

写一个 int 容器、再写一个 string 容器、再写对象容器 → 重复抄代码

用泛型:

只写 1 个模板<T>,int/string/ 类随便套用,全程强类型不拆箱。

2. 核心优点

  1. 代码复用:一套搞定所有数据类型

  2. 类型安全:编译报错,不会存错类型

  3. 无装箱拆箱:性能更高

3. 最简单代码示例

泛型类

复制代码
// T 是类型占位符
public class Box<T>
{
    public T Value { get; set; }
}

// 使用时再指定类型
Box<int> b1 = new Box<int>();
Box<string> b2 = new Box<string>();

泛型方法

复制代码
public void Show<T>(T msg)
{
    Console.WriteLine(msg);
}
Show(100);
Show("Hello");

4. 你天天在用的泛型

复制代码
List<int>       Dictionary<string,object>

这些全是泛型。

5. 记忆口诀

尖括号放 T,类型后期替;

复用又安全,性能还给力。

25、类和对象

类是模板,对象是模板造出来的实例;类抽象定义,对象真实存在。

1. 通俗理解

  • = 图纸(汽车设计图,定义有轮子、能跑)

  • 对象

    = 真车(按图纸造出来的一辆辆实体车)

一张图纸,可以造出无数辆车。

2. 代码演示

csharp

运行

复制代码
// 1.类:模板
public class Person
{
    public string Name;
    public void Speak(){}
}

// 2.对象:new 出来实例
Person p1 = new Person();
p1.Name = "张三";

Person p2 = new Person();
p2.Name = "李四";

3. 核心区别

  1. 类:抽象、定义、不占实例内存,不能直接用

  2. 对象:具体、实例、堆内存分配,可以调用属性方法

  3. 关系:一个类可以 new 出无数个对象

4. 速记口诀

类是模型画蓝图,对象实例堆里住;

一类能生千万象,各自数据互不输。

26、关键字static

static 静态:属于类本身,不属于对象;全局唯一,不用 new 就能直接用。

1. 三大用法

  1. 静态字段 / 属性

    全类共享一份内存,所有对象共用

复制代码
public class Person
{
    public static int Count = 0;
}
//调用:类名.成员
Person.Count = 10;
  1. 静态方法

    不用 new 对象,直接类名调用,不能访问非静态成员

复制代码
public static void Show(){}
Person.Show();
  1. 静态构造函数

    类第一次被使用时,只执行一次,初始化静态数据

复制代码
static Person()
{
    Count = 1;
}

2. 核心区别

  • 非静态:属于对象,new 一个就有一份

  • 静态:属于类,全局只有一份,常驻内存

3. 内存 & 踩坑(你工控开发重点)

  1. static 变量程序运行全程不释放,容易内存泄漏

  2. 多线程下静态共享数据,要加锁防冲突

4. 口诀

静态属于类,全局只一份;

不用 new 实例,常驻要谨慎。

相关推荐
yugi9878382 小时前
基于STM32F107和DP83848的TCP服务器数据收发方案
服务器·stm32·tcp/ip
番茄去哪了2 小时前
Retrofit框架调用第三方api
java·服务器·retrofit
攻城狮在此2 小时前
MobaXterm下载安装及SSH远程连接(交换机/路由器/服务器)
linux·运维·服务器·网络
花间相见2 小时前
【Agent开发】—— ToolCall 、 FunctionCall 底层原理与极简实现
运维·服务器
武藤一雄2 小时前
深入理解 C# 中的 sizeof 与非托管类型约束
开发语言·windows·c#·.net·.netcore
mounter6253 小时前
【LSF/MM内核前沿】Linux 内存回收推倒重来?解析 MGLRU 与传统 LRU 的“统一之战”
linux·运维·服务器·网络·内核·内存回收
Exquisite.3 小时前
k8s的Pod管理
linux·运维·服务器
IMPYLH3 小时前
Linux 的 env 命令
linux·运维·服务器·数据库
桌面运维家3 小时前
Nginx服务器安全:高级访问控制与流量清洗实战
服务器·nginx·安全