C#基础07-类与对象

零、文章目录

C#基础07-类与对象

1、类和对象

(1)基本定义
概念 说明 核心特性
类 (Class) 用户自定义的数据类型,描述具有相同属性和行为的实体模板 - 封装状态(字段)和行为(方法) - 支持继承、多态
对象 (Object) 类的具体实例,占用独立内存空间 - 通过 new 关键字实例化 - 每个对象拥有独立的字段值
  • 类比:
    • 类 = 汽车设计蓝图(定义颜色、型号、引擎等属性)
    • 对象 = 根据蓝图生产的实际汽车(如车牌 A123 的红色轿车)
(2)类的组成成员
  • 核心成员类型
成员 作用 示例
字段 (Field) 存储对象状态数据 private string _name;
属性 (Property) 封装字段访问逻辑(get/set) public string Name { get => _name; set => _name = value; }
方法 (Method) 定义对象行为 public void StartEngine() { ... }
构造函数 初始化对象状态 public Car(string model) { Model = model; }
事件 (Event) 实现对象间通信 public event Action EngineStarted;
索引器 使对象支持数组式访问 public string this[int index] { get; set; }
  • 特殊成员
    • 常量 (const):编译时确定值的不可变量
csharp 复制代码
public const double PI = 3.14;  // 类级别常量
复制代码
- 静态成员 (static):类级别共享(无需实例化)  
csharp 复制代码
public static int CarCount; // 统计所有实例数量
️(3)对象的生命周期
  • 实例化:使用 new 关键字分配堆内存并调用构造函数:
csharp 复制代码
Car myCar = new Car("Tesla Model 3"); 
  • 初始化:通过构造函数传递初始值
csharp 复制代码
public class Car {
    public Car(string model) => Model = model; // 构造初始化 
}
  • 销毁:依赖垃圾回收器(GC)自动管理内存,可通过析构函数释放资源:
csharp 复制代码
~Car() {
    Console.WriteLine("Car object destroyed"); 
}

2、构造函数

(1)本质与特性
  • 定义:构造函数是与类同名、无返回类型的特殊方法,用于初始化对象状态。在 new 实例化时自动调用。
csharp 复制代码
class Person {
    public string Name;
    // 构造函数 
    public Person(string name) {
        this.Name = name; // 初始化成员 
    }
}
  • 核心特性
    • 强制命名规则:必须与类名完全相同。
    • 无返回值:不能使用 void 或其他返回类型。
    • 自动调用:仅通过 new 触发,不可显式调用。
    • 隐式默认构造:未定义时编译器自动生成无参构造;若自定义任意构造,则默认构造失效。
(2)类型与使用场景
  • 实例构造函数
    • 作用:初始化实例成员变量。
    • 重载支持:参数列表不同的多个构造方法。
csharp 复制代码
class Car {
    public string Model;
    public int Year;
    // 无参构造
    public Car() { Model = "Unknown"; } 
    // 带参构造 
    public Car(string model, int year) { 
        Model = model; 
        Year = year;
    }
}
  • 静态构造函数
    • 作用:初始化静态成员,在类首次加载时执行(首次实例化或访问静态成员前)。
    • 特性:
      • static 修饰,无访问修饰符(隐式私有)。
      • 每个类仅允许一个,且无参数。
csharp 复制代码
class Logger {
    public static string LogPath;
    static Logger() {
        LogPath = @"C:\logs\app.log"; // 初始化静态变量 
    }
}
  • 私有构造函数
    • 作用:禁止类实例化,适用于工具类或单例模式。
    • 示例:
csharp 复制代码
public class Utility {
    private Utility() { } // 外部无法 new 
    public static void Print() { ... }
}
(3)调用链与继承
  • 调用同级构造:this(),复用当前类的其他构造逻辑
csharp 复制代码
class Rectangle {
    public int Width, Height;
    public Rectangle() : this(10, 5) { } // 调用带参构造 
    public Rectangle(int w, int h) {
        Width = w; Height = h;
    }
}
  • 调用基类构造:base(),子类必须通过 base 显式调用父类构造(若父类无默认构造):
csharp 复制代码
class Vehicle {
    public int Speed;
    public Vehicle(int speed) { Speed = speed; }
}
class Car : Vehicle {
    public Car(int speed) : base(speed) { } // 必须传递参数 
}
(4)关键注意事项
  • 子类构造规则
    • 未显式调用 base 时,编译器自动尝试调用父类无参构造;若无则报错。
    • 执行顺序:父类构造 → 子类构造。
  • 避免常见错误
    • 自定义带参构造后,需显式添加无参构造(否则子类可能报错)。
    • 静态构造中避免耗时操作(影响首次加载性能)。
  • 性能优化
    • 减少构造函数的重复初始化逻辑,用 this() 复用代码。
    • 值类型(如 struct)的默认构造由编译器自动生成,无需定义。
