📝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#的设计原则