资讯动态

C#面向对象编程:类(Class)核心概念与实战技巧

发布时间:2026/10/14 0:23:29 来源:尧图企业网站定制
1. 类Class基础概念解析在C#编程语言中类Class是面向对象编程OOP的核心构建块。它就像是一个自定义的数据类型蓝图定义了对象的结构和行为。我刚开始学习C#时对类的理解就是把数据和操作数据的方法打包在一起的容器。一个典型的类包含以下核心组成部分字段Fields存储数据的变量属性Properties控制对字段的访问方法Methods定义类的行为构造函数Constructors初始化对象的特殊方法事件Events用于通知其他对象的机制// 最简单的类定义示例 public class Person { // 字段 private string name; // 属性 public string Name { get { return name; } set { name value; } } // 方法 public void Greet() { Console.WriteLine($Hello, my name is {Name}); } // 构造函数 public Person(string name) { this.name name; } }提示在Visual Studio中输入prop然后按Tab键可以快速生成属性代码片段这是提高编码效率的小技巧。2. 类的核心特性深入探讨2.1 访问修饰符的实战应用访问控制是类的关键特性之一C#提供了以下几种访问修饰符修饰符可访问范围使用场景public无限制需要被其他类广泛使用的成员private仅限类内部实现细节不希望外部访问protected类及其派生类为继承设计的成员internal同一程序集组件内部使用的功能protected internal程序集或派生类特殊框架设计需求在实际项目中我遵循最小权限原则默认使用private只有确实需要暴露的成员才提升访问级别。这样可以减少意外的外部依赖提高代码的健壮性。2.2 属性和字段的选择策略初学者常常困惑于何时使用字段何时使用属性。我的经验法则是永远使用属性暴露数据即使是最简单的get/set仅在类内部使用私有字段存储实际数据需要计算或验证时使用属性// 不好的实践直接暴露字段 public string Name; // 好的实践使用属性 private string name; public string Name { get name; set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(Name cannot be empty); name value; } }注意从C# 3.0开始可以使用自动实现的属性简化代码public string Name { get; set; }但在需要添加逻辑时可以随时扩展而不破坏现有代码。3. 类的高级特性实战3.1 构造函数的最佳实践构造函数是类初始化的关键点我总结了几个实用技巧提供最少参数的构造函数保持简洁使用可选参数或方法链来支持多种初始化方式对必要参数进行验证public class Order { public int Id { get; } public DateTime Date { get; } public decimal Amount { get; } // 主构造函数 public Order(int id, DateTime date, decimal amount) { if (id 0) throw new ArgumentException(ID must be positive); if (amount 0) throw new ArgumentException(Amount cannot be negative); Id id; Date date; Amount amount; } // 便捷构造函数 public Order(int id) : this(id, DateTime.Now, 0) {} }3.2 静态类与静态成员静态类static class是不能实例化的类常用于工具类或扩展方法。静态成员属于类本身而非实例。使用场景数学计算工具如Math类全局配置访问工厂方法public static class StringHelper { public static bool IsValidEmail(string email) { return Regex.IsMatch(email, ^[^\s][^\s]\.[^\s]$); } } // 使用方式 bool isValid StringHelper.IsValidEmail(testexample.com);警告过度使用静态成员会导致代码难以测试和维护特别是在需要模拟依赖的情况下。仅在确实需要全局访问且无状态时使用。4. 面向对象原则在类设计中的应用4.1 封装的艺术封装是OOP的三大支柱之一另外两个是继承和多态。好的封装应该隐藏实现细节暴露最小必要接口确保对象始终处于有效状态我在项目中常用的封装技巧使用readonly字段防止意外修改将集合属性暴露为IEnumerable 而非List使用工厂方法控制对象创建public class BankAccount { private readonly string accountNumber; private decimal balance; public string AccountNumber accountNumber; public decimal Balance balance; private BankAccount(string accountNumber) { this.accountNumber accountNumber; } public static BankAccount CreateNew(string accountNumber) { if (!IsValidAccountNumber(accountNumber)) throw new ArgumentException(Invalid account number); return new BankAccount(accountNumber); } public void Deposit(decimal amount) { if (amount 0) throw new ArgumentException(Amount must be positive); balance amount; } // 其他方法... }4.2 继承与组合的选择很多初学者过度使用继承实际上组合优于继承是更现代的设计理念。我的选择标准使用继承当确实是is-a关系需要多态行为基类稳定且不会频繁变更使用组合当只是has-a关系需要灵活替换组件避免继承层次过深// 继承示例 public class Animal { /* ... */ } public class Dog : Animal { /* ... */ } // 组合示例 public class Car { private Engine engine; private ListWheel wheels; public Car(Engine engine, IEnumerableWheel wheels) { this.engine engine; this.wheels wheels.ToList(); } }5. 实际项目中的类设计经验5.1 领域模型类的设计模式在业务系统开发中我常用以下几种模式设计类贫血模型 vs 富模型贫血模型只有数据没有行为常见于早期Java/C#项目富模型数据和行为在一起更符合OO理念值对象模式不可变通过相等性而非标识比较如Money、Address等类型实体模式有唯一标识可变状态如User、Order等// 值对象示例 public class Address : IEquatableAddress { public string Street { get; } public string City { get; } public string ZipCode { get; } public Address(string street, string city, string zipCode) { Street street; City city; ZipCode zipCode; } public bool Equals(Address other) Street other.Street City other.City ZipCode other.ZipCode; // 其他相等性实现... } // 实体示例 public class Customer { public Guid Id { get; } public string Name { get; private set; } public Address ShippingAddress { get; private set; } public Customer(string name, Address shippingAddress) { Id Guid.NewGuid(); Name name; ShippingAddress shippingAddress; } public void UpdateShippingAddress(Address newAddress) { ShippingAddress newAddress ?? throw new ArgumentNullException(nameof(newAddress)); } }5.2 类的大小与单一职责一个常见的误区是创建上帝类包含太多功能的超大类。我遵循以下原则控制类的大小单一职责原则SRP一个类只做一件事如果类的方法超过15-20个考虑拆分如果类的代码超过300-500行考虑重构判断方法能否用一句话清晰描述这个类的职责如果不能说明职责过多。6. C#类的最新语言特性6.1 记录类型RecordC# 9.0引入了记录类型特别适合不可变数据模型public record Person(string FirstName, string LastName); // 使用 var person1 new Person(John, Doe); var person2 person1 with { LastName Smith }; // 非破坏性修改记录类型自动提供值相等性比较基于属性的ToString()With表达式支持解构功能6.2 主构造函数Primary ConstructorsC# 12进一步简化了类定义public class Student(string name, int age) { public string Name { get; } name; public int Age { get; } age; public override string ToString() ${Name}, {Age} years old; }7. 常见陷阱与调试技巧7.1 对象引用问题新手常犯的错误是混淆值类型和引用类型var list1 new Listint { 1, 2, 3 }; var list2 list1; // list2和list1指向同一个对象 list2.Add(4); Console.WriteLine(list1.Count); // 输出4不是3解决方法对于引用类型需要时创建副本使用不可变集合System.Collections.Immutable实现ICloneable接口谨慎使用7.2 相等性比较的坑默认情况下类的相等性比较是基于引用的。要实现值比较需要重写Equals方法重写GetHashCode方法实现IEquatable 接口public class Product : IEquatableProduct { public int Id { get; } public string Name { get; } public Product(int id, string name) { Id id; Name name; } public override bool Equals(object obj) Equals(obj as Product); public bool Equals(Product other) other ! null Id other.Id Name other.Name; public override int GetHashCode() HashCode.Combine(Id, Name); }8. 性能优化注意事项8.1 避免过度封装虽然封装是好的但过度封装会影响性能// 不好的做法多层属性访问 public class A { public B B { get; } new B(); } public class B { public C C { get; } new C(); } public class C { public int Value { get; set; } } var value a.B.C.Value; // 每次访问都要通过多个属性优化方法对于高频访问的深层属性考虑缓存在性能关键路径上减少间接访问8.2 结构体struct的使用场景对于小型、不可变、频繁创建销毁的类型考虑使用结构体而非类public struct Point { public double X { get; } public double Y { get; } public Point(double x, double y) { X x; Y y; } }使用结构体的条件大小不超过16字节不可变不需要继承值语义更合适9. 单元测试最佳实践9.1 可测试的类设计编写易于测试的类需要注意依赖通过构造函数注入而非静态方法针对接口编程避免在构造函数中进行复杂操作public class OrderProcessor { private readonly IPaymentGateway paymentGateway; private readonly IInventoryService inventoryService; public OrderProcessor(IPaymentGateway paymentGateway, IInventoryService inventoryService) { this.paymentGateway paymentGateway; this.inventoryService inventoryService; } public bool ProcessOrder(Order order) { // 使用注入的服务处理订单 } }9.2 测试私有方法通常只测试公共接口但如果必须测试私有方法改为protected并通过子类测试使用InternalsVisibleTo特性使用反射最后选择[assembly: InternalsVisibleTo(Tests)] public class MyClass { internal int InternalCalculate() { /* ... */ } }10. 实际案例用户管理系统让我们通过一个用户管理系统的例子整合所学知识public class User : IEquatableUser { public Guid Id { get; } public string Username { get; } public string Email { get; private set; } public DateTime CreatedAt { get; } public DateTime? LastLogin { get; private set; } private readonly ListRole roles new(); public IReadOnlyListRole Roles roles.AsReadOnly(); public User(string username, string email) { if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException(Username cannot be empty); if (!EmailValidator.IsValid(email)) throw new ArgumentException(Invalid email address); Id Guid.NewGuid(); Username username; Email email; CreatedAt DateTime.UtcNow; } public void UpdateEmail(string newEmail) { if (!EmailValidator.IsValid(newEmail)) throw new ArgumentException(Invalid email address); Email newEmail; } public void RecordLogin() { LastLogin DateTime.UtcNow; } public void AddRole(Role role) { if (role null) throw new ArgumentNullException(nameof(role)); if (!roles.Contains(role)) roles.Add(role); } public bool RemoveRole(Role role) { return roles.Remove(role); } // 相等性实现... } public record Role(string Name, string Description);这个示例展示了封装业务规则用户名、邮箱验证控制可变性大部分属性只读通过方法修改集合属性的安全暴露记录类型的使用领域逻辑的集中管理11. 进阶主题模式匹配与类C# 7.0引入的模式匹配可以简化基于类类型的条件逻辑public abstract class Shape { /* ... */ } public class Circle : Shape { public double Radius { get; } /* ... */ } public class Rectangle : Shape { public double Width { get; } public double Height { get; } /* ... */ } public double CalculateArea(Shape shape) { return shape switch { Circle c Math.PI * c.Radius * c.Radius, Rectangle r r.Width * r.Height, _ throw new ArgumentException(Unknown shape type) }; }12. 类与异步编程现代C#开发中类的方法经常需要支持异步操作public class DataService { private readonly HttpClient httpClient; public DataService(HttpClient httpClient) { this.httpClient httpClient; } public async Taskstring FetchDataAsync(string url) { if (string.IsNullOrWhiteSpace(url)) throw new ArgumentException(URL cannot be empty); try { var response await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } catch (HttpRequestException ex) { throw new DataServiceException(Failed to fetch data, ex); } } }关键点异步方法命名以Async为后缀参数验证在await之前正确处理异常并转换为领域异常13. 源代码生成与类设计C#的源代码生成功能可以自动生成类代码// 使用partial类配合源代码生成器 public partial class Person { public string FirstName { get; } public string LastName { get; } public Person(string firstName, string lastName) { FirstName firstName; LastName lastName; } } // 源代码生成器生成的代码 partial class Person { public string FullName ${FirstName} {LastName}; }常见应用场景序列化/反序列化逻辑INotifyPropertyChanged实现DTO到模型的映射14. 类设计中的SOLID原则14.1 单一职责原则SRP一个类应该只有一个引起变化的原因。例如将用户存储和用户验证分离// 违反SRP public class UserService { public void AddUser(User user) { /* 保存到数据库 */ } public bool ValidatePassword(string password) { /* 密码规则验证 */ } } // 遵循SRP public class UserRepository { public void Add(User user) { /* 保存到数据库 */ } } public class PasswordValidator { public bool IsValid(string password) { /* 密码规则验证 */ } }14.2 开闭原则OCP对扩展开放对修改关闭。使用策略模式示例public interface IDiscountStrategy { decimal ApplyDiscount(decimal amount); } public class NoDiscount : IDiscountStrategy { /* ... */ } public class PercentageDiscount : IDiscountStrategy { /* ... */ } public class FixedAmountDiscount : IDiscountStrategy { /* ... */ } public class Order { private readonly IDiscountStrategy discountStrategy; public Order(IDiscountStrategy discountStrategy) { this.discountStrategy discountStrategy; } public decimal CalculateTotal(decimal subtotal) { return subtotal - discountStrategy.ApplyDiscount(subtotal); } }15. 领域驱动设计DDD中的类15.1 聚合根设计聚合根是DDD中的核心概念负责维护一组相关对象的一致性public class Order : IAggregateRoot { private readonly ListOrderItem items new(); public Guid Id { get; } public Customer Customer { get; } public IReadOnlyCollectionOrderItem Items items.AsReadOnly(); public Order(Guid id, Customer customer) { Id id; Customer customer; } public void AddItem(Product product, int quantity) { if (product null) throw new ArgumentNullException(nameof(product)); if (quantity 0) throw new ArgumentException(Quantity must be positive); var existingItem items.FirstOrDefault(i i.Product.Id product.Id); if (existingItem ! null) { existingItem.IncreaseQuantity(quantity); } else { items.Add(new OrderItem(product, quantity)); } } // 其他领域逻辑... }15.2 领域事件使用领域事件处理跨聚合的业务逻辑public class Order { // ... public void Complete() { // 订单完成逻辑... AddDomainEvent(new OrderCompletedEvent(Id, DateTime.UtcNow)); } private readonly ListIDomainEvent domainEvents new(); public IReadOnlyCollectionIDomainEvent DomainEvents domainEvents.AsReadOnly(); protected void AddDomainEvent(IDomainEvent domainEvent) { domainEvents.Add(domainEvent); } public void ClearDomainEvents() { domainEvents.Clear(); } }16. 类与依赖注入现代C#开发中类设计需要考虑依赖注入(DI)public interface ILogger { void Log(string message); } public class FileLogger : ILogger { /* ... */ } public class ProductService { private readonly ILogger logger; public ProductService(ILogger logger) { this.logger logger ?? throw new ArgumentNullException(nameof(logger)); } public void AddProduct(Product product) { try { // 添加产品逻辑 logger.Log($Product {product.Id} added); } catch (Exception ex) { logger.Log($Error adding product: {ex.Message}); throw; } } }最佳实践依赖于抽象接口而非具体实现所有依赖通过构造函数注入避免服务定位器反模式17. 类的版本控制与兼容性当类需要演进时保持向后兼容的技巧添加新方法而非修改现有方法使用过时标记[Obsolete]而非直接删除成员为重大变更引入新类而非修改现有类public class Customer { [Obsolete(Use Email property instead)] public string GetEmailAddress() Email; public string Email { get; set; } // 新功能 public IReadOnlyListAddress Addresses { get; } }18. 调试与诊断技巧18.1 重写ToString()为类重写ToString()可以极大简化调试public override string ToString() { return ${GetType().Name} [Id{Id}, Name{Name}, Email{Email}]; }18.2 使用DebuggerDisplay更精细地控制调试器中的显示[DebuggerDisplay({DebuggerDisplay,nq})] public class Order { public int Id { get; } public decimal Total { get; } private string DebuggerDisplay $Order #{Id} (Total: {Total:C}); }19. 性能敏感场景的类优化19.1 对象池模式对于频繁创建销毁的类使用对象池减少GC压力public class ObjectPoolT where T : new() { private readonly ConcurrentBagT pool new(); public T Get() { return pool.TryTake(out T item) ? item : new T(); } public void Return(T item) { pool.Add(item); } } // 使用 var pool new ObjectPoolMemoryStream(); var stream pool.Get(); try { // 使用stream... } finally { pool.Return(stream); }19.2 结构体替代小型类对于小型、短生命期的数据容器考虑使用结构体public struct Point3D { public double X; public double Y; public double Z; public Point3D(double x, double y, double z) { X x; Y y; Z z; } }20. 跨平台开发中的类设计在.NET跨平台开发中类设计需要考虑避免平台特定API使用依赖注入抽象平台功能考虑可移植类库(PCL)或.NET Standardpublic interface IFileSystem { Stream OpenRead(string path); bool FileExists(string path); } // 平台特定实现 public class WindowsFileSystem : IFileSystem { /* ... */ } public class LinuxFileSystem : IFileSystem { /* ... */ } public class DataLoader { private readonly IFileSystem fileSystem; public DataLoader(IFileSystem fileSystem) { this.fileSystem fileSystem; } public byte[] LoadData(string filePath) { if (!fileSystem.FileExists(filePath)) throw new FileNotFoundException(); using var stream fileSystem.OpenRead(filePath); using var memoryStream new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