Skip to content

17-类和对象

面向对象编程(OOP)是 C# 的核心编程范式。(Class)是对象的蓝图,对象(Object)是类的实例。


一、OOP 三大特性

特性说明类比
封装(Encapsulation)隐藏内部实现细节,只暴露必要接口自动挡汽车——踩油门就走,不用管发动机内部
继承(Inheritance)子类复用父类成员,扩展新功能孩子继承父母的基因,又有自己特点
多态(Polymorphism)同一操作在不同对象上有不同表现"按喇叭"——不同车发出不同声音

二、类的定义

基本语法

csharp
[访问修饰符] class 类名
{
    // 字段(存储数据)
    // 属性(封装字段)
    // 方法(行为)
    // 构造函数(初始化对象)
    // 事件
    // 索引器
}

完整示例

csharp
public class Person
{
    // ===== 字段(field):私有,存储数据 =====
    private string name;
    private int age;
    private static int totalCount = 0;  // 静态字段——属于类本身

    // ===== 属性(property):封装字段 =====
    public string Name
    {
        get { return name; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("姓名不能为空");
            name = value;
        }
    }

    public int Age
    {
        get { return age; }
        set
        {
            if (value < 0 || value > 150)
                throw new ArgumentException("年龄必须在 0-150 之间");
            age = value;
        }
    }

    // 自动属性——编译器自动生成字段
    public string Email { get; set; }

    // 只读属性
    public string Id { get; } = Guid.NewGuid().ToString("N");

    // 表达式主体属性(C# 6.0+)
    public string DisplayName => $"{Name}({Age}岁)";

    // ===== 静态属性 =====
    public static int TotalCount => totalCount;

    // ===== 构造函数 =====
    public Person(string name, int age)
    {
        Name = name;   // 通过属性赋值(会触发验证)
        Age = age;
        totalCount++;
    }

    // ===== 方法 =====
    public void Introduce()
    {
        Console.WriteLine($"你好,我叫{Name},今年{Age}岁。");
    }

    // 静态方法
    public static void ShowTotalCount()
    {
        Console.WriteLine($"总共创建了 {totalCount} 个人");
    }
}

// ===== 使用类 =====
var p = new Person("张三", 25);
p.Email = "zhang@test.com";
p.Introduce();       // 输出:你好,我叫张三,今年25岁。
Console.WriteLine(p.DisplayName);  // 输出:张三(25岁)
Person.ShowTotalCount();  // 输出:总共创建了 1 个人

三、字段 vs 属性

对比项字段(Field)属性(Property)
访问方式直接读写通过 get/set 访问器
封装性通常 privatepublic 或 protected
验证逻辑无法添加可以在 set 中添加验证
计算逻辑不能get 中可以计算
接口支持不支持支持(接口可以定义属性)
绑定支持不支持支持(WPF/WinForms 数据绑定)
csharp
public class Product
{
    // ❌ 公有字段(不推荐——破坏封装)
    public decimal Price;

    // ✅ 属性:可以添加验证逻辑
    private decimal _price;
    public decimal Price
    {
        get { return _price; }
        set
        {
            if (value < 0)
                throw new ArgumentException("价格不能为负数");
            _price = value;
        }
    }

    // ✅ 自动属性(推荐——最简洁)
    public string Name { get; set; }

    // ✅ 计算属性(只有 get,没有存储字段)
    public decimal Tax => Price * 0.1m;
}

属性访问器不同权限

csharp
public class User
{
    public string Name { get; set; }                 // 公开读写
    public int Age { get; private set; }             // 公开读,类内部写
    public string Password { private get; set; }     // 公开写,类内部读
    internal string Role { get; set; }               // 同一程序集可访问
}

四、构造函数

构造函数在 new 创建对象时自动调用,用于初始化对象。

多种构造函数

csharp
public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Grade { get; set; }
    public readonly DateTime CreateTime;

    // 1. 默认构造函数(无参数)
    public Student()
    {
        Name = "未知";
        Age = 0;
        Grade = "未分配";
        CreateTime = DateTime.Now;
    }

    // 2. 带参数构造函数
    public Student(string name, int age) : this()  // 先调用默认构造函数
    {
        Name = name;
        Age = age;
    }

    // 3. 完整参数构造函数
    public Student(string name, int age, string grade) : this(name, age)
    {
        Grade = grade;
    }

    // 4. 复制构造函数
    public Student(Student other)
    {
        Name = other.Name;
        Age = other.Age;
        Grade = other.Grade;
        CreateTime = other.CreateTime;
    }
}

// 使用
var s1 = new Student();                           // 默认
var s2 = new Student("张三", 20);                 // 部分参数
var s3 = new Student("李四", 22, "A班");           // 完整参数
var s4 = new Student(s3);                         // 复制

