C# 语言进阶(九)泛型、内置集合、Socket网络

📅 2026/7/7 4:18:40 👁️ 阅读次数 📝 编程学习
C# 语言进阶(九)泛型、内置集合、Socket网络

本篇核心知识点:泛型(泛型类 / 泛型方法)、泛型约束、协变与逆变、C# 内置集合(Hashtable、Dictionary、ArrayList、List、Queue/Stack)、Socket 网络编程、TCP/UDP 协议、服务端 & 客户端基础流程、端口与 IP 规范、阻塞方法

一、C# 泛型(Generic)

1.泛型基础概念

  1. 定义:泛型是一种类型参数化机制,使用T/K/V等占位符代表未知类型,编译时确定真实类型,一套代码适配多种数据类型,消除重复重载。

  2. 核心优势

    编译期类型检查,类型错误提前捕获;

    避免值类型装箱、拆箱,提升运行性能;

    大幅减少重复模板代码,复用逻辑。

  3. 基础语法分类:泛型类、泛型方法、泛型委托、泛型接口。

(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 : 约束限制泛型可传入的类型,否则编译器无法识别类型成员,无法调用属性 / 方法。

五类约束规则
  1. 值类型约束where T : struct

    T 只能是 struct、int、bool 等值类型,禁止 class 引用类型;

  2. 引用类型约束where T : class

    T 只能是类、接口、委托等引用类型,不能是值类型;

  3. 无参构造约束where T : new()T 必须拥有公共无参构造函数,代码内可new T()创建对象;

    若类只写带参构造,默认无参构造消失,会编译报错;

  4. 基类约束where T : 父类名

    T 必须是该父类 / 抽象类的派生类,可调用父类全部 public/protected 方法;

  5. 接口约束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(泛型接口 / 委托专用)

  1. 协变out T:输出类型,允许派生类实例赋值给基类变量(返回值场景);

  2. 逆变in T:输入类型,允许基类实例传入派生类型参数(入参场景);

  3. 限制:仅能修饰泛型接口、泛型委托,普通泛型类不能使用 in/out;

  4. 作用:拓宽泛型类型转换灵活性,编译校验类型安全。

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.泛型拓展优缺点

  1. 优点:类型安全、无装箱拆箱、代码复用;

  2. 缺点:无法直接对泛型做加减等运算(无统一数值接口),必须加约束;

  3. 工程场景:游戏道具容器、消息池、自定义链表 / 队列优先使用泛型。

二、C# 内置集合类(容器)

1.Hashtable 非泛型哈希表

概念

系统早期键值容器,key/value 全部为 object 类型,无泛型约束,存入值类型自动装箱,取出强制拆箱,性能差。

特性
  1. 键不允许重复,无序存储;

  2. 取值方式:ht[key],不存在返回 null,不抛异常;

  3. 遍历方式:遍历 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。

核心特性
  1. Key 唯一,底层哈希表;

  2. 两种取值:

    dic[key]:键不存在直接程序崩溃;

    dic.TryGetValue(key, out var val):安全取值,返回 bool,推荐项目使用;

  3. 工程用途:资源缓存(名字对应预制 / 音效)、玩家数据存储;

完整代码
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> 泛型动态数组(最常用)

概念

长度可变泛型数组,连续内存,支持增删改查、排序、遍历;

特性:
  1. 尾部添加 O (1),中间插入需要挪动元素;

  2. 泛型约束,类型统一无装箱;

  3. 游戏场景:道具列表、怪物列表、玩家列表;

5.Queue<T> / Stack<T> 泛型队列、栈

  1. Queue:FIFO 先进先出,网络消息队列核心载体;

  2. Stack:LIFO 先进后出,DFS、撤销功能;

  3. 多线程操作容器时,读写必须加lock互斥锁,防止数据错乱。

集合对比总结

容器泛型底层性能适用场景
Hashtable哈希差(装箱)老旧项目兼容
Dictionary哈希资源缓存、键值映射
ArrayList数组淘汰
List<T>动态数组批量同类型数据

三、Socket 网络编程基础(TCP/UDP)

1.Socket 核心概念

Socket(套接字)是操作系统提供网络通信接口,所有客户端、服务器数据收发底层全部依赖 Socket,分为 TCP、UDP 两套传输协议。

2.TCP 与 UDP 完整对比

TCP 传输控制协议
  1. 特性:面向连接、可靠传输、字节流

  2. 连接流程:三次握手建立连接,四次挥手断开;

  3. 优点:数据不丢失、不乱序、重传机制保证可靠;

  4. 缺点:握手挥手额外开销,存在网络延迟;

  5. 适用场景:登录、任务、交易、回合制战斗等核心业务,数据不能丢失。

UDP 数据报协议
  1. 特性:无连接、不可靠、数据报

  2. 优点:无握手,传输速度极快,低延迟;

  3. 缺点:无重传,数据可能丢失、乱序;

  4. 优化方案:手动添加消息序列号、丢失重传逻辑;

  5. 适用场景:FPS / 赛车实时位置同步、角色动作、特效同步;

  6. 市面游戏主流方案:TCP 存核心数据,UDP 同步实时状态,混合使用。

通俗类比

TCP = 打电话,必须接通才能对话,掉线双方感知;

UDP = 发短信,直接发送,对方不在线消息丢失。

3.IP 地址 + 端口 概念

  1. IP:设备网络唯一地址,本机测试固定回环地址127.0.0.1;IPv4 四段 0~255 数字;

  2. 端口:一台机器内程序区分标识,范围 0~65535;

  3. 规范:系统占用知名端口(80 网页、3306 数据库)不要使用,项目选用 8000~10000 区间;

  4. 冲突:同一端口同一时间仅能一个程序占用,否则绑定失败。

4.TCP Socket 服务端完整执行流程

  1. 引入命名空间:using System.Net; using System.Net.Sockets;

  2. 创建 Socket,协议类型选SocketType.Stream(TCP 字节流);

  3. Bind 绑定本机 IP 与端口;

  4. Listen 设置最大监听连接数;

  5. Accept 阻塞等待客户端连接(无客户端则卡死,阻塞方法);

  6. 连接成功得到客户端专属 Socket;

  7. Send 发送数据、Receive 接收数据;

  8. 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 客户端执行流程

  1. 创建同类型 Socket

  2. 构造服务端 IP + 端口端点

  3. Connect 发起连接(无需 Bind、Listen、Accept)

  4. 连接成功后 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 阻塞线程带来的游戏界面卡死解决方案(多线程异步网络)。