📝 C# 静态成员总结

🎯 核心区别表格

特性静态成员/方法非静态成员/方法
关键字static无关键字
属于谁属于类本身属于类的实例对象
调用方式类名.成员名对象.成员名
内存位置内存中只有一份每个对象都有独立副本
何时创建类加载时(程序启动)对象实例化时(new时)
生命周期程序运行期间对象存在期间

💻 代码要点总结

1. 访问规则

csharp

public class People
{
    public string Name;           // 非静态字段
    public static int Count;      // 静态字段
    
    // ✅ 非静态方法可以访问:静态+非静态
    public void Test1()
    {
        Console.WriteLine(this.Name);  // ✅ 访问非静态
        Console.WriteLine(Count);      // ✅ 访问静态
    }
    
    // ✅ 静态方法只能访问:静态
    public static void Test2()
    {
        // Console.WriteLine(Name);    // ❌ 错误!不能访问非静态
        Console.WriteLine(Count);      // ✅ 只能访问静态
    }
}

2. 使用场景对比

csharp

// 使用示例
People p1 = new People();
p1.Name = "张三";        // ✅ 对象访问非静态成员
People.Count = 20;       // ✅ 类名访问静态成员

p1.Test1();              // ✅ 对象调用非静态方法
People.Test2();          // ✅ 类名调用静态方法

🎮 游戏开发实用示例

示例1:玩家计数器

csharp

public class 玩家
{
    public string 名字;            // 非静态:每个玩家不同
    public static int 总玩家数;    // 静态:所有玩家共享
    
    public 玩家(string 名称)
    {
        名字 = 名称;
        总玩家数++;               // 静态计数器,统计总数
    }
    
    public void 显示信息()
    {
        // 非静态方法可以访问静态成员
        Console.WriteLine($"玩家:{名字},总玩家数:{总玩家数}");
    }
}

// 使用
玩家 玩家1 = new 玩家("张三");
玩家 玩家2 = new 玩家("李四");
玩家1.显示信息();  // 输出:玩家:张三,总玩家数:2

示例2:游戏工具类

csharp

// 静态工具类 - 不需要实例化
public static class 游戏工具
{
    public static int 生成随机数(int 最小值, int 最大值)
    {
        Random rnd = new Random();
        return rnd.Next(最小值, 最大值);
    }
    
    public static float 计算伤害(float 攻击力, float 防御力)
    {
        return 攻击力 * 100 / (100 + 防御力);
    }
}

// 使用:直接通过类名调用
int 随机伤害 = 游戏工具.生成随机数(10, 50);
float 最终伤害 = 游戏工具.计算伤害(100, 30);

🔑 关键记忆点

1. this 关键字

  • 只在非静态方法中使用

  • 代表当前对象实例

  • 可以省略(编译器自动推断)

csharp

public void Test()
{
    Console.WriteLine(this.Name);  // 显式使用this
    Console.WriteLine(Name);       // 隐式使用this(效果相同)
}

2. 静态构造器

csharp

public class 配置管理器
{
    public static string 游戏版本;
    
    // 静态构造器:类加载时自动执行一次
    static 配置管理器()
    {
        游戏版本 = "v1.0.0";
        Console.WriteLine("静态构造器执行");
    }
}

3. 实用场景总结

csharp

// ✅ 使用静态:
// 1. 工具类方法(Math.Max, Console.WriteLine)
// 2. 全局配置(游戏设置、数据库连接字符串)
// 3. 计数器、统计信息
// 4. 工厂方法

// ✅ 使用非静态:
// 1. 对象特有属性(玩家名字、血量)
// 2. 需要操作对象状态的方法
// 3. 需要继承和多态的场景

⚠️ 常见错误

错误1:静态访问非静态

csharp

public static void 静态方法()
{
    Console.WriteLine(Name);  // ❌ 错误!静态方法不能访问非静态字段
}

错误2:混淆调用方式