构造函数链(: this()

csharp
public class Order
{
    public string OrderId { get; }
    public DateTime CreateTime { get; }
    public decimal Amount { get; }

    // 最终执行的基础构造函数
    public Order(string orderId, DateTime createTime, decimal amount)
    {
        OrderId = orderId;
        CreateTime = createTime;
        Amount = amount;
    }

    // 链式调用:生成订单号,使用当前时间
    public Order(decimal amount)
        : this($"ORD-{DateTime.Now:yyyyMMdd-HHmmss}", DateTime.Now, amount)
    {
    }

    // 链式调用:金额为 0 的默认订单
    public Order() : this(0)
    {
    }
}

静态构造函数

用于初始化静态成员,在第一次访问该类时自动执行一次

csharp
public class DatabaseConfig
{
    public static readonly string ConnectionString;
    public static readonly string DbType;
    public static readonly DateTime InitTime;

    // 静态构造函数——只执行一次
    static DatabaseConfig()
    {
        Console.WriteLine("静态构造函数执行...");
        ConnectionString = "Server=localhost;Database=mydb;";
        DbType = "SQL Server";
        InitTime = DateTime.Now;
    }

    public static void ShowConfig()
    {
        Console.WriteLine($"{DbType} - 初始化于 {InitTime}");
    }
}

Console.WriteLine("第一次访问:");
DatabaseConfig.ShowConfig();  // 触发静态构造函数

Console.WriteLine("第二次访问:");
DatabaseConfig.ShowConfig();  // 不会再次执行静态构造函数

五、静态成员

静态成员属于 类本身,所有实例共享同一份数据。

csharp
public class Counter
{
    private static int total = 0;   // 静态字段——所有实例共享
    private string name;            // 实例字段——每个实例独有

    public Counter(string name)
    {
        this.name = name;
        total++;  // 每次创建实例,静态字段 +1
    }

    public void Show()
    {
        Console.WriteLine($"{name}:当前总计数 = {total}");
    }

    public static int GetTotal() => total;  // 静态方法
}

var c1 = new Counter("A");  // total = 1
var c2 = new Counter("B");  // total = 2
var c3 = new Counter("C");  // total = 3

c1.Show();  // A:当前总计数 = 3
c2.Show();  // B:当前总计数 = 3
c3.Show();  // C:当前总计数 = 3

Console.WriteLine(Counter.GetTotal());  // 3(通过类名调用静态方法)

静态方法 vs 实例方法

对比静态方法实例方法
调用方式ClassName.Method()instance.Method()
访问实例成员不能
访问静态成员
this 关键字不能使用能使用
内存类加载时分配创建对象时分配

静态类

静态类不能实例化,只能包含静态成员,常用于工具类。

csharp
public static class StringHelper
{
    public static bool IsNullOrEmpty(string s) => string.IsNullOrEmpty(s);

    public static string Reverse(string s)
    {
        char[] chars = s.ToCharArray();
        Array.Reverse(chars);
        return new string(chars);
    }

    public static string ToBase64(string s)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(s);
        return Convert.ToBase64String(bytes);
    }
}

// 直接使用,无需创建实例
string reversed = StringHelper.Reverse("Hello");  // olleH

六、访问修饰符

