C# 语言进阶(九)泛型、内置集合、Socket网络
本篇核心知识点:泛型(泛型类 / 泛型方法)、泛型约束、协变与逆变、C# 内置集合(Hashtable、Dictionary、ArrayList、List、Queue/Stack)、Socket 网络编程、TCP/UDP 协议、服务端 & 客户端基础流程、端口与 IP 规范、阻塞方法
一、C# 泛型(Generic)
1.泛型基础概念
定义:泛型是一种类型参数化机制,使用
T/K/V等占位符代表未知类型,编译时确定真实类型,一套代码适配多种数据类型,消除重复重载。核心优势
编译期类型检查,类型错误提前捕获;
避免值类型装箱、拆箱,提升运行性能;
大幅减少重复模板代码,复用逻辑。
基础语法分类:泛型类、泛型方法、泛型委托、泛型接口。
(1)泛型类
概念
类名后加<T>,整个类内部均可使用T作为数据类型,实例化时尖括号指定真实类型。
完整代码示例
using System; // 单个泛型参数容器类 public class Container<T>{ private T _data; // 存储数据 public void SetData(T val){ _data = val; } // 获取数据 public T GetData(){ return _data; } } static void Main(){ // 指定T为int Container<int> intBox = new Container<int>(); intBox.SetData(100); Console.WriteLine(intBox.GetData()); // 指定T为字符串 Container<string> strBox = new Container<string>(); strBox.SetData("游戏道具"); } 多泛型参数(字典场景 K/V) // 双泛型:Key+Value public class MyDic<K,V>{ private K _key; private V _value; public void Set(K k, V v){ _key = k; _value = v; } } // 使用 MyDic<string,int> dic = new MyDic<string,int>();(2)泛型方法
概念
方法名前加<T>,仅当前方法使用泛型,类本身无需泛型。
代码示例
public class Tool{ // 泛型打印方法 public static void Print<T>(T msg){ Console.WriteLine(msg); } } static void Main(){ Tool.Print<int>(666); Tool.Print<string>("测试文字"); // 编译器自动推导类型,可省略<T> Tool.Print(99.9f); }2.泛型约束 where(核心考点)
概念
通过where T : 约束限制泛型可传入的类型,否则编译器无法识别类型成员,无法调用属性 / 方法。
五类约束规则
值类型约束
where T : structT 只能是 struct、int、bool 等值类型,禁止 class 引用类型;
引用类型约束
where T : classT 只能是类、接口、委托等引用类型,不能是值类型;
无参构造约束
where T : new()T 必须拥有公共无参构造函数,代码内可new T()创建对象;若类只写带参构造,默认无参构造消失,会编译报错;
基类约束
where T : 父类名T 必须是该父类 / 抽象类的派生类,可调用父类全部 public/protected 方法;
接口约束
where T : 接口名T 必须实现指定接口,可调用接口定义方法。
多重约束(逗号分隔)
// 同时满足:引用类型 + 无参构造 + 实现IMsg接口 public class MsgPool<T> where T : class, IGameMsg, new(){ public T CreateMsg(){ return new T(); // 必须new约束才能创建 } }约束报错示例
class Dog{ // 仅带参构造,无默认无参构造 public Dog(string name) { } } // 约束要求无参构造,传入Dog直接报错 class Test<T> where T : new() { } // Test<Dog> 编译失败3.协变 out / 逆变 in(泛型接口 / 委托专用)
协变
out T:输出类型,允许派生类实例赋值给基类变量(返回值场景);逆变
in T:输入类型,允许基类实例传入派生类型参数(入参场景);限制:仅能修饰泛型接口、泛型委托,普通泛型类不能使用 in/out;
作用:拓宽泛型类型转换灵活性,编译校验类型安全。
4.泛型委托
概念
委托参数、返回值可使用泛型,一套委托适配任意参数 / 返回类型,替代大量重复委托定义。
// 泛型委托:接收T,返回R delegate R Calc<T,R>(T input); static void Main(){ Calc<int, string> func = num => num.ToString(); Console.WriteLine(func(123)); }5.泛型拓展优缺点
优点:类型安全、无装箱拆箱、代码复用;
缺点:无法直接对泛型做加减等运算(无统一数值接口),必须加约束;
工程场景:游戏道具容器、消息池、自定义链表 / 队列优先使用泛型。
二、C# 内置集合类(容器)
1.Hashtable 非泛型哈希表
概念
系统早期键值容器,key/value 全部为 object 类型,无泛型约束,存入值类型自动装箱,取出强制拆箱,性能差。
特性
键不允许重复,无序存储;
取值方式:
ht[key],不存在返回 null,不抛异常;遍历方式:遍历 Keys 集合,或迭代器遍历 DictionaryEntry;
代码示例
using System.Collections; static void Main(){ Hashtable ht = new Hashtable(); ht.Add("玩家1", 2000); ht.Add("玩家2", 3500); // 按键遍历 foreach(string key in ht.Keys){ Console.WriteLine($"{key}:{ht[key]}"); } }缺陷
任意类型可存入,类型混杂时取值极易转换异常,新项目优先 Dictionary。
2.Dictionary<K,V> 泛型字典(游戏高频)
概念
泛型哈希键值容器,严格约束 Key、Value 类型,无装箱拆箱,性能远优于 Hashtable。
核心特性
Key 唯一,底层哈希表;
两种取值:
dic[key]:键不存在直接程序崩溃;dic.TryGetValue(key, out var val):安全取值,返回 bool,推荐项目使用;工程用途:资源缓存(名字对应预制 / 音效)、玩家数据存储;
完整代码
using System.Collections.Generic; static void Main(){ Dictionary<string, int> playerHp = new Dictionary<string, int>(); player.Add("战士", 5000); // 安全取值 int hp; if(playerHp.TryGetValue("战士", out hp)){ Console.WriteLine("血量:" + hp); } else{ Console.WriteLine("该玩家不存在"); } }3.ArrayList 非泛型动态数组
概念
早期动态数组,存储 object,任意类型均可添加,无类型约束,频繁装箱拆箱,新项目废弃。
缺陷:同一容器存 int、string、自定义类时,取出强制转换极易报错;
替代方案:泛型List<T>
4.List<T> 泛型动态数组(最常用)
概念
长度可变泛型数组,连续内存,支持增删改查、排序、遍历;
特性:
尾部添加 O (1),中间插入需要挪动元素;
泛型约束,类型统一无装箱;
游戏场景:道具列表、怪物列表、玩家列表;
5.Queue<T> / Stack<T> 泛型队列、栈
Queue:FIFO 先进先出,网络消息队列核心载体;
Stack:LIFO 先进后出,DFS、撤销功能;
多线程操作容器时,读写必须加
lock互斥锁,防止数据错乱。
集合对比总结
| 容器 | 泛型 | 底层 | 性能 | 适用场景 |
|---|---|---|---|---|
| Hashtable | 否 | 哈希 | 差(装箱) | 老旧项目兼容 |
| Dictionary | 是 | 哈希 | 高 | 资源缓存、键值映射 |
| ArrayList | 否 | 数组 | 差 | 淘汰 |
| List<T> | 是 | 动态数组 | 高 | 批量同类型数据 |
三、Socket 网络编程基础(TCP/UDP)
1.Socket 核心概念
Socket(套接字)是操作系统提供网络通信接口,所有客户端、服务器数据收发底层全部依赖 Socket,分为 TCP、UDP 两套传输协议。
2.TCP 与 UDP 完整对比
TCP 传输控制协议
特性:面向连接、可靠传输、字节流;
连接流程:三次握手建立连接,四次挥手断开;
优点:数据不丢失、不乱序、重传机制保证可靠;
缺点:握手挥手额外开销,存在网络延迟;
适用场景:登录、任务、交易、回合制战斗等核心业务,数据不能丢失。
UDP 数据报协议
特性:无连接、不可靠、数据报;
优点:无握手,传输速度极快,低延迟;
缺点:无重传,数据可能丢失、乱序;
优化方案:手动添加消息序列号、丢失重传逻辑;
适用场景:FPS / 赛车实时位置同步、角色动作、特效同步;
市面游戏主流方案:TCP 存核心数据,UDP 同步实时状态,混合使用。
通俗类比
TCP = 打电话,必须接通才能对话,掉线双方感知;
UDP = 发短信,直接发送,对方不在线消息丢失。
3.IP 地址 + 端口 概念
IP:设备网络唯一地址,本机测试固定回环地址
127.0.0.1;IPv4 四段 0~255 数字;端口:一台机器内程序区分标识,范围 0~65535;
规范:系统占用知名端口(80 网页、3306 数据库)不要使用,项目选用 8000~10000 区间;
冲突:同一端口同一时间仅能一个程序占用,否则绑定失败。
4.TCP Socket 服务端完整执行流程
引入命名空间:
using System.Net; using System.Net.Sockets;创建 Socket,协议类型选
SocketType.Stream(TCP 字节流);Bind 绑定本机 IP 与端口;
Listen 设置最大监听连接数;
Accept 阻塞等待客户端连接(无客户端则卡死,阻塞方法);
连接成功得到客户端专属 Socket;
Send 发送数据、Receive 接收数据;
Shutdown/Close 关闭套接字。
服务端关键代码片段
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint ep = new IPEndPoint(ip, 8080); serverSocket.Bind(ep); serverSocket.Listen(100); Console.WriteLine("服务器等待连接..."); Socket clientSocket = serverSocket.Accept(); // 阻塞 Console.WriteLine("客户端已连接");5.TCP Socket 客户端执行流程
创建同类型 Socket
构造服务端 IP + 端口端点
Connect 发起连接(无需 Bind、Listen、Accept)
连接成功后 Send/Receive 收发数据
客户端关键代码
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint serverEp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); client.Connect(serverEp);6.阻塞方法说明
Accept()、Receive()、Console.ReadKey()均为阻塞方法,执行时线程暂停,等待触发条件才继续;
游戏主线程直接使用会界面卡死,网络操作必须新开子线程。
7.TCP 特有问题:粘包
TCP 基于连续字节流,多条消息连续发送时字节合并,无法区分消息边界;
解决方案:每条消息头部附加数据长度,接收时按长度拆分数据包;
UDP 无粘包问题,一次发送对应一次接收。
四、拓展
1 泛型约束五类作用,new 无参构造使用场景;
2 Dictionary 对比 Hashtable 性能差异,TryGetValue 安全取值;
3 List/Queue 多线程操作必须加 lock 锁;
4 TCP/UDP 核心差异、游戏业务选型;
5 Socket 服务端与客户端代码流程区分;
6 端口取值规范、127.0.0.1 回环地址作用;
7 阻塞线程带来的游戏界面卡死解决方案(多线程异步网络)。