(5)应用场景示例
csharp 复制代码
// 单例模式(私有构造 + 静态实例)
public class AppConfig {
    private static AppConfig _instance;
    public static AppConfig Instance => _instance ??= new AppConfig();
    
    private AppConfig() { } // 禁止外部实例化
}
 
// 带继承链的构造调用 
class Animal {
    public string Type;
    public Animal(string type) => Type = type;
}
class Dog : Animal {
    public string Breed;
    public Dog(string breed) : base("Mammal") { 
        Breed = breed;
    }
}
(6)最佳实践
  • 工具类设计 → 私有构造
  • 全局配置初始化 → 静态构造
  • 对象复用 → 构造链 this()
  • 跨层初始化 → 显式 base()

3、析构函数

(1)本质与作用
  • 析构函数是类的特殊成员函数,用于对象销毁前执行资源清理(如关闭文件、释放非托管资源)。
  • 语法:~类名(),无参数与返回类型(如 ~MyClass() { ... })。
(2)与构造函数的对比
特性 构造函数 析构函数
执行时机 对象创建时自动调用 对象销毁时自动调用
命名 与类名相同 类名前加 ~
数量限制 可重载(多个) 仅允许一个
调用控制 可通过 new 触发 完全由垃圾回收器(GC)控制
(3)关键限制与特性
  • 使用约束
    • 仅适用于类(不适用于结构体)。
    • 不可继承或重载,且不能被显式调用。
    • 禁止添加访问修饰符(如 public)或参数。
  • 执行机制
    • 由垃圾回收器(GC)在对象不可达时自动触发,具体时机不可预测。
    • 程序退出时,所有未析构的对象会按继承链反向顺序调用析构函数(子类→父类)。
  • 示例:继承链析构顺序
csharp 复制代码
class A { ~A() => Console.WriteLine("A destroyed"); }
class B : A { ~B() => Console.WriteLine("B destroyed"); }
// 输出:B destroyed → A destroyed 
(4)适用场景与替代方案
  • 核心用途
    • 非托管资源清理(如文件句柄、网络连接),作为资源释放的保底机制。
    • 需与 IDisposable 接口配合使用,避免资源泄露。
  • 替代方案:IDisposable 模式
    • 优先通过 Dispose() 方法主动释放资源,而非依赖析构函数。
    • 典型实现:
csharp 复制代码
class Resource : IDisposable {
    private bool disposed = false;
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this); // 阻止析构函数重复调用 
    }
    protected virtual void Dispose(bool disposing) {
        if (!disposed) {
            if (disposing) { /* 释放托管资源 */ }
            /* 释放非托管资源 */
            disposed = true;
        }
    }
    ~Resource() => Dispose(false); // 析构函数兜底
}
(5)性能注意事项
  • 避免空析构函数
    • 空析构函数会导致 GC 额外开销(将对象加入终止队列),降低性能。
  • 慎用强制 GC
    • 调用 GC.Collect() 强制回收可能引发性能问题,仅在必要时使用。
(6)最佳实践
场景 推荐方案 原因
非托管资源释放 IDisposable + 析构函数兜底 确保资源安全释放
纯托管资源 无需析构函数 GC 自动管理,析构函数反而降低效率
需要确定性释放 实现 Dispose() 主动控制释放时机
资源释放逻辑复杂 析构函数仅调用 Dispose(false) 分离托管/非托管资源处理
  • 关键原则:析构函数应作为资源清理的最后防线,而非主要手段。优先使用 IDisposable 模式确保及时释放资源。
(7)进阶说明
  • Finalize() 的关系:C# 析构函数编译后会转换为 Finalize() 方法,由 GC 调用。
  • 结构体限制:结构体不支持析构函数,因其为值类型且由栈管理,无需终结。

4、字段

(1)定义与本质
  • 基本概念
    • 字段是类或结构体内部的数据存储单元,本质是成员变量。
    • 声明语法:访问修饰符 数据类型 字段名;
csharp 复制代码
private int _age;          // 私有整型字段
public string Name;        // 公有字符串字段
  • 核心特性
    • 存储位置:值类型字段存储于栈内存,引用类型字段存储堆内存地址。
    • 生命周期:与所属对象实例绑定(实例字段)或与类型共存(静态字段)。
    • 默认值:未显式初始化时,数值类型为 0,引用类型为 null
