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 访问器 |
| 封装性 | 通常 private | public 或 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 | 运行期常量 |
设计原则
- 字段使用 private——通过属性暴露数据,便于添加验证逻辑
- 优先使用自动属性——
{ get; set; }最简洁 - 构造函数初始化必要字段——确保对象创建时处于有效状态
- 使用静态成员管理全局状态——例如计数器、配置信息
- IDisposable 释放非托管资源——配合
using语句 - 封装变化——将可能变化的逻辑封装在方法内部,对外隐藏实现细节


