一份系统性梳理C#进阶编程核心知识的博客,涵盖Socket网络通信、委托与事件机制、Task异步编程模型以及LINQ查询技术
一、网络编程(Socket编程)
1.1 什么是网络编程?
网络编程就是通过网络进行设备与设备之间的数据传输,也称为 Socket网络编程。Socket是网络通信的核心接口,提供了一组方法和属性,使开发者可以轻松地创建连接、发送和接收数据。
1.2 网络通信模型
网络通信遵循分层模型,常见的有OSI七层模型和TCP/IP四层模型:
flowchart TD
subgraph OSI["OSI七层模型"]
A1[应用层] --> A2[表示层] --> A3[会话层] --> A4[传输层] --> A5[网络层] --> A6[数据链路层] --> A7[物理层]
end
subgraph TCPIP["TCP/IP四层模型"]
B1[应用层] --> B4[传输层] --> B5[网络层] --> B6[网络接口层]
end
A1 -.-> B1
A2 -.-> B1
A3 -.-> B1
A4 -.-> B4
A5 -.-> B5
A6 -.-> B6
A7 -.-> B6
1.3 网络通信三要素
| 要素 | 说明 |
|---|---|
| 协议 | 约束数据如何发送和格式化 |
| IP地址 | 标识数据要发送的目标设备 |
| 端口号 | 标识数据要发送到目标设备的哪个应用程序 |
1.4 IPAddress类
IPAddress类位于System.Net命名空间,用于表示和操作IP地址:
csharp
using System.Net;
// 解析IP地址
IPAddress ip = IPAddress.Parse("127.0.0.1");
// 获取回送地址
IPAddress loopback = IPAddress.Loopback;
// 获取任意IP地址
IPAddress any = IPAddress.Any;
// 获取地址族
Console.WriteLine(ip.AddressFamily); // InterNetwork
// 获取字节数组
byte[] bytes = ip.GetAddressBytes();
1.5 UDP协议(无连接通信)
UDP是无连接协议,发送数据前不建立逻辑连接,通信效率高,但不保证数据完整性。
sequenceDiagram
participant Client as UDP客户端
participant Server as UDP服务器
Client->>Server: Send() 发送数据报文
Server-->>Client: Receive() 接收数据报文
Note over Client,Server: 无需建立连接,不保证可靠性
UdpClient示例
csharp
// UDP服务器
UdpClient server = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888));
IPEndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = server.Receive(ref clientEP);
string msg = Encoding.UTF8.GetString(data);
// UDP客户端
UdpClient client = new UdpClient();
IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
byte[] data = Encoding.UTF8.GetBytes("Hello Server");
client.Send(data, data.Length, serverEP);
1.6 TCP协议(面向连接通信)
TCP是面向连接的协议,通信前必须建立连接,保证数据传输的可靠性。
sequenceDiagram
participant Client as TCP客户端
participant Server as TCP服务器
Server->>Server: TcpListener.Start() 监听
Client->>Server: TcpClient.Connect() 连接请求
Server->>Server: AcceptTcpClient() 接受连接
Server->>Client: 建立连接
Client->>Server: NetworkStream 发送数据
Server-->>Client: NetworkStream 响应数据
Client->>Server: Close() 关闭连接
TCP服务端示例
csharp
using System.Net.Sockets;
using System.Net;
TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8888);
listener.Start();
TcpClient client = listener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
// 接收数据
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
string request = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// 发送响应
byte[] response = Encoding.UTF8.GetBytes("Hello Client");
stream.Write(response, 0, response.Length);
client.Close();
listener.Stop();
1.7 TCP与UDP对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠传输 |
| 速度 | 较慢 | 较快 |
| 应用场景 | 文件传输、网页访问 | 视频会议、实时游戏 |
二、委托与事件
2.1 什么是委托?
委托是C#中的引用类型,专门用于存储方法的引用(内存地址)。它约束一类方法的签名规则(返回值类型 + 参数个数、类型、顺序)。
flowchart LR
subgraph 代码区
Add["Add() 方法"]
Multiply["Multiply() 方法"]
end
subgraph 堆内存
DelegateInstance["委托实例<br>存储方法地址"]
end
subgraph 栈内存
CalVar["cal 变量"]
end
CalVar -->|指向| DelegateInstance
DelegateInstance -->|存储地址| Add
DelegateInstance -.->|存储地址| Multiply
style 代码区 fill:#e8f5e9
style 堆内存 fill:#fff3e0
style 栈内存 fill:#e3f2fd
2.2 委托的定义与使用
csharp
// 定义委托类型
public delegate int Calculate(int x, int y);
// 匹配委托签名的方法
static int Add(int x, int y) => x + y;
static int Multiply(int x, int y) => x * y;
// 使用委托
Calculate cal = Add; // 方法组简写
int result = cal(3, 5); // 调用委托
cal = Multiply;
result = cal.Invoke(3, 5); // 使用Invoke调用
2.3 委托的意义
- 实现回调函数:任务完成后自动执行外部方法
- 代码解耦:通用代码与业务逻辑分离
- 动态行为传递:运行时切换执行逻辑
- 统一管理回调:可在委托调用前后增加统一逻辑
2.4 多播委托
委托继承自MulticastDelegate,天然支持多播特性:
flowchart LR
subgraph 主委托["主委托(多播)"]
direction TB
M1["子委托1"] --> M2["子委托2"] --> M3["子委托3"]
end
M1 -.->|绑定| Add["Add()"]
M2 -.->|绑定| Multiply["Multiply()"]
M3 -.->|绑定| Subtract["Subtract()"]
Invoke["调用主委托"] --> M1 & M2 & M3
Invoke -->|按顺序执行| Result["全部方法依次执行"]
csharp
Calculate cal = Add;
cal += Multiply; // 追加方法
cal += Subtract; // 再追加
cal(5, 3); // 依次执行 Add → Multiply → Subtract
// 移除方法
cal -= Multiply;
// 注意:多播委托直接调用只返回最后一个方法的返回值
// 要获取全部返回值,使用GetInvocationList()
foreach (Calculate sub in cal.GetInvocationList())
{
int result = sub(5, 3);
Console.WriteLine(result);
}
2.5 Action和Func
系统提供的预定义委托,无需手动声明:
| 委托类型 | 返回值 | 参数数量 | 使用场景 |
|---|---|---|---|
Action |
void | 0~16个 | 纯执行行为,无需返回 |
Func<TResult> |
有返回值 | 0~15个 | 需要获取返回结果 |
csharp
// Action - 无返回值
Action log = () => Console.WriteLine("日志记录");
Action<int, string> print = (age, name) => Console.WriteLine($"{name}今年{age}岁");
// Func - 有返回值
Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 5); // 8
Func<string> getMsg = () => "Hello World";
2.6 事件(Event)
事件是封装保护后的多播委托,遵循"发布-订阅"设计模式:
flowchart LR
subgraph 发布者
Publisher["Button 发布者"] --> Event["OnClick 事件"]
end
subgraph 订阅者1["订阅者1"]
Handler1["HandleClick()"]
end
subgraph 订阅者2["订阅者2"]
Handler2["LogClick()"]
end
Event -->|通知| Handler1
Event -->|通知| Handler2
Note["⚠️ 事件只能在发布者内部触发<br>外部只能订阅/取消订阅"]
事件与普通委托的区别
| 特性 | 普通委托 | 事件(event) |
|---|---|---|
| 赋值操作 | 支持 = 覆盖 |
禁止 = 赋值 |
| 调用权限 | 类内外都可调用 | 仅在声明类内部调用 |
| 封装安全性 | 较低 | 较高 |
csharp
// 定义事件
public class Button
{
public delegate void ClickHandler();
public event ClickHandler OnClick;
public void Click()
{
Console.WriteLine("按钮被点击");
OnClick?.Invoke(); // 只能在类内部触发
}
}
// 订阅事件
Button btn = new Button();
btn.OnClick += () => Console.WriteLine("开始游戏");
btn.OnClick += () => Console.WriteLine("记录日志");
btn.Click(); // 触发事件
2.7 标准事件写法(EventHandler)
csharp
// 自定义事件参数
public class ButtonClickArgs : EventArgs
{
public string ButtonName { get; set; }
public DateTime ClickTime { get; set; }
}
// 发布者
public class GameButton
{
public event EventHandler<ButtonClickArgs> OnClick;
public void Click()
{
var args = new ButtonClickArgs
{
ButtonName = "开始按钮",
ClickTime = DateTime.Now
};
OnClick?.Invoke(this, args);
}
}
// 订阅者
GameButton btn = new GameButton();
btn.OnClick += (sender, e) =>
{
Console.WriteLine($"{e.ButtonName} 于 {e.ClickTime} 被点击");
};
三、异步编程(Task与async/await)
3.1 同步与异步
| 类型 | 特点 |
|---|---|
| 同步方法 | 调用时需要等待返回结果,才能继续执行 |
| 异步方法 | 调用时无须等待返回结果,可以继续执行 |
flowchart LR
subgraph 同步
S1["任务A"] -->|等待| S2["任务B"] -->|等待| S3["任务C"]
end
subgraph 异步
A1["任务A"] --> A2["任务B"]
A1 -->|不等待| A3["任务C"]
A2 --> A4["任务D"]
A3 --> A4
end
3.2 Task的创建与启动
csharp
// 方式1:new + Start
Task t1 = new Task(() => Console.WriteLine("任务1"));
t1.Start();
// 方式2:Task.Run(最常用)
Task t2 = Task.Run(() => Console.WriteLine("任务2"));
// 方式3:TaskFactory
Task t3 = Task.Factory.StartNew(() => Console.WriteLine("任务3"));
// 带返回值
Task<int> t4 = Task.Run(() =>
{
int sum = 0;
for (int i = 0; i < 100; i++) sum += i;
return sum;
});
int result = t4.Result; // 阻塞等待
3.3 async与await
csharp
// 定义异步方法
async Task<int> GetSumAsync()
{
await Task.Delay(1); // 模拟异步操作
int sum = 0;
for (int i = 0; i < 100; i++) sum += i;
return sum;
}
// 调用异步方法
async Task UseAsync()
{
int result = await GetSumAsync(); // 等待但不阻塞
Console.WriteLine($"结果: {result}");
}
// Main方法中调用
static async Task Main()
{
await UseAsync();
}
3.4 任务等待与延续
flowchart TD
T1["Task1"] -->|Wait| WaitAll["阻塞等待所有任务"]
T2["Task2"] -->|Wait| WaitAll
T3["Task3"] -->|Wait| WaitAll
T1 -->|ContinueWith| CT1["Task1完成后执行"]
T2 -->|ContinueWith| CT2["Task2完成后执行"]
subgraph 非阻塞等待
WhenAny["Task.WhenAny"]
WhenAll["Task.WhenAll"]
end
csharp
// 阻塞等待
Task.WaitAll(t1, t2, t3); // 等待所有完成
Task.WaitAny(t1, t2, t3); // 任意一个完成
// 非阻塞延续
Task.WhenAll(t1, t2).ContinueWith(t =>
{
Console.WriteLine("所有任务完成");
});
// 使用await
await Task.WhenAll(t1, t2, t3);
3.5 取消任务
csharp
using CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("任务已取消");
return;
}
Thread.Sleep(100);
}
}, cts.Token);
// 取消任务
cts.Cancel();
3.6 更新UI的正确方式
在WinForm/WPF中,不能在非UI线程直接操作控件:
csharp
// 错误方式 ❌
Task.Run(() =>
{
textBox1.Text = "更新"; // 抛出异常
});
// 正确方式 ✅ 使用Invoke
Task.Run(() =>
{
this.Invoke(new Action(() =>
{
textBox1.Text = "更新";
}));
});
// 或使用BeginInvoke(异步)
this.BeginInvoke(new Action(() =>
{
textBox1.Text = "更新";
}));
// Lambda简化
this.Invoke(new Action(() => textBox1.Text = "更新"));
Invoke vs BeginInvoke
| 方法 | 同步/异步 | 说明 |
|---|---|---|
Invoke |
同步 | 等待UI线程执行完成,子线程才继续 |
BeginInvoke |
异步 | 提交给UI线程后立即返回,子线程不等待 |
四、LINQ语言集成查询
4.1 什么是LINQ?
LINQ(Language Integrated Query)允许使用类似SQL的查询语法直接操作各种数据源。
flowchart TD
LINQ["LINQ 语言集成查询"]
LINQ --> O["LINQ to Objects<br>(集合/数组)"]
LINQ --> S["LINQ to SQL<br>(数据库)"]
LINQ --> X["LINQ to XML<br>(XML文件)"]
LINQ --> D["LINQ to DataSet<br>(DataSet)"]
O --> Application["统一的查询语法<br>操作不同数据源"]
S --> Application
X --> Application
D --> Application
4.2 查询语法 vs 方法语法
csharp
// 数据源
int[] numbers = { 1, 2, 5, 10, 15, 20, 25 };
// 查询语法
var result1 = from n in numbers
where n > 10 && n % 2 == 0
select n;
// 方法语法(推荐)
var result2 = numbers.Where(n => n > 10 && n % 2 == 0);
4.3 常用标准查询运算符
flowchart LR
subgraph 筛选
Where["Where"]
OfType["OfType"]
end
subgraph 投影
Select["Select"]
SelectMany["SelectMany"]
end
subgraph 排序
OrderBy["OrderBy"]
ThenBy["ThenBy"]
Reverse["Reverse"]
end
subgraph 分组
GroupBy["GroupBy"]
end
subgraph 聚合
Count["Count"]
Sum["Sum"]
Avg["Average"]
Max["Max"]
Min["Min"]
end
subgraph 联接
Join["Join"]
GroupJoin["GroupJoin"]
end
4.4 筛选:Where
csharp
var students = new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 18 },
new Student { Id = 2, Name = "李四", Age = 20 },
new Student { Id = 3, Name = "王五", Age = 16 }
};
// 筛选年龄在12-20之间的学生
var teens = students.Where(s => s.Age > 12 && s.Age < 20);
// OfType:筛选特定类型
var persons = mixedList.OfType<Person>();
4.5 投影:Select / SelectMany
csharp
// Select:提取属性或转换
var names = students.Select(s => s.Name);
var anonym = students.Select(s => new { s.Name, s.Age });
// SelectMany:扁平化嵌套集合
var classes = new List<Class>
{
new Class { Students = new List<string> { "张三", "李四" } },
new Class { Students = new List<string> { "王五", "赵六" } }
};
var allStudents = classes.SelectMany(c => c.Students);
// ["张三", "李四", "王五", "赵六"]
4.6 排序:OrderBy
csharp
// 单级排序
var sorted = products.OrderBy(p => p.Price);
// 多级排序
var sorted2 = products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price);
4.7 分组:GroupBy
csharp
var grouped = products.GroupBy(p => p.Category);
foreach (var group in grouped)
{
Console.WriteLine($"分类: {group.Key}");
foreach (var product in group)
{
Console.WriteLine($" {product.Name}: {product.Price:C}");
}
}
4.8 联接:Join
csharp
// 内连接
var result = from c in customers
join o in orders on c.CId equals o.CId
select new { c.Name, o.Total };
// 方法语法
var result2 = customers.Join(orders,
c => c.CId,
o => o.CId,
(c, o) => new { c.Name, o.Total });
4.9 聚合
csharp
var sales = new List<decimal> { 100.5m, 200.3m, 150.7m, 300.2m };
int count = sales.Count(); // 4
decimal total = sales.Sum(); // 751.7m
decimal avg = sales.Average(); // 187.925m
decimal max = sales.Max(); // 300.2m
decimal min = sales.Min(); // 100.5m
// 自定义聚合
decimal result = sales.Aggregate(0m, (acc, cur) => acc + cur * 0.1m);
4.10 转换运算符
| 方法 | 说明 |
|---|---|
ToList() |
转换为List |
ToArray() |
转换为数组 |
ToDictionary() |
转换为字典 |
ToLookup() |
转换为Lookup |
Cast<T>() |
强制转换为泛型集合 |
OfType<T>() |
筛选并转换为指定类型 |
csharp
// 转换示例
var list = students.ToList();
var array = students.ToArray();
var dict = students.ToDictionary(s => s.Id);
五、总结
本文系统梳理了C#进阶编程的四大核心模块:
| 模块 | 核心要点 |
|---|---|
| 网络编程 | Socket通信、TCP/UDP协议、IPAddress/端口、TcpClient/TcpListener |
| 委托与事件 | 委托作为方法容器、多播委托、Action/Func、事件发布订阅 |
| 异步编程 | Task创建与启动、async/await、取消任务、UI线程安全更新 |
| LINQ | 查询语法与方法语法、标准查询运算符、延迟执行与立即执行 |
掌握这些知识点,是C#开发者从入门到进阶的关键一步。在实际项目中,这些技术往往相互配合使用------例如使用异步Task进行网络请求,通过委托/事件回调通知UI更新,再用LINQ对数据进行查询处理。