本篇 核心知识点 :自定义 Attribute 特性完整规则、委托 delegate、多播委托、event 事件、委托与事件核心区别、观察者模式实战、匿名方法、多线程基础、线程抢占资源冲突、lock 线程锁、单例多线程安全
一、自定义特性 Attribute 完整规则
1. 概念
特性是附着在类 / 方法 / 字段 / 属性上的元数据标记,运行时通过反射读取,用来存储描述、约束、版本、功能标识等附加信息,不改变代码执行逻辑。
2. 自定义特性基类规则
所有自定义特性必须继承Attribute,类名约定以Attribute结尾,使用时可省略后缀。
3. 特性三大核心构造参数(继承 Attribute 时控制生效范围)
构造函数三个参数(顺序:使用范围、允许多标记、是否可继承)
-
第一个参数:AttributeTargets 标记适用对象枚举,限定该特性能写在哪些代码元素上;
可选值:
Class/Method/Field/Property/All等;用|分隔可同时多选;例:
AttributeTargets.Class | AttributeTargets.Method代表类、方法都能使用。 -
第二个参数:AllowMultiple(bool)
false(默认):同一个元素只能贴 1 个该特性,不能重复;true:同一个类 / 方法可多次叠加同一特性(技能同时带火 + 毒属性)。 -
第三个参数:Inherited(bool)
false(默认):父类标记特性,派生类不会自动继承;true:子类自动拥有父类附加的同特性,无需重复标注。
代码示例:技能类型自定义特性
using System;
// 自定义技能特性:允许类、方法使用,可重复、支持继承
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class SkillAttr : Attribute{
// 构造函数:存储技能类型
public string SkillType { get; }
public SkillAttr(string type){
SkillType = type;
}
}
// 使用特性(省略后缀Attr)
[Skill("火系")]
[Skill("灼烧")] // AllowMultiple=true 允许重复
class FireSkill{
[Skill("单体伤害")]
public void Release() { }
}
拓展系统内置特性
[Obsolete("提示", true)] 标记弃用代码,第二个参数 true 直接编译报错,false 仅警告。
二、委托 delegate
1. 概念
委托是函数引用类型 ,等价 C/C++ 函数指针,用来存储、传递、调用方法;规定统一的返回值、参数列表,只有签名匹配的方法才能存入委托变量。
2. 基础语法
// 定义委托类型:无返回、两个int参数
public delegate void CalcDelegate(int a, int b);
3. 基础使用(单委托)
步骤
-
使用
new 委托类型(方法名)绑定; -
直接
委托变量(参数)执行;class Test{
// 匹配委托签名的三个方法
static void Add(int a, int b) => Console.WriteLine(a + b);
static void Sub(int a, int b) => Console.WriteLine(a - b);
static void Mul(int a, int b) => Console.WriteLine(a * b);
static void Main(){
CalcDelegate del = new CalcDelegate(Add);
del(3, 5); // 输出8
del = Sub;
del(3, 5); // 输出-2
}
}
4. 多播委托(+=/-= 增减方法)
概念
一个委托变量绑定多个同签名方法,调用时按绑定顺序全部执行;
+=:追加方法监听;
-=:移除指定监听方法;
=:直接覆盖,清空所有绑定(不安全)。
static void Main(){
CalcDelegate del = Add;
del += Sub; // 追加
del += Mul;
del(3, 5); // 依次执行Add、Sub、Mul
del -= Sub; // 移除减法
del(3, 5); // 仅执行Add、Mul
}
多播缺陷
委托变量可直接用del = 新方法覆盖,会清空全部已绑定函数,破坏多监听逻辑,不安全,由此引出 event 事件。
三、event 事件(基于委托封装)
1. 核心概念
事件是受保护的委托变量,专门实现观察者(发布订阅)模式;
发布者:内部可以+=/-=绑定、也可以调用触发事件;
外部订阅者:仅允许+=/-=增删监听,禁止直接赋值 =、禁止外部调用触发,解决多播委托覆盖安全漏洞。
2. 语法规则
-
先定义配套委托类型;
-
类内用
event 委托类型 事件名声明; -
触发只能写在本类内部方法中。
实战案例(老鼠触发事件,猫、主人订阅)
using System;
// 1. 定义事件配套委托
public delegate void NoiseDelegate();
// 发布者:老鼠(触发事件)
class Mouse{
// 事件:噪音
public event NoiseDelegate OnNoise;
// 老鼠出洞,触发事件
public void OutHole(){
Console.WriteLine("老鼠钻出洞口,发出声响");
// 本类内才能调用触发
OnNoise?.Invoke(); // 空安全调用
}
}
// 订阅者:猫
class Cat{
public void Wake() => Console.WriteLine("猫被吵醒,喵喵叫");
}
// 订阅者:主人
class Master{
public void Wake() => Console.WriteLine("主人被吵醒,伸懒腰");
}
static void Main(){
Mouse mouse = new Mouse();
Cat cat = new Cat();
Master master = new Mouse();
// 订阅:+=绑定监听
mouse.OnNoise += cat.Wake;
mouse.OnNoise += master.Wake;
mouse.OutHole();
// mouse.OnNoise(); 外部直接调用编译报错,安全限制
// mouse.OnNoise = null; 外部赋值编译报错
}
委托 vs 事件对比表
| 对比项 | 普通委托 delegate | event 事件 |
|---|---|---|
外部赋值= |
允许,会清空所有绑定 | 禁止,编译报错 |
| 外部直接调用 | 允许 | 禁止,仅发布类内部可触发 |
| 安全性 | 低,易误覆盖监听 | 高,规范发布订阅 |
| 适用场景 | 临时回调、内部逻辑 | 游戏 UI、怪物死亡、消息广播 |
3. 观察者模式工程价值
游戏开发高频使用:怪物死亡发布事件,UI 面板、任务系统、成就系统同时订阅,解耦代码,无需硬编码耦合。
四、匿名方法(委托简化写法)
概念
不用单独定义具名函数,创建委托时直接写内嵌代码块,简化一次性回调逻辑。
代码示例
delegate void TestDel(int num);
static void Main(){
// 匿名方法赋值委托
TestDel del = delegate(int n){
Console.WriteLine("数字:" + n);
};
del(99);
}
拓展:内置通用委托
系统预定义无需手写 delegate:
-
Action:无返回值,支持 0~16 个参数; -
Func<T>:带返回值,最后泛型为返回类型。
五、多线程基础
1. 核心概念
单线程:代码自上而下串行执行;
多线程:CPU 分片并行执行多段代码,互不阻塞;
游戏用途:网络收发、资源加载、后台计算,防止主线程界面卡死。
2. 线程抢占问题
多个线程同时读写同一份共享变量时,CPU 切换时机随机,导致数据错乱(售票多窗口超卖、负数票)。
错误示例:多窗口卖票
static int ticket = 200;
static void SellTicket(){
while (ticket > 0){
// 模拟耗时操作,放大线程冲突
System.Threading.Thread.Sleep(1);
ticket--;
Console.WriteLine("剩余票数:" + ticket);
}
}
static void Main(){
// 创建4个售票线程
new System.Threading.Thread(SellTicket).Start();
new System.Threading.Thread(SellTicket).Start();
new System.Threading.Thread(SellTicket).Start();
new System.Threading.Thread(SellTicket);
}
// 运行结果会出现ticket负数、重复票号
3. lock 线程互斥锁(解决抢占冲突)
概念
定义私有只读锁对象,lock(对象)包裹共享数据操作代码块;
同一时间仅一个线程进入锁内代码,其余线程阻塞等待,保证原子操作。
规范
锁对象必须private readonly object,禁止 string、值类型。
修复售票代码
static int ticket = 200;
// 全局锁对象
private static readonly object lockObj = new object();
static void SellTicket(){
while (true){
lock (lockObj){
if (ticket <= 0) break;
System.Threading.Thread.Sleep(1);
ticket--;
Console.WriteLine("剩余票数:" + ticket);
}
}
}
4. 多线程单例(懒汉模式线程安全)
无锁懒汉缺陷:
多线程同时判断inst==null,会创建多个实例,破坏单例唯一性。
双重校验锁安全单例
class ResManager{
private static ResManager inst = null;
private static readonly object lockObj = new object();
// 私有构造
private ResManager() { }
// 只读实例属性
public static ResManager Instance{
get{
if (inst == null){
lock (lockObj){
if (inst == null)
inst = new ResManager();
}
}
return inst;
}
}
}
六、游戏实战:多线程消息队列
场景需求
网络线程阻塞等待服务端消息,主线程刷新 UI;
多线程同时操作队列会冲突,队列读写加锁保证安全。
流程
-
网络子线程:接收消息,lock 后入队;
-
主线程每帧 lock 读取队列,处理消息并清空;
-
锁隔离入队 / 出队,避免数据错乱。
七、面试 & 工程拓展考点
1 自定义 Attribute 三个构造参数各自作用;
2 委托、event 核心区别,事件安全机制;
3 观察者模式实现思路、游戏业务场景;
4 多线程抢占资源产生数据错乱原因,lock 锁使用规范;
5 懒汉单例多线程安全双重校验锁写法;
6 匿名方法、Action/Func 内置委托简化使用;
7 多线程游戏网络、资源加载实战价值。