(2)字段的分类与作用
分类 修饰符 作用 示例
实例字段 存储对象状态数据 private int _id;
静态字段 static 全局共享数据,类级别存储 public static int Count;
只读字段 readonly 仅允许在声明或构造函数中赋值 private readonly string _key;
常量字段 const 编译时确定值,不可修改 public const float PI = 3.14f;
  • readonlyconst
    • const 必须在声明时赋值,且仅支持基本类型。
    • readonly 可在构造函数赋值,支持任意类型。
csharp 复制代码
public class Config {
    public const string Env = "Prod";  // 正确
    public readonly string DbPath;
    public Config() { DbPath = "C:/Data"; } // 构造函数赋值 
}
(3)关键注意事项
  • 初始化要求
    • 常量字段 (const) 必须声明时初始化。
    • 只读字段 (readonly) 必须在声明或构造函数中初始化 。
  • 访问安全
    • 避免将字段设为 public,防止外部直接篡改内部状态。
    • 静态字段需注意线程安全(如用 lock 同步)。
  • 性能优化
    • 频繁访问的字段可标记为 readonly 减少意外修改风险 。
    • 大型对象避免过多实例字段,防止内存碎片。
(4)典型应用场景
csharp 复制代码
// 场景1:存储对象状态
public class Person {
    private int _age;          // 私有字段
    public string Name { get; } // 只读属性(通过构造函数赋值)
    public Person(string name) => Name = name;
}
 
// 场景2:全局配置(静态只读字段)
public static class AppConfig {
    public static readonly string ConnectionString = "Server=...";
}
 
// 场景3:枚举替代方案(常量字段分组)
public class ErrorCodes {
    public const int NotFound = 404;
    public const int Forbidden = 403;
}
(5)开发建议
  • 优先使用属性封装字段,增强代码健壮性。
  • 对敏感数据(如密钥)使用 readonly + 私有字段组合。

5、属性

(1)基本概念与作用
  • 属性(Property) 是类中封装字段的成员,提供对私有字段的安全访问机制。核心作用:
    • 封装性:隐藏字段实现细节,仅暴露安全访问接口。
    • 数据验证:在 set 访问器中添加逻辑(如非空检查、范围验证)。
    • 计算属性:动态生成值(如全名 = 名 + 姓)。
  • 示例:基础属性定义
csharp 复制代码
public class Person  
{  
    private string _name;  // 私有字段  
    public string Name     // 属性  
    {  
        get { return _name; }  
        set {  
            if (string.IsNullOrWhiteSpace(value))  
                throw new ArgumentException("姓名不能为空");  
            _name = value;  
        }  
    }  
}  
(2)分类与语法
  • 读写属性
    • 同时包含 get(读取)和 set(写入)访问器。
    • 自动属性:编译器自动生成私有字段(简化代码):
csharp 复制代码
public string Name { get; set; } = "Unknown";  // 默认值初始化  
  • 只读属性:仅含 get 访问器,初始化后不可修改:
csharp 复制代码
public int Age { get; } = 18;  // 构造函数或初始化器赋值  
  • 访问器权限控制:为 get/set 单独设置访问级别:
csharp 复制代码
public string Id { get; private set; }  // 外部只读,类内可写  
  • 高级特性:
    • init 访问器(C# 9+):仅对象初始化时可赋值:
csharp 复制代码
public required string Email { get; init; }  // 必需属性  
复制代码
- 表达式体属性:单行逻辑简化:  
csharp 复制代码
public string FullName => $"{FirstName} {LastName}";  
(3)关键注意事项
  • 性能优化:
    • 避免在属性访问器中执行耗时操作(如数据库查询)。
    • 优先使用自动属性,除非需额外逻辑。
  • 数据绑定支持:
    • 实现 INotifyPropertyChanged 接口,通知界面更新:
csharp 复制代码
public event PropertyChangedEventHandler? PropertyChanged;  
public string Name  
{  
    set {  
        _name = value;  
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));  
    }  
}  
(4)与字段的区别
特性 字段(Field) 属性(Property)
数据验证 不支持 支持(通过 set 访问器)
计算逻辑 不支持 支持(动态返回值)
访问控制 仅通过访问修饰符 可单独控制 get/set 权限
接口实现 不可用于接口 可用于接口定义
  • 封装原则:字段通常设为 private,通过属性对外提供受控访问:
csharp 复制代码
private string _name;  // 私有字段
public string Name {   // 封装属性
    get => _name;
    set {
        if (!string.IsNullOrEmpty(value)) 
            _name = value;
    }
}
(5)实际应用场景
  • 数据验证