csharp

People p = new People();
p.Count = 10;      // ⚠️ 不推荐!虽然能编译,但让人困惑
People.Count = 10; // ✅ 正确!明确表示操作静态成员

💡 快速判断指南

问自己几个问题:

  1. 这个数据/方法需要每个对象独立吗?

    • 是 → 用非静态

    • 否 → 考虑静态

  2. 这个方法需要操作对象内部状态吗?

    • 是 → 用非静态

    • 否 → 考虑静态

  3. 这个方法像工具函数吗?(如计算、转换)

    • 是 → 用静态

📚 一句话总结

静态属于类,一份共享;非静态属于对象,各自独立。静态不能直接访问非静态,但非静态可以访问静态。

记住这个核心规则,就能正确使用静态和非静态成员了!

📝 C# 常量(const)与只读字段(readonly)总结

🎯 核心区别表格

特性常量(const)只读字段(readonly)
关键字constreadonly
赋值时机编译时(声明时必须赋值)运行时(可在声明时或构造函数中赋值)
内存位置编译时直接替换(类似宏)存储在类型的内存中
类型限制只能是基本类型(int, string等)可以是任何类型
静态性隐式静态可静态或实例
性能更好(编译时优化)正常访问
修改灵活性重新编译才能修改可在构造函数中设置不同值

💻 代码要点详解

1. 基本语法对比

csharp

class 游戏配置
{
    // ✅ 常量 - 编译时确定
    public const double 默认税率 = 0.1;
    public const string 游戏名称 = "三角洲行动";
    
    // ✅ 只读字段 - 运行时确定
    public readonly DateTime 创建时间;
    public readonly List<string> 武器列表;
    public static readonly int 服务器ID;
}

2. 赋值规则总结

csharp

class 三角洲
{
    // ✅ 只读字段可以在声明时赋值
    public readonly double 资产 = 10000;
    public static readonly string 昵称 = "伊娃诺夫斯基";
    
    // ✅ 也可以在构造函数中赋值
    public 三角洲()
    {
        资产 = 10000000;  // ✅ 非静态只读字段可以在构造中修改
        // 昵称 = "ss";   // ❌ 静态只读字段不能在非静态构造中修改
    }
    
    // ✅ 静态只读字段只能在静态构造中修改
    static 三角洲()
    {
        昵称 = "ss";  // ✅ 可以在静态构造中修改
    }
    
    public void 普通方法()
    {
        // 资产 = 100;  // ❌ 只读字段不能在普通方法中修改
    }
}

🎮 游戏开发实用示例

示例1:配置系统

csharp

public class 游戏设置
{
    // ✅ 真正的常量 - 永远不会变
    public const string 游戏引擎 = "Unity";
    public const int 最大帧率 = 144;
    
    // ✅ 只读配置 - 启动时确定,运行时不变
    public readonly string 语言设置;
    public readonly bool 开启音效;
    public static readonly DateTime 游戏启动时间;
    
    // 静态构造器初始化静态只读字段
    static 游戏设置()
    {
        游戏启动时间 = DateTime.Now;
    }
    
    // 实例构造器初始化实例只读字段
    public 游戏设置(string 语言, bool 音效)
    {
        语言设置 = 语言;
        开启音效 = 音效;
    }
}

示例2:装备属性

csharp

public class 装备
{
    // ✅ 常量:所有装备共享的固定值
    public const int 最大耐久度 = 100;
    public const string 装备品质_传说 = "Legendary";
    
    // ✅ 只读:每个装备特有的属性
    public readonly string 装备名称;
    public readonly int 基础攻击力;
    public readonly DateTime 获取时间;
    
    public 装备(string 名称, int 攻击力)
    {
        装备名称 = 名称;
        基础攻击力 = 攻击力;
        获取时间 = DateTime.Now;  // 每个装备获取时间不同
    }
}

🔑 关键规则总结

1. 只读字段的修改权限

