C# 类与面向对象
C# 是纯粹的面向对象语言——一切皆对象,所有代码都写在类里(顶级语句背后也是自动生成的 Program 类)。这一篇我们系统讲 C# 类的核心三件套:字段、属性、方法,再加上构造函数、静态成员、访问修饰符。最后认识现代 C# 的 record。
一、类的核心结构
看一个完整的 Student 类:
public class Student
{
// 字段(私有,内部存储)
private string _name;
// 属性(对外暴露,PascalCase)
public string Name
{
get { return _name; }
set { _name = value ?? throw new ArgumentNullException(nameof(value)); }
}
// 自动属性(最常用):背后自动生成私有字段
public int Age { get; set; }
// 只读属性(只能在构造函数中改)
public string Id { get; }
// init 属性(C# 9+):初始化时可赋值,之后不可变
public string Email { get; init; } = "";
// 构造函数
public Student(string name, int age, string id)
{
_name = name;
Age = age;
Id = id;
}
// 方法
public string Introduce()
{
return $"我是{Name},今年{Age}岁。";
}
}
// 使用
var stu = new Student("小明", 20, "S001");
Console.WriteLine(stu.Name); // "小明"
stu.Age = 21; // 可改(set)
// stu.Id = "S002"; // ❌ 只读
Console.WriteLine(stu.Introduce());几个核心概念:
- 字段(field):类内部的"存储",通常 private,_ 前缀命名。
- 属性(property):对外的"接口",用
get/set。C# 特色,比 Java getter/setter 简洁。 - 构造函数(constructor):与类同名,无返回值,用于初始化对象。
- 方法(method):类的"行为"。
二、属性:自动属性 + init + required
C# 的属性系统是它最优雅的设计之一。现代 C# 优先用自动属性,代码极简:
// 自动属性:最常用
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
}
// 对象初始化器(C# 3+)
var p = new Product { Id = 1, Name = "鼠标", Price = 99.9m };
// required 修饰符(C# 11):必须初始化
public class Order
{
public required int Id { get; set; }
public required DateTime CreatedAt { get; set; }
public string? Note { get; set; }
}
var order = new Order { Id = 1, CreatedAt = DateTime.Now };
// 缺 Id 或 CreatedAt 编译报错
// 表达式体属性(C# 6+)
public class Circle
{
public double Radius { get; set; }
public double Area => Math.PI * Radius * Radius; // 只读
}关键特性:
- 自动属性
{ get; set; }:背后自动生成私有字段,无需手写。 - 只读属性
{ get; }:只能在构造函数中赋值。 init(C# 9+):对象初始化时可赋值,之后不可变,适合不可变数据。required(C# 11+):强制对象初始化器必须赋值。- 对象初始化器
new T { ... }:不调用构造函数也能批量赋值。
三、构造函数
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Role { get; set; }
// 主构造函数(C# 12+,可省略字段)
public User(string name, int age)
{
Name = name;
Age = age;
Role = "user";
}
// 构造函数重载:用 : this(...) 调用其他构造
public User(string name) : this(name, 0) { }
// 静态构造函数:类型首次使用时调用一次
static User()
{
Console.WriteLine("User 类初始化");
}
}
var u1 = new User("Tom", 25);
var u2 = new User("Jerry"); // age = 0几个细节:
- 不写构造函数,编译器提供一个默认无参构造(初始化所有字段为默认值)。
- 一旦写了任何构造函数,默认无参构造就消失。
- : this(...) 调用同一个类的其他构造函数,避免重复初始化代码。
- 静态构造函数:类首次使用时调用一次,用于初始化静态字段。
- 主构造函数(C# 12+):类声明里直接写参数,可在任意方法中使用。
四、static 静态成员
static 成员属于类本身,不属于某个对象,通过类名直接访问:
public class MathHelper
{
// 静态字段:类共享一份
public static int CallCount = 0;
// 静态方法:直接用类名调用
public static int Square(int x)
{
CallCount++;
return x * x;
}
// 静态类:不能 new,所有成员必须 static
}
// 调用
int r1 = MathHelper.Square(5); // 25
int r2 = MathHelper.Square(10); // 100
Console.WriteLine(MathHelper.CallCount); // 2
// 静态类(常见于工具类)
public static class StringExtensions
{
public static bool IsBlank(string s) => string.IsNullOrWhiteSpace(s);
}
// const 隐式 static
public class Config
{
public const string Version = "1.0"; // 编译时常量
public static readonly DateTime BuildTime = DateTime.Now; // 运行时常量
}static 适用场景:
- 工具类:Math、Convert、JsonSerializer、File。
- 常量:const 隐式 static,如
Math.PI。 - 全局计数/缓存:但要考虑线程安全。
- 工厂方法:如
DateTime.Now、Task.Run。
注意:static 是面向对象的反模式(全局状态),滥用会让代码难测试。能不用就不用,优先用依赖注入。
五、访问修饰符
控制类、字段、方法对外可见性:
// 访问修饰符
public class Example
{
public int Public; // 任何地方可访问
private int _private; // 仅本类可访问(默认)
protected int Protected; // 本类 + 子类
internal int Internal; // 同一程序集(同一项目)
protected internal int ProtInternal; // 程序集内 OR 子类
private protected int PrivProt; // 程序集内的子类
}
// 推荐:
// - 字段:private(以 _ 开头命名)
// - 属性:public(对外接口)
// - 方法:按需 public/private/internal
// - 静态辅助类:internal(不对外暴露)核心规则:
- public:任何代码都能访问(类、方法、字段都可以)。
- private:只有本类可访问(默认成员修饰符)。
- protected:本类 + 子类。
- internal:同一程序集(同一 .dll/.exe),默认类修饰符。
- protected internal / private protected:组合,较少用。
设计原则:最小暴露。字段默认 private,对外用属性。public API 慎重,一旦发布就难改。
六、record(C# 9+ 强推!)
record 是不可变数据对象的语法糖,函数式风格,特别适合 DTO、值对象、API 模型:
// record(C# 9+):不可变数据对象,函数式风格
public record Person(string Name, int Age);
// 创建
var p1 = new Person("小明", 20);
// 不可变,但可以 with 表达式生成副本(只改某些字段)
var p2 = p1 with { Age = 21 };
Console.WriteLine(p1.Age); // 20(原对象不变)
Console.WriteLine(p2.Age); // 21
// 基于值相等(不像 class 比较引用)
var p3 = new Person("小明", 20);
Console.WriteLine(p1 == p3); // True(class 的话是 False)
// 解构
var (name, age) = p1;
Console.WriteLine($"{name}, {age}");
// record 内部仍可加额外成员
public record Point(int X, int Y)
{
public double Distance() => Math.Sqrt(X * X + Y * Y);
}record 和 class 的关键区别:
- 基于值相等:两个 record 内容相同就 == ,不像 class 比较引用。
- 不可变:默认 init-only 属性,改用
with生成副本。 - 自动生成:构造、解构、ToString、Equals、GetHashCode。
- 支持继承:record 可以继承 record。
实际开发中,数据用 record,行为用 class 是现代 C# 的推荐实践。
七、结构体 struct vs 类 class
// struct:值类型,赋值时复制,适合小而轻的数据
public struct Point
{
public int X { get; init; }
public int Y { get; init; }
}
var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // 复制(因为是 struct)
// p2.X = 99; // ❌ init-only
// record struct(C# 10+):值类型 record
public record struct Point3D(int X, int Y, int Z);选择:
- class:引用类型,适合大多数场景。
- struct:值类型,适合小数据(Point、Color、DateTime)、避免堆分配。
- record:不可变引用类型,适合数据。
- record struct:不可变值类型。
八、this 与对象自身
this 表示当前对象的引用:
public class Counter
{
private int count;
public Counter(int count)
{
this.count = count; // 区分参数和字段
}
public Counter Increment()
{
this.count++;
return this; // 链式调用
}
}
var c = new Counter(0).Increment().Increment().Increment();
// c.count = 3九、partial 类
一个类可以拆到多个文件,编译时合并:
// User.cs
public partial class User
{
public string Name { get; set; }
}
// User.Address.cs
public partial class User
{
public string Address { get; set; }
}
// 适合:生成代码(如 EF Core 模型)、大型类分块十、面向对象三大特性
- 封装(Encapsulation):用 private 字段 + public 属性,隐藏内部实现。
- 继承(Inheritance):子类继承父类,代码复用。下一篇专门讲。
- 多态(Polymorphism):同一接口不同实现。下一篇讲 virtual/override。
小结
类是面向对象的基石。记住现代 C# 几个核心实践:用自动属性而非字段、用 init/required 实现不可变、用 record 表示数据、访问最小化。下一篇我们看继承与多态——把类的复用性发挥到极致。
← 上一篇 C# 字符串
下一篇 C# 继承与多态 →