修饰符说明访问范围
public公开任何地方都可访问
private私有仅当前类内部
protected受保护当前类 + 派生类
internal内部同一程序集
protected internal受保护内部同一程序集 + 派生类
private protected私有受保护同一程序集内的派生类(C# 7.2+)
csharp
public class AccessDemo
{
    public int PublicField;             // 任何地方
    private int PrivateField;           // 仅本类
    protected int ProtectedField;       // 本类 + 子类
    internal int InternalField;         // 同一程序集
    protected internal int ProIntField; // 同一程序集 + 子类
    private protected int PriProField;  // 同一程序集的子类
}

七、this 关键字

this 表示当前对象实例,用于:

csharp
public class Person
{
    private string name;
    private int age;

    // 1. 区分参数和字段
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    // 2. 调用另一个构造函数
    public Person() : this("默认", 0)
    {
    }

    // 3. 返回当前实例(链式调用)
    public Person SetName(string name)
    {
        this.name = name;
        return this;
    }

    public Person SetAge(int age)
    {
        this.age = age;
        return this;
    }

    public void Print()
    {
        Console.WriteLine($"{name}, {age}");
    }
}

// 链式调用
new Person()
    .SetName("张三")
    .SetAge(25)
    .Print();  // 输出:张三, 25

八、const 和 readonly

关键字说明初始化时机是否为静态
const编译期常量声明时必须初始化隐式 static
readonly运行时常量声明时 或 构造函数中可 static 或实例
csharp
public class Constants
{
    // const:编译期确定,隐式 static
    public const double PI = 3.14159;
    public const int MaxRetries = 3;
    public const string AppName = "MyApp";

    // readonly:运行期确定,每个对象可以不同
    public readonly Guid Id;
    public readonly DateTime CreateTime;

    // static readonly:运行期确定,所有对象共享
    public static readonly string MachineName;

    // readonly 可在构造函数中赋值
    public Constants()
    {
        Id = Guid.NewGuid();
        CreateTime = DateTime.Now;
    }

    // 静态 readonly 在静态构造函数中赋值
    static Constants()
    {
        MachineName = Environment.MachineName;
    }
}

九、析构函数和 IDisposable

析构函数在 GC 回收时调用。但推荐使用 IDisposable 显式释放资源。

csharp
// 方式一:析构函数(不推荐——不确定何时调用)
public class FileHandler
{
    ~FileHandler()
    {
        Console.WriteLine("析构函数被调用");
    }
}

// 方式二:IDisposable(推荐)
public class DatabaseConnection : IDisposable
{
    public void Connect()
    {
        Console.WriteLine("连接数据库...");
    }

    public void Dispose()
    {
        Console.WriteLine("断开数据库连接,释放资源...");
        // 阻止 GC 调用析构函数
        GC.SuppressFinalize(this);
    }
}

// 使用 using 自动释放
using (var db = new DatabaseConnection())
{
    db.Connect();
    // 使用数据库...
}  // 自动调用 Dispose()

十、综合案例:银行账户管理

csharp
public class BankAccount
{
    // 静态成员
    private static int totalAccounts = 0;
    public static int TotalAccounts => totalAccounts;

    // 只读字段
    public readonly string AccountNumber;
    public readonly DateTime CreateTime;

    // 私有字段
    private decimal balance;
    private List<string> transactionLog = new List<string>();

    // 属性
    public string OwnerName { get; }
    public decimal Balance
    {
        get { return balance; }
        private set { balance = value; }  // 外部不能直接修改
    }

    // 只读计算属性
    public string AccountStatus
    {
        get
        {
            if (balance > 10000) return "VIP";
            if (balance >= 0) return "正常";
            return "透支";
        }
    }

    // 构造函数
    public BankAccount(string ownerName, decimal initialDeposit = 0)
    {
        OwnerName = ownerName;
        AccountNumber = $"ACC-{DateTime.Now:yyyyMMdd}-{++totalAccounts:D4}";
        CreateTime = DateTime.Now;
        Deposit(initialDeposit);
    }

    // 方法
    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("存款金额必须大于0");

        balance += amount;
        LogTransaction($"存款:+{amount:C},余额:{balance:C}");
    }

    public bool Withdraw(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("取款金额必须大于0");

        if (amount > balance)
        {
            LogTransaction($"取款失败:余额不足(尝试取款 {amount:C})");
            return false;
        }

        balance -= amount;
        LogTransaction($"取款:-{amount:C},余额:{balance:C}");
        return true;
    }

    public void TransferTo(BankAccount target, decimal amount)
    {
        if (Withdraw(amount))
        {
            target.Deposit(amount);
            LogTransaction($"转账:-{amount:C} 至 {target.AccountNumber}");
        }
    }

    private void LogTransaction(string message)
    {
        string log = $"[{DateTime.Now:HH:mm:ss}] {message}";
        transactionLog.Add(log);
        Console.WriteLine(log);
    }

    public void PrintStatement()
    {
        Console.WriteLine($"\n=== 账户对账单 ===");
        Console.WriteLine($"户主:{OwnerName}");
        Console.WriteLine($"账号:{AccountNumber}");
        Console.WriteLine($"状态:{AccountStatus}");
        Console.WriteLine($"余额:{Balance:C}");
        Console.WriteLine($"创建时间:{CreateTime:yyyy-MM-dd}");
        Console.WriteLine($"\n交易记录:");
        foreach (string log in transactionLog)
            Console.WriteLine($"  {log}");
    }
}

// 使用
var acc1 = new BankAccount("张三", 5000);
var acc2 = new BankAccount("李四");

acc1.Deposit(10000);         // 存款
acc1.Withdraw(2000);         // 取款
acc1.TransferTo(acc2, 3000); // 转账

Console.WriteLine($"\n总共开户:{BankAccount.TotalAccounts} 个");
acc1.PrintStatement();
acc2.PrintStatement();

核心知识点总结

类成员速查

成员关键字说明
字段field存储数据,通常 private
属性{ get; set; }封装字段,可添加验证
构造函数ClassName()初始化对象
方法ReturnType MethodName()定义行为
静态成员static属于类本身,所有实例共享
常量const编译期常量
只读字段readonly运行期常量

设计原则

  1. 字段使用 private——通过属性暴露数据,便于添加验证逻辑
  2. 优先使用自动属性——{ get; set; } 最简洁
  3. 构造函数初始化必要字段——确保对象创建时处于有效状态
  4. 使用静态成员管理全局状态——例如计数器、配置信息
  5. IDisposable 释放非托管资源——配合 using 语句
  6. 封装变化——将可能变化的逻辑封装在方法内部,对外隐藏实现细节

Released under the MIT License.