十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#类分类全解析:从职责划分到实战应用

C#类分类全解析:从职责划分到实战应用 “类”这个概念在 C# 里其实特别大。你说它是面向对象的基本单位吧对但真到项目里你写一个User、写一个OrderService、写一个Program它们都叫“类”职责和用法却完全不一样。很多新人卡在“C#类的分类”上多半不是语法不懂而是没搞明白一个类到底该扮演什么角色、该按什么规则设计面试问“抽象类和接口的区别”能背出来真到自己搭上位机项目、写数据采集框架的时候类还是堆成一团。这篇文章我想换个角度把 C# 里的类拆成几个维度来讲按职责分、按定义约束分、按进阶形态分、按生命周期分最后落到扫码枪、Socket 通信、循环采集 UI 刷新这些实战场景里。无论你是刚入门的 C# 学习者还是准备面试或者正在做上位机、IoT、桌面端开发这套分类思路都能直接拿来用。1. 按职责分工给类分类先把项目结构理清楚写项目跟组织团队很像。你不可能让一个人既管财务又写代码还负责对外接待类也一样。我一般在项目里第一眼看的就是这个类是干什么的也就是“职责”这是最贴近日常开发的一种分类方式。1.1 实体类只装数据不干活实体类也叫模型类、POCO 类是项目里最朴素的一类。它基本上只包含属性可能加几个简单的校验或计算属性但绝对不碰数据库操作、不碰文件读写、不碰业务规则。public class Product { public int Id { get; set; } public string Name { get; set; } string.Empty; public decimal Price { get; set; } public int Stock { get; set; } }这类类存在的意义就是“描述数据长什么样”。它对应数据库的一张表、某个接口返回的 JSON 结构或者界面上一个表单模型。我见过不少人把业务方法也塞进实体类里比如Save()、Delete()小项目可能没问题但一旦业务复杂起来实体类就变成了大杂烩改一个数据库逻辑要牵连整个对象。实体类下面还可以细分出 DTO数据传输对象、ViewModel视图模型、POCO纯 CLR 对象。它们本质都是“数据载体”区别只在于服务对象不同DTO 服务于接口层ViewModel 服务于 UI 层POCO 强调不依赖任何框架。设计原则就是保持干净最多放一些只读计算属性。1.2 业务逻辑类流程和规则都归它管业务逻辑类通常叫 Service、Manager、Handler、Provider。它负责的是“这个业务应该怎么走”。比如下单需要校验库存、计算金额、生成订单号、扣减库存这些步骤串起来就是OrderService的活。public class OrderService { private readonly IProductRepository _productRepository; private readonly IOrderRepository _orderRepository; public OrderService(IProductRepository productRepository, IOrderRepository orderRepository) { _productRepository productRepository; _orderRepository orderRepository; } public async TaskOrderResult CreateOrderAsync(CreateOrderRequest request) { // 1. 校验参数 // 2. 查询库存 // 3. 计算金额 // 4. 保存订单 } }这类类我通常建议用“接口 实现类”的方式成对出现IOrderService和OrderService。好处是方便替换、方便写单元测试也方便依赖注入。业务逻辑类的命名后缀很重要一看xxxService、xxxManager就知道这地方是处理流程的而不是装数据的。1.3 工具类静态方法为主无状态纯函数工具类也叫 Helper、Utility、Extension。它的特点是不保存任何实例状态全部方法都是静态的只做“输入输出转换”。字符串截取、日期格式化、Excel 导出、正则校验这些无状态操作适合放工具类。public static class StringHelper { public static string CutString(string input, int maxLength, string suffix ...) { if (string.IsNullOrEmpty(input) || input.Length maxLength) return input; return input.Substring(0, maxLength) suffix; } }工具类是静态类最常见的应用场景。写的时候要克制不要什么方法都往里丢。我看到过几千行的CommonHelper里面从数据库连接字符串加密到按钮闪烁控制全都有这种类其实就是“垃圾抽屉”维护起来非常痛苦。工具类的方法应该是通用的、无副作用的、不依赖全局状态的否则它不应该叫工具类而应该叫业务类。注意工具类里不要接数据库、不要读写配置文件、不要依赖其他服务。一旦工具类有了“状态”和“环境依赖”它就不纯了很难测也容易出莫名其妙的坑。1.4 配置与选项类把 AppSettings 变成强类型配置类是用强类型方式承载配置项的类通常和IOptionsT配套使用。以前读配置是到处ConfigurationManager.AppSettings[Key]拼错一个字符串到运行时才发现。用了配置类编译期就能发现错误。public class DeviceOptions { public string ComPort { get; set; } COM3; public int BaudRate { get; set; } 9600; public int ReconnectInterval { get; set; } 5000; }然后在appsettings.json里绑定相应配置节。这个类在工控类项目里特别实用比如扫码枪串口参数、Socket 服务器端口、采集卡采样间隔全部收敛成一个强类型配置类比散落的魔法数字好维护多了。这四类是项目中最常见、也最应该在一开始就区分清楚的角色。可以做个简单对照类后缀状态典型成员使用场景Entity / DTO / ViewModel有属性几乎无行为属性、只读计算属性数据和界面传值Service / Manager / Handler可能有依赖方法、业务逻辑业务流程、规则处理Helper / Utility / Extension无状态静态方法通用功能、格式化Options / Config / Settings有属性映射配置属性配置绑定、参数传递2. 按定义约束给类分类面试第一关全靠它如果说按职责分类是“这个类干什么”那按定义约束分类回答的就是“这个类能不能被继承、能不能被实例化、能不能被拆分”。这部分是 C# 面试题里最容易出现的点也是很多初学者容易混的地方。2.1 普通类默认形态其他类的比较基准没有加任何关键字修饰的类就是普通类。它能被实例化、能被继承、能实现接口、能包含字段属性方法事件等一切成员。普通类是 C# 类的“默认值”所有其他形态都是在普通类的基础上加了约束或扩展。public class NormalClass { public string Name { get; set; } public void DoSomething() { } }普通类虽然简单设计时仍要注意能不被继承就不要刻意制造继承层级能不写空构造就不留无参构造。这些习惯比“会用 abstract 和 sealed”更难养成。2.2 抽象类为继承而生的“半成品”抽象类用abstract修饰。它不能被实例化只能被继承它可以包含抽象方法子类必须实现和普通方法子类可以复用。抽象类适合表达“具有共同特征但实现不同”的事物。public abstract class DeviceSource { public string DeviceName { get; set; } string.Empty; public abstract void Connect(); public abstract void Disconnect(); public void Log(string message) { Console.WriteLine(${DateTime.Now:HH:mm:ss} [{DeviceName}] {message}); } } public class SerialDeviceSource : DeviceSource { public override void Connect() { /* 串口连接 */ } public override void Disconnect() { /* 关闭串口 */ } }这里Log是通用能力放在抽象类里供所有子类共用Connect和Disconnect在子类里有完全不同的实现就定义成抽象方法。这种“共性上提、个性下发”的设计是抽象类最核心的价值。面试常考“抽象类和接口的区别”。我的理解是抽象类是“一个什么类型的对象”强调的是 is-a 关系比如扫码枪是一个设备源接口是“一个对象能做什么”强调的是 can-do 能力比如能连接、能断开。一个类只能继承一个抽象类但可以实现多个接口。现实中两者经常配合用抽象类提供公共基础实现接口对外声明能力契约。实操心得不要为了复用几个方法就强行搞继承。如果你发现子类里有一大半方法都是重写后才不一样的抽象类带来的复用价值就很低了这时候组合或接口往往更合适。2.3 密封类设计小气一点反而更安全密封类用sealed修饰表示不能被继承。很多人觉得不让继承是一种限制但从设计角度看密封类是在告诉调用者“我的实现已经定稿不许你乱改。”public sealed class SerialPortManager { // 具体实现 }密封类对性能还有一点隐性的好处。JIT 编译器看到无法被继承的类时可以把虚方法调用优化成直接调用减少一次间接寻址。在高频采集、UI 刷新的场景里这一点点性能收益虽然不大但聊胜于无。框架设计里System.String就是密封的你去看看它为什么这么做就能理解密封类存在的意义了。2.4 静态类全局唯一工具类首选静态类用static修饰所有成员默认也是静态的。它不能被实例化也不能被继承本质上是一个“带访问控制的全局函数集合”。前面说的工具类就是静态类的典型应用。public static class LocalCache { private static readonly ConcurrentDictionarystring, object _items new(); public static void Set(string key, object value) { } public static object? Get(string key) { } }静态类的坑在于容易滋生“隐形全局状态”。比如静态类里放了一个可变列表Windows 窗体程序里两个线程同时读写不加锁就会出问题。用静态类前先想清楚这个类的方法是不是纯函数内部静态字段是不是线程安全的。2.5 部分类天生为“多作者协作”准备部分类用partial修饰允许一个类拆分在多个文件里。最典型的场景是 WinForms、WPF 的设计器Form1.cs放你的业务逻辑Form1.Designer.cs放设计器生成的界面代码两者合起来才是完整的一个类。// Product.partial1.cs public partial class Product { public string Name { get; set; } } // Product.partial2.cs public partial class Product { public decimal Price { get; set; } }部分类还有个实用场景是团队并行开发两个人同时改同一个类的不同文件合并冲突的概率小很多。比如一个人写属性另一个人写方法各改各的文件。不过要注意部分类不能跨程序集必须在同一个程序集内。2.6 嵌套类不想对外暴露的“内部帮手”嵌套类是定义在另一个类内部的类。它适合那些“只给外部类使用不需要暴露给外部调用者”的辅助类。比如某个服务类内部需要一个临时数据结构可以定义成私有嵌套类。public class OrderService { private class OrderItemInternal { public string Sku { get; set; } public int Count { get; set; } } }嵌套类可以直接访问外部类的私有成员这在某些场景下比“外部类 内部辅助类”传参更舒服。但嵌套类不要滥用以至于代码里一个外部类包十个内部类那样可读性会很糟糕。嵌套类主要以代码组织为目的不是功能需要。3. 进阶形态泛型、记录、不可变类现代 C# 的豪华套餐前面两类属于“经典 C#”下面要说的泛型类、记录、匿名类型、不可变类更多是 C# 语言进化过程中逐渐加入的形态。它们背后的核心思想是用语言能力帮你写出更安全、更简洁、更不容易出 bug 的代码。3.1 泛型类让代码适配“任意类型”泛型类的核心是“把一个类的类型参数化”使用时再指定具体类型。你天天用的ListT、DictionaryTKey, TValue都是泛型类。自己定义泛型类最常见的场景是封装通用的数据访问、通用的缓存、通用的结果返回。public class ApiResultT { public int Code { get; set; } public string Message { get; set; } string.Empty; public T? Data { get; set; } }泛型约束where T : class, new()用来限制类型参数可以做什么。class表示必须是引用类型new()表示必须有无参构造函数。加了约束编译器才能在泛型类里放心new T()否则编译器会认为类型参数可能不允许实例化直接报错。约束不是随便加的它是在“通用性”和“安全性”之间找平衡。3.2 记录类record比类更省心的不可变数据风C# 9 引入了record它看起来是一个类但默认具备值相等性也就是说两个 record 只要属性值相等用比较就是相等的。普通类比较的是引用record 默认按属性值比较这一点对 DTO、查询结果、接口返回值特别友好。public record DeviceInfo(int DeviceId, string DeviceName, bool IsOnline);record 默认是不可变的配合with表达式可以很方便地“拷贝一个新的但改某个值”var d1 new DeviceInfo(1, 扫码枪1, true); var d2 d1 with { IsOnline false };record 还有一个面向对象的好处可以继承。定义基类 record 时用abstract record也可以。现代 .NET 项目里我推荐在 DTO、事件参数、命令对象这些场景优先使用 record它比 class 写起来短语义也更准确——它们本来就是用来“传数据”的。注意record 并不是适合所有场景比如需要大量修改、可变状态明显的业务对象还是应该用传统的 class。3.3 匿名类型局部使用不想单独定义类的时候匿名类型用new { }创建不需要提前声明类名。它大多是配合 LINQ 投影使用从对象里挑几个字段出来。var summary products .Where(p p.Stock 0) .Select(p new { p.Name, p.Price });匿名类型只能在局部使用不能作为方法返回值对外暴露除非用 dynamic 或对象转型但不推荐。它适合“临时拼一下数据不值得单独写一个类”的场景。如果同一个匿名类型要被多个方法共用就应该升格为一个正式类。3.4 不可变类线程安全的最简单解法不可变类指创建之后内部状态就不再改变。C# 里有几种实现路径readonly字段 构造参数初始化、init属性访问器.NET 5、或者直接用 record。不可变对象天然线程安全因为对象创建完成后就没人能改它了多个线程同时读不需要加锁。比如配置类、路由信息、扫码枪的扫码结果这类“一旦生成就不会变”的数据设计成不可变类并在并发环境下会非常省心。我踩过的一个坑是在采集线程里复用同一个对象反复改属性然后在 UI 线程读取结果界面显示的数据时不时是“半新半旧”的状态。改成不可变类后这个幽灵问题直接消失。3.5 静态抽象接口成员面向接口的泛型算法.NET 7 起接口里可以声明static abstract成员配合泛型类能实现“让泛型算法调用类型上的静态方法”。设计意图是让数值类型、字符串类型都能参与到泛型计算中public interface ICalculableTSelf where TSelf : ICalculableTSelf { static abstract TSelf Add(TSelf left, TSelf right); }这套机制在 C# 里的应用场景主要是数学运算、序列化、工厂方法这类需要“多态地调用静态成员”的地方。普通业务项目用得少但如果你在做框架、算法库值得了解。它属于进阶中的进阶面试能讲到这一层会很加分。4. 按生命周期与实例化方式分类救命于依赖注入时代写桌面程序和 ASP.NET Core 的差别之一就是“类由谁来创建”变得格外重要。类是每次用都 new 一个还是全局只有一份还是每个请求各自一份这就是生命周期的问题。搞不清楚的话最常见的症状就是“扫个码事件给我注册了十次”“采集线程和 UI 线程状态不同步”“内存一直涨”。4.1 单例类全局只有一份但要防并发单例类的特点是整个进程中只有一个实例。经典写法是双重检查锁但现在基本都被依赖注入容器接管了。在 ASP.NET Core 或一些支持 DI 的桌面框架里注册成单例services.AddSingletonILoggerService, FileLoggerService();单例适合无状态的服务或者状态本身就需要全局共享的对象比如日志服务、配置服务、设备连接管理器。但单例一旦有了可变状态就要小心并发多个线程同时读写同一个字段不加锁或不用并发集合轻则数据错乱重则直接崩溃。我在设备通信项目里就遇到过一次问题ScannerManager注册成单例里面的SerialPort实例被两个窗口共用一个窗口操作串口另一个窗口也在操作串口结果重复打开串口直接抛异常。后来加了一个状态机和锁才稳住。单例不是不能有状态而是所有访问这个状态的路径都必须是线程安全的。4.2 瞬时类和作用域类瞬时类是每次请求实例都会 new 一个用完即弃。它适合“本身很轻、无状态、创建成本低”的服务。作用域类则是在一个作用域一般是一个请求、一个窗口、一个业务操作单元内共享同一个实例最典型的是 EF Core 的DbContext注册为Scoped可以在同一个业务请求里让多个仓储共享同一个数据库上下文事务一致性就容易保证了。一般的规约是注册方式实例数量典型对象AddSingleton全程一个日志、配置、设备管理器AddScoped一个作用域一个DbContext、UnitOfWork、一个请求内的一致性服务AddTransient每次解析一个新实例轻量无状态服务、选项对象这三种方式直接影响业务逻辑类怎么设计。比如LoginService如果注册成 Singleton 但它内部依赖一个 Transient 的PasswordEncoder容器会直接把 Transient 对象提升为 Singleton 的依赖等于它的生命周期被“捕获”了。这会让短期对象长期存活内存泄漏的源头之一。4.3 工厂类不想在业务代码里 new 出依赖工厂类是专门负责“创建对象”的类。它解决的核心问题是调用方不该知道对象的具体创建细节。比如你有两种扫码枪串口扫码枪和网络扫码枪具体用哪个取决于配置。public interface IScannerFactory { IScanner Create(); } public class ScannerFactory : IScannerFactory { private readonly DeviceOptions _options; public ScannerFactory(IOptionsDeviceOptions options) { _options options.Value; } public IScanner Create() { return _options.ComPort.StartsWith(COM, StringComparison.OrdinalIgnoreCase) ? new SerialScanner(_options.ComPort) : new NetworkScanner(_options.Ip, _options.Port); } }工厂类的使用在通信框架里尤其重要因为通信对象的生命周期往往和业务对象不一致连接要长期保存、断线要重连、不同的连接对应不同的通道。把“怎么创建一个正确的连接”交给工厂业务逻辑保持干净后续增加新的设备类型只需要在工厂里多返回一个实现类即可。4.4 建造者类Builder处理复杂创建过程建造者类和工厂类容易混区别在于复杂度。工厂通常是“一行决定返回哪个类”建造者通常用于“一个对象有很多可选参数构造流转复杂”。C# 里最经典的是StringBuilder它不是简单的一次性构造而是通过不断 Append 逐步构建最终结果。自定义建造者往往是链式调用每一步都返回thispublic class DeviceCommandBuilder { private readonly StringBuilder _sb new(); public DeviceCommandBuilder AddHeader() { _sb.Append(STX); return this; } public DeviceCommandBuilder AddLength(int length) { _sb.Append(length.ToString(X2)); return this; } public string Build() { _sb.Append(ETX); return _sb.ToString(); } }这种设计适合组装帧协议、拼接复杂请求报文等场景。把过程封装在建造者里业务代码就不用没完没了地写字符串拼接也不会把报文结构漏掉。5. 实战场景拆解上位机开发中的类是怎么搭起来的热词里出现了“扫码枪触发事件”“C#上位机”“Socket”“循环数据采集和UI刷新卡顿”“后台处理 Excel”这几个都是 C# 开发里非常典型的落地场景。我们拿这些场景练练手看看前面那些类分类的知识到底是怎么融合在一起的。5.1 扫码枪触发事件事件与委托承载体扫码枪在工控场景里通常是串口或 USB 输入模式每次扫码完毕会往串口发一段字符串。我们自然希望这段数据被封装成一个类以便承载扫码结果并触发事件。public sealed class ScannerDataReceivedEventArgs : EventArgs { public string Barcode { get; } public DateTime ReceiveTime { get; } public ScannerDataReceivedEventArgs(string barcode) { Barcode barcode; ReceiveTime DateTime.Now; } } public class SerialScanner { private readonly SerialPort _port; public event EventHandlerScannerDataReceivedEventArgs? DataReceived; public SerialScanner(string comPort, int baudRate) { _port new SerialPort(comPort, baudRate); _port.DataReceived OnDataReceived; } private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { var data _port.ReadExisting(); DataReceived?.Invoke(this, new ScannerDataReceivedEventArgs(data)); } }这里ScannerDataReceivedEventArgs就是一个不可变的实体类专门搬运事件数据SerialScanner是带状态的业务逻辑类持有串口资源。C# 事件机制相当适合扫码枪这类“外部输入驱动”的场景并且可以让你把扫码逻辑和业务处理逻辑解耦。在 WinForms 里要注意串口线程收到数据后触发事件事件处理函数如果要更新界面必须通过BeginInvoke或Invoke切换回 UI 线程否则跨线程操作控件会直接抛异常。常见坑事件订阅后没退订。窗口关闭了但事件处理器还挂在 Scanner 上结果 Scanner 一直引用窗口窗口内存无法释放这就是所谓的“事件导致内存泄漏”。在窗口Closed事件里退订或者将 Scanner 注册成 Transient 并跟随窗口释放能绕开这个问题。5.2 Socket 客户端的类设计连接管理是关键热词里有人搜“C# Socket”“C# TCP连接数量多少”这说明大家在做网络通信时都会遇到如何封装连接的问题。直接用裸TcpClient写业务逻辑很容易失控所以一般会封装一层public class TcpConnection { private readonly TcpClient _client; private readonly NetworkStream _stream; private readonly CancellationTokenSource _cts new(); public TcpConnection(string ip, int port) { _client new TcpClient(); _client.Connect(ip, port); _stream _client.GetStream(); } public async Task SendAsync(byte[] data, CancellationToken token default) { await _stream.WriteAsync(data, token); } public async Taskint ReadAsync(byte[] buffer, CancellationToken token default) { return await _stream.ReadAsync(buffer, token); } public void Close() { _cts.Cancel(); _stream.Close(); _client.Close(); } }这里最关键的是要定义一个“连接状态”类来管理每条连接的通断、心跳、接收缓冲区多个连接并存时常用ConcurrentDictionarystring, TcpConnection来保存。很多新人以为 C# 写 TCP 连接越多越好其实操作系统对端口数量、线程资源都有上限而且太多连接没有业务意义反而造成资源浪费。实际项目中更重要的是每个连接内部要做好断线重连和心跳保活而不是盲目开连接。5.3 循环数据采集与 UI 刷新卡顿生产者消费者类架构搜索词“C# 循环数据采集和UI刷新卡顿”是上位机开发里的经典痛点。卡顿的根本原因通常是在 UI 线程里做采集和数据处理后台线程直接更新 UI 控件没通过同步机制更新 UI 频率太高积压大量消息。解决办法是“生产者消费者”模式。采集线程负责读硬件数据生产出数据对象UI 线程只负责消费这些数据并刷新界面。中间用线程安全的队列或 Channel 传递数据。public class DataCollector { private readonly Channelint _channel; public DataCollector() { _channel Channel.CreateUnboundedint(); } public async Task ProduceAsync(CancellationToken token) { while (!token.IsCancellationRequested) { var value ReadDevice(); await _channel.Writer.WriteAsync(value, token); await Task.Delay(10, token); } } public async Task ConsumeAsync(Actionint updateUi, CancellationToken token) { await foreach (var value in _channel.Reader.ReadAllAsync(token)) { // 在 UI 线程上调用 updateUi(value); } } private int ReadDevice() { // 模拟读硬件 return Random.Shared.Next(0, 1000); } }这个架构里最关键的一点是Producer 和 Consumer 是两个不同的类角色中间传递的数据最好设计为不可变对象或 record。这样采集线程修改数据不会影响 UI 线程读取避免锁竞争。UI 刷新频率也不要跟着采集频率走采集可能 100ms 一次但界面每秒刷新 10 次就够了多出来的数据合并显示界面就不会一卡一卡。5.4 后台处理 Excel用业务服务类隔离文件操作热词里有“C# 后台处理前端传过来的Excel”这也是后台管理系统常见的需求。不能把 Excel 解析逻辑直接写在 Controller 或按钮事件里应该单独抽出业务逻辑类。public class ExcelImportService { private readonly IProductRepository _productRepository; public ExcelImportService(IProductRepository productRepository) { _productRepository productRepository; } public async TaskImportResult ImportAsync(Stream fileStream) { // 1. 解析 Excel 为 ListProductImportRow // 2. 校验数据 // 3. 批量入库 } }这里的ExcelImportService是业务逻辑类ProductImportRow是实体类导入结果ImportResult也是实体类。整个链条类与类的协作非常清晰。后台处理 Excel 时要注意大文件的流式读取不要一次性 Load 到内存里否则内存会爆炸。业务类把文件细节隔离在内部调用方只需要传入Stream既方便单元测试也方便以后替换成 CSV 解析。6. 常见坑位与面试高频题踩坑清单和速记最后这部分我整理一些实战中反复遇到的坑也顺带回答几个高频面试题。这些经验写在文档里的不多但几乎每个 C# 项目都会碰上。6.1 类的设计“反模式”避坑万能上帝类一个类里既有数据属性又有业务方法还有 UI 逻辑维护成本极高。拆开不丢人。滥用静态类存全局可变状态静态字段一旦变成可变集合就等于你自己制造了一个“隐藏单例”并且没有生命周期管理。能注入就用注入少用静态。继承层级太深为了复用一段代码搞出BaseService - BaseBaseService - BaseBaseBaseService改一个基类方法所有子类行为都可能变。组合优于继承这句话在 C# 里同样适用。到处 new 依赖业务逻辑类里到处都是new SqlConnection()、new FileStream()测试没法写。让依赖从构造函数进来类与类之间通过接口协作。事件忘记退订长生命周期的对象持有短生命周期对象的引用GC 永远回收不了短生命周期对象。在窗口关闭或服务销毁时把事件处理器退订掉。6.2 面试高频题速答抽象类和接口的区别是什么抽象类是 is-a 关系接口是 can-do 能力一个类只能继承一个抽象类但能实现多个接口抽象类可以有字段和构造器接口现代 C# 里虽然有默认实现更偏契约。静态类能不能被继承为什么不能。静态类在编译后是密封的并且只有静态成员没有实例构造函数。它被设计为不可以实例化的函数集合。sealed 修饰的类有什么好处阻止继承保护设计意图让 JIT 可以做去虚拟化优化有微小性能收益避免某个类被无意识继承后产生错误行为。partial class 有什么实际用途将一个类的实现分散到多个文件常用于设计器生成的代码和手写业务逻辑分离也用于多人协作减少文件冲突。泛型约束有什么作用约束类型参数必须具备的能力比如引用类型、值类型、有无参构造、实现某接口可以让编译器在泛型类内部安全地使用这些能力。record 和 class 的差别在哪里record 默认值相等性、默认不可变有with表达式、容易实现 DTO 语义class 默认引用相等、可变适合复杂业务实体。两者可以相互转换使用但语义差别要清楚。6.3 类库规划建议一个中大型项目我的习惯是先把命名空间按职责拆开Entities实体类Services业务逻辑类Utilities工具类Options配置类Models画面前端传给后端的 DTODataAccess仓储和数据库相关类。命名空间与文件夹结构对应类名与功能对应后缀必须统一。这样进到一个新项目看目录就能猜出类大概是什么样子看名字后缀就能知道它的角色。C# 类分类看起来像理论实际上是在教你“怎么命名、怎么放、怎么约束、怎么让别人一眼看明白”。这个分类体系是我自己从实践中摸出来的。以前我也把 C# 的类理解成“一个放方法和属性的容器”后来做上位机项目通信层、业务层、界面层混在一起一个类里千行代码改一个功能碰三处地方才意识到类分类的意义不是“学术标签”而是提醒自己每个类都应该有清晰的身份要么是数据要么是流程要么是能力要么是约束。下次新建一个类之前先问自己三个问题它表示什么数据谁负责创建它它允许被继承吗把这三个问题想清楚你写的类大概率不会太差。
返回列表