csharp

class 示例
{
    // 实例只读字段:
    // ✅ 声明时赋值
    // ✅ 实例构造函数中赋值
    // ❌ 其他方法中赋值
    // ❌ 静态方法中赋值
    
    // 静态只读字段:
    // ✅ 声明时赋值
    // ✅ 静态构造函数中赋值
    // ❌ 实例构造函数中赋值
    // ❌ 其他方法中赋值
}

2. 常量与只读的编译行为

csharp

const int MAX_LEVEL = 100;
readonly int CurrentLevel = 1;

// 编译后:
// MAX_LEVEL 被直接替换为字面值 100
// CurrentLevel 仍然是字段访问

3. 重要注意事项

csharp

public class 玩家
{
    // ❌ 常量不能用自定义类型
    // public const List<string> 技能列表 = new List<string>();
    
    // ✅ 只读字段可以用任何类型
    public readonly List<string> 技能列表 = new List<string>();
    
    // 注意:只读只保护引用,不保护对象内容!
    public void 学习技能()
    {
        // 技能列表 = new List<string>();  // ❌ 不能重新赋值
        技能列表.Add("火球术");             // ✅ 可以修改列表内容
    }
}

💡 使用场景选择指南

✅ 使用 const 的情况:

csharp

// 1. 数学/物理常数
public const double PI = 3.1415926535;
public const float 重力加速度 = 9.8f;

// 2. 固定配置值
public const int 最大玩家数 = 100;
public const string 默认语言 = "zh-CN";

// 3. 枚举值的替代(简单情况)
public const int 状态_空闲 = 0;
public const int 状态_战斗中 = 1;

✅ 使用 readonly 的情况:

csharp

// 1. 需要运行时确定的配置
public readonly string 数据库连接字符串;
public readonly int 服务器端口;

// 2. 对象特有但创建后不变的属性
public readonly DateTime 出生日期;
public readonly string 角色ID;

// 3. 复杂类型的不变引用
public readonly Dictionary<string, int> 物品价格表;
public readonly Color 主题颜色;

⚠️ 常见错误与陷阱

错误1:混淆赋值时机

csharp

class 测试
{
    public readonly int 数值;
    
    public 测试(int x)
    {
        // 这里可以赋值
        数值 = x;
    }
    
    public void 修改()
    {
        // 这里不能赋值!
        // 数值 = 10;  // ❌ 编译错误
    }
}

错误2:静态只读字段的错误使用

csharp

class 游戏
{
    public static readonly int 玩家数量;
    
    public 游戏()
    {
        // 玩家数量 = 1;  // ❌ 错误!静态只读不能在实例构造中赋值
    }
    
    static 游戏()
    {
        玩家数量 = 0;  // ✅ 正确!在静态构造中赋值
    }
}

陷阱:只读字段的对象内容可变

csharp

public class 背包
{
    public readonly List<string> 物品列表 = new List<string>();
    
    public void 添加物品(string 物品)
    {
        物品列表.Add(物品);  // ✅ 可以!只读的是引用,不是内容
        // 物品列表 = new List<string>();  // ❌ 不可以!不能重新赋值
    }
}

🚀 最佳实践建议

1. 明确意图

csharp

// 明确表达:"这个值永远不会变"
public const double 数学常数_PI = 3.14159;

// 明确表达:"这个值对象创建后不会变"
public readonly DateTime 账号创建时间;

// 明确表达:"这个值类加载后不会变"
public static readonly string 游戏版本号;

2. 命名约定

csharp

// 常量:全部大写,下划线分隔
public const int MAX_PLAYER_COUNT = 100;
public const string DEFAULT_LANGUAGE = "zh-CN";

// 只读字段:正常Pascal命名
public readonly DateTime AccountCreateTime;
public static readonly string GameVersion;

3. 性能考虑

csharp

// 频繁访问的固定值用const(编译时优化)
public const int BUFFER_SIZE = 1024;

