C#静态成员总结 常量与只读字段总结 类的继承总结
·
📝 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; // ✅ 正确!明确表示操作静态成员
💡 快速判断指南
问自己几个问题:
-
这个数据/方法需要每个对象独立吗?
-
是 → 用非静态
-
否 → 考虑静态
-
-
这个方法需要操作对象内部状态吗?
-
是 → 用非静态
-
否 → 考虑静态
-
-
这个方法像工具函数吗?(如计算、转换)
-
是 → 用静态
-
📚 一句话总结
静态属于类,一份共享;非静态属于对象,各自独立。静态不能直接访问非静态,但非静态可以访问静态。
记住这个核心规则,就能正确使用静态和非静态成员了!
📝 C# 常量(const)与只读字段(readonly)总结
🎯 核心区别表格
| 特性 | 常量(const) | 只读字段(readonly) |
|---|---|---|
| 关键字 | const | readonly |
| 赋值时机 | 编译时(声明时必须赋值) | 运行时(可在声明时或构造函数中赋值) |
| 内存位置 | 编译时直接替换(类似宏) | 存储在类型的内存中 |
| 类型限制 | 只能是基本类型(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#的设计原则
更多推荐


所有评论(0)