csharp 复制代码
private int _age;  
public int Age  
{  
    get => _age;  
    set => _age = value >= 0 ? value : throw new ArgumentException("年龄不能为负");  
}  
  • 延迟加载
csharp 复制代码
private List<string>? _data;  
public List<string> Data  
{  
    get => _data ??= LoadData();  // 首次访问时加载  
}  
  • 兼容旧代码
csharp 复制代码
// 旧字段升级为属性(保持API兼容)  
public string LegacyField { get; set; }  // 替换原 public 字段  
(6)最佳实践总结
  • 优先使用属性而非公共字段:确保封装性和扩展性。
  • 为必需属性添加 required 修饰符:强制调用方初始化(C# 11+)。
  • 避免过度复杂逻辑:保持属性简洁,复杂操作移至方法中。
  • 单元测试验证:确保 set 验证逻辑和 get 计算正确性。

6、索引器

(1)核心概念
  • 本质与作用
    • 索引器是特殊属性,允许类或结构体的实例像数组一样通过索引访问元素。
    • 核心价值:简化集合类操作,提供直观的数据访问语法(如 obj[index])。
  • 基本语法结构
csharp 复制代码
public 返回值类型 this[参数类型 参数名] 
{
    get { /* 返回索引对应的值 */ }
    set { /* 设置索引对应的值(value关键字) */ }
}
(2)实现步骤与示例
  • 基础实现(封装数组)
    • 索引器使用 this 关键字定义。
    • get/set 访问器控制读写逻辑(可只实现其一)。
csharp 复制代码
public class IntCollection 
{
    private int[] _data = new int[10];
 
    public int this[int index] 
    {
        get => _data[index]; 
        set => _data[index] = value; 
    }
}
 
// 使用示例
var collection = new IntCollection();
collection[0] = 100;  // 调用set 
Console.WriteLine(collection[0]); // 输出100(调用get)
  • 多维索引器(如矩阵):支持多参数(如 row, col)实现多维访问
csharp 复制代码
public class Matrix 
{
    private int[,] _grid = new int[3, 3];
    
    public int this[int row, int col] 
    {
        get => _grid[row, col];
        set => _grid[row, col] = value;
    }
}
 
// 使用示例 
var matrix = new Matrix();
matrix[1, 2] = 5; // 设置第2行第3列的值 
  • 非整数索引(如字典式访问):索引参数可为任意类型(字符串、枚举等)
csharp 复制代码
public class ConfigLoader 
{
    private Dictionary<string, string> _configs = new();
    
    public string this[string key] 
    {
        get => _configs[key];
        set => _configs[key] = value;
    }
}
 
// 使用示例 
var config = new ConfigLoader();
config["Timeout"] = "30"; // 字符串作为索引 
(3)关键特性与限制
  • 重载支持 :同一类中可定义多个索引器(参数类型或数量不同)
csharp 复制代码
public string this[int id] { ... }  
public string this[string name] { ... } // 重载
  • 使用限制
    • 必须是实例成员(不能声明为 static)。
    • 无法定义可选参数或使用 params 关键字。
    • 访问性需与类一致(如公共类的索引器必须为 public)。
  • 异常处理 :需手动检查索引有效性(如防止数组越界):
csharp 复制代码
get 
{
    if (index < 0 || index >= _data.Length) 
        throw new IndexOutOfRangeException();
    return _data[index];
}
(4)典型应用场景
  • 自定义集合类:封装列表、字典等,提供类似原生数组的访问体验。
  • 封装复杂数据结构:如矩阵、树形结构,通过索引简化节点操作。
  • 配置文件/资源访问:通过字符串索引快速读写配置项。
(5)索引器 vs. 属性
特性 索引器 属性
访问语法 obj[index] obj.PropertyName
参数支持 必须带参数 无参数
重载能力 支持多参数重载 不支持重载
命名 固定为 this 自定义名称
  • 索引器本质是带参数的属性,两者均依赖 get/set 访问器。
(6)最佳实践与陷阱规避
  • 优先场景:需类支持集合式访问时使用(如自定义容器)。
  • 避免滥用:非集合类不建议使用索引器,避免逻辑混淆。
  • 性能优化:在 get/set 中避免复杂计算(如数据库查询)。

7、成员重载

(1)定义与核心规则
  • 定义:在 同一作用域内(如类、结构体),声明多个 同名方法/构造函数/运算符,但参数列表(参数类型、数量或顺序)不同。
  • 参数差异:必须通过参数区分(类型、数量或顺序),仅返回值不同不构成重载(编译错误)。
csharp 复制代码
// 合法重载 
void Print(int a) { }
void Print(string a) { }  // 参数类型不同
void Print(int a, double b) { }  // 参数数量不同

// 非法重载(返回值不同)
int GetValue() { return 0; }
string GetValue() { return ""; } // 编译报错
  • 作用域限制:重载必须在同一类或派生类中(派生类需用 new 关键字隐藏基类方法)。
(2)方法重载
  • 参数列表必须不同(满足以下任一):
    • 参数类型不同:void Print(int x)void Print(string x)
    • 参数数量不同:void Print(int a)void Print(int a, int b)
    • 参数顺序不同:void Print(int a, string b)void Print(string a, int b)
  • 返回值类型和参数名不影响重载:
    • int GetValue()string GetValue() 不合法(编译错误)
    • void Process(int num)void Process(int count) 不合法(参数名不同无效)
  • 示例:
csharp 复制代码
class Calculator {
    // 参数数量不同
    public int Add(int a, int b) => a + b;
    public int Add(int a, int b, int c) => a + b + c; 
 
    // 参数类型不同 
    public double Add(double a, double b) => a + b;
}
(3)构造函数重载
  • 作用:提供多种对象初始化方式。
  • 规则:与方法重载一致,通过 this 关键字复用代码。
csharp 复制代码
class Person {
    public string Name;
    public int Age;
 
    // 无参构造 
    public Person() : this("Unknown", 0) {} 
 
    // 全参构造 
    public Person(string name, int age) {
        Name = name;
        Age = age;
    }
}
(4)运算符重载
  • 规则:
    • 使用 operator 关键字定义静态方法(如 +, ==)。
    • 至少一个参数为当前类类型。
  • 示例(重载 + 运算符):
csharp 复制代码
class Vector {
    public int X, Y;
    public Vector(int x, int y) { X = x; Y = y; }
 
    public static Vector operator +(Vector v1, Vector v2) {
        return new Vector(v1.X + v2.X, v1.Y + v2.Y);
    }
}
// 使用:Vector v3 = v1 + v2;
(5)设计规范与注意事项
  • 避免歧义:确保参数差异足够明确,避免因隐式转换导致调用歧义:
csharp 复制代码
void Process(float num) { }
void Process(double num) { }
Process(10); // 编译错误:int 可隐式转 float 或 double,无法确定调用哪个
  • 优先使用泛型:若重载仅因类型不同(如 Add(int)Add(double)),改用泛型方法更简洁:
csharp 复制代码
public T Add<T>(T a, T b) where T : INumber<T> => a + b; // .NET 7+ 支持 
  • 慎用 params 参数:params 数组参数可能掩盖重载冲突,需测试边界情况:
csharp 复制代码
void Log(string format) { }
void Log(params object[] args) { }
Log("test"); // 优先调用第一个重载
  • 慎用运算符重载:确保语义符合直觉(如 + 不应执行减法)。
(6)重载决策机制
  • 精确匹配 > 隐式转换 > 参数数组展开
  • 示例分析:
csharp 复制代码
void Execute(int a) { }      // 候选1 
void Execute(double a) { }   // 候选2 
Execute(5); // 选择候选1(int 精确匹配)
Execute(5.0); // 选择候选2(double 精确匹配)
Execute(5.0f); // 选择候选2(float 隐式转 double)
(7)应用场景
  • 简化 API 设计:Console.WriteLine 支持不同参数类型(int, string, object)。
  • 灵活初始化:类提供多种构造函数(如 FileStream 支持路径或句柄初始化)。
  • 自定义类型行为:为结构体重载运算符实现向量运算。
相关推荐
李宥小哥2 小时前
C#基础08-面向对象
开发语言·c#
樱木...2 小时前
MySQL 8.0 新特性之原子 DDL
数据库·mysql
Murphy_lx2 小时前
Linux(操作系统)文件系统--对打开文件的管理(C语言层面)
linux·服务器·c语言
1688red3 小时前
MySQL连接时提示ERROR 2002 (HY000)解决方案
数据库·mysql
代码小菜鸡6663 小时前
10.2 刷题知识点总结(1) ---- 正则表达式
数据库
lagelangri6664 小时前
数据库连接池以及HikariCP使用
数据库·oracle
包达叔4 小时前
仿NewLife的XmlConfig类实现Json配置文件
c#·json·newlife
脏脏a4 小时前
【Linux篇】Linux指令进阶:从入门到熟练的实操指南
linux·运维·服务器
意疏4 小时前
平替MongoDB:金仓多模数据库助力电子证照国产化实践
数据库·mongodb