// 需要运行时计算的用readonly
public readonly int CacheSize = CalculateOptimalCacheSize();

📚 一句话总结

const是"编译时就固定"的永恒不变,readonly是"运行时确定但之后不变"的承诺。

记住:

  • const → 简单类型,真正永恒不变,编译时替换

  • readonly → 任何类型,对象/类创建后不变,运行时确定

选择时问自己:"这个值在编译时就能确定吗?"

  • 能 → 考虑 const

  • 不能 → 用 readonly

📝 C# 类的继承与构造函数总结

🎯 继承的核心概念

📚 面向对象四大特征总结

特征说明示例
封装隐藏内部实现,暴露必要接口属性封装字段,方法封装逻辑
继承子类获得父类特性,避免重复代码class Student : People
多态同一接口不同实现,提高灵活性虚方法重写、接口实现
抽象定义规范而不指定实现抽象类、接口

1. 继承的基本语法

csharp

// 父类(基类)
class People
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 子类(派生类)使用 : 继承
class Student : People
{
    public int StudentId { get; set; }
}

2. 继承的访问权限

csharp

class People
{
    public string Name;         // ✅ 公共 - 可以被继承和访问
    private string Secret;      // ❌ 私有 - 不能被继承
    protected string Nickname;  // ✅ 受保护 - 可以被继承,但不能被外部对象访问
    protected internal string Code; // ✅ 可以被继承,可以在同一程序集访问
}

class Student : People
{
    public void Show()
    {
        Console.WriteLine(Name);     // ✅ 可以访问
        // Console.WriteLine(Secret); // ❌ 不能访问私有
        Console.WriteLine(Nickname); // ✅ 可以访问受保护
        Console.WriteLine(Code);     // ✅ 可以访问
    }
}

🔄 构造函数的执行顺序

1. 默认执行顺序

csharp

class People
{
    public People()
    {
        Console.WriteLine("父类构造执行");
    }
}

class Student : People
{
    public Student()
    {
        Console.WriteLine("子类构造执行");
    }
}

// 使用
Student s = new Student();
// 输出:
// 父类构造执行
// 子类构造执行

2. 手动调用父类有参构造

csharp

class People
{
    public string Name;
    public int Age;
    
    public People() { }
    
    public People(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"父类有参构造: {name}, {age}");
    }
}

class Student : People
{
    public int Score;
    
    // 调用父类有参构造
    public Student(string name, int age, int score) 
        : base(name, age)  // 🔑 关键在这里!
    {
        Score = score;
        Console.WriteLine($"子类构造,分数: {score}");
    }
}

// 使用
Student s = new Student("张三", 18, 90);
// 输出:
// 父类有参构造: 张三, 18
// 子类构造,分数: 90

📊 继承规则总结表

特性能否被继承说明
公共成员 (public)✅ 可以子类和外部都可访问
私有成员 (private)❌ 不能仅本类可访问
受保护成员 (protected)✅ 可以子类可访问,外部不可
内部成员 (internal)✅ 可以(同程序集)同一程序集内可访问
受保护内部 (protected internal)✅ 可以子类或同程序集
方法✅ 可以遵循访问修饰符规则
构造函数❌ 不能但会隐式/显式调用
静态成员✅ 可以但属于父类,不重复创建

🎮 游戏开发继承示例

示例1:游戏角色继承体系

csharp

// 基类:所有游戏角色的共性
public class GameCharacter
{
    public string Name { get; set; }
    public int Level { get; set; }
    public int Health { get; set; }
    
    public GameCharacter(string name)
    {
        Name = name;
        Level = 1;
        Health = 100;
    }
    
    public virtual void Attack()
    {
        Console.WriteLine($"{Name}发起普通攻击");
    }
}

// 战士类
public class Warrior : GameCharacter
{
    public int Armor { get; set; }
    
