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());

几个核心概念:

二、属性:自动属性 + 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;   // 只读
}

关键特性:

三、构造函数

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

几个细节:

四、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 适用场景:

注意: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(不对外暴露)

核心规则:

设计原则:最小暴露。字段默认 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 是现代 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);

选择:

八、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 模型)、大型类分块

十、面向对象三大特性

小结

类是面向对象的基石。记住现代 C# 几个核心实践:用自动属性而非字段用 init/required 实现不可变用 record 表示数据访问最小化。下一篇我们看继承与多态——把类的复用性发挥到极致。

← 上一篇 C# 字符串

下一篇 C# 继承与多态

✈️💬