    public Warrior(string name, int armor) 
        : base(name)  // 调用父类构造
    {
        Armor = armor;
    }
    
    public override void Attack()
    {
        Console.WriteLine($"{Name}使用剑猛砍!");
    }
    
    public void Defend()
    {
        Console.WriteLine($"{Name}举盾防御");
    }
}

// 法师类
public class Mage : GameCharacter
{
    public int Mana { get; set; }
    
    public Mage(string name, int mana) : base(name)
    {
        Mana = mana;
    }
    
    public override void Attack()
    {
        Console.WriteLine($"{Name}释放火球术!");
    }
    
    public void CastSpell()
    {
        Console.WriteLine($"{Name}施法消耗{10}法力");
        Mana -= 10;
    }
}

示例2:多层继承

csharp

// 基类
public class Vehicle
{
    public string Brand { get; set; }
    
    public Vehicle(string brand)
    {
        Brand = brand;
    }
}

// 一级派生
public class Car : Vehicle
{
    public int Doors { get; set; }
    
    public Car(string brand, int doors) : base(brand)
    {
        Doors = doors;
    }
}

// 二级派生
public class SportsCar : Car
{
    public int MaxSpeed { get; set; }
    
    public SportsCar(string brand, int doors, int maxSpeed) 
        : base(brand, doors)  // 调用直接父类构造
    {
        MaxSpeed = maxSpeed;
    }
}

⚠️ 重要注意事项

1. 单继承限制

csharp

// ❌ 错误:C#不支持多继承
// class MyClass : ClassA, ClassB  // 编译错误

// ✅ 正确:但可以实现多个接口
class MyClass : ClassA, IInterface1, IInterface2

// ✅ 正确:支持多层继承
class GrandParent { }
class Parent : GrandParent { }
class Child : Parent { }  // 包含 GrandParent 和 Parent 的成员

2. 构造函数调用规则

csharp

class Parent
{
    public Parent() { }           // 无参构造
    public Parent(int x) { }      // 有参构造
}

class Child : Parent
{
    // 如果不写 : base(),编译器会自动调用父类无参构造
    public Child() { }
    
    // 如果父类没有无参构造,子类必须显式调用有参构造
    public Child(int x) : base(x) { }
}

3. 继承链中的访问权限

csharp

class A
{
    protected int Value = 10;
}

class B : A
{
    public void Show()
    {
        Console.WriteLine(Value);  // ✅ 可以访问
    }
}

class C : B
{
    public void Display()
    {
        Console.WriteLine(Value);  // ✅ 可以访问(通过B继承)
    }
}

💡 最佳实践

1. 合理的继承层次

csharp

// ✅ 好的设计:"是一个"的关系
class Animal { }
class Dog : Animal { }        // 狗是一种动物
class Cat : Animal { }        // 猫是一种动物

// ❌ 不好的设计:"有一个"的关系
class Car { }
class Engine { }
// class Car : Engine { }     // 错误!车有引擎,但不是引擎

2. 使用 base 关键字

csharp

class Parent
{
    protected string message = "来自父类";
    
    public virtual void Show()
    {
        Console.WriteLine(message);
    }
}

class Child : Parent
{
    public override void Show()
    {
        base.Show();  // 调用父类方法
        Console.WriteLine("添加子类功能");
    }
}

3. 构造函数的设计

csharp

// 推荐:为基类提供无参构造,便于继承
public class BaseClass
{
    public BaseClass() { }
    public BaseClass(string param) { }
}

// 或者在派生类中显式调用基类构造
public class DerivedClass : BaseClass
{
    public DerivedClass() : base("默认值") { }
}


🚀 一句话总结

继承让代码复用,构造函数执行顺序:先父后子,用 : base() 调用父类构造。

记住:

  • 继承关系是"是什么"(is-a)的关系

  • 构造函数总是从最基类开始执行

  • 访问权限决定哪些成员可以被子类使用

  • 单继承,多接口是C#的设计原则

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