C# 核心知识精讲:多线程、网络编程(UDP/TCP)与 LINQ 方法全解析

本文系统梳理 C# 开发中四大核心模块:多线程编程、UDP 网络通信、TCP 网络通信以及 LINQ 数据查询方法。每个模块均配有概念讲解与可运行代码示例,适合学习复习与面试备考。

前言

在 C# 开发中,多线程、网络编程和 LINQ 是三个高频使用且相互关联的核心技术领域:

  • 多线程是提升程序并发性能、避免 UI 阻塞的基础;
  • **网络编程(UDP/TCP)**是实现分布式通信、客户端-服务器架构的关键;
  • LINQ 则极大简化了数据查询与集合操作,是 C# 开发者必备技能。

本文将从基础概念出发,结合实际代码示例,逐一深入讲解这三大模块。


一、C# 多线程编程

1.1 多线程基础概念

进程(Process):操作系统资源分配的基本单位,一个应用程序运行时就是一个进程。

线程(Thread):CPU 调度的最小单位,一个进程可以包含多个线程,它们共享进程的内存资源。

多线程的优势

  • 提高程序响应速度(UI 线程不被阻塞)
  • 充分利用多核 CPU 资源
  • 并行处理耗时任务(IO、计算等)

1.2 Thread 类

System.Threading.Thread 是最基础的线程类,用于创建和控制线程。

using System;
using System.Threading;

class ThreadDemo
{
    static void Main()
    {
        // 创建线程
        Thread t = new Thread(Worker);
        t.Name = "工作线程1";
        t.IsBackground = true; // 设置为后台线程
        t.Start(); // 启动线程

        // 主线程继续执行
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"主线程: {i}");
            Thread.Sleep(100);
        }

        t.Join(); // 等待工作线程完成
        Console.WriteLine("程序结束");
    }

    static void Worker()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}: {i}");
            Thread.Sleep(150);
        }
    }
}

常用属性与方法

成员 说明
Name 获取或设置线程名称
IsBackground 获取或设置是否为后台线程
IsAlive 获取线程是否处于活动状态
Start() 启动线程
Sleep(int) 静态方法,暂停当前线程指定毫秒
Join() 等待线程终止
Abort() 终止线程(已不推荐使用)

1.3 线程池 ThreadPool

线程池维护一组工作线程,避免频繁创建和销毁线程带来的开销。

using System;
using System.Threading;

class ThreadPoolDemo
{
    static void Main()
    {
        // 将工作项排入线程池
        for (int i = 0; i < 5; i++)
        {
            ThreadPool.QueueUserWorkItem(Worker, i);
        }

        Console.WriteLine("主线程继续运行...");
        Thread.Sleep(2000); // 等待线程池任务完成
        Console.WriteLine("所有任务完成");
    }

    static void Worker(object state)
    {
        int taskId = (int)state;
        Console.WriteLine($"任务 {taskId} 在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
        Thread.Sleep(500);
    }
}

线程池特点

  • 线程复用,减少创建销毁开销
  • 自动管理线程数量
  • 适合短时间运行的任务
  • 不适合长时间运行的任务(会占用池资源)

1.4 Task 与 async/await

TaskTask<T> 是 .NET 推荐的异步编程模型,配合 async/await 关键字使用。

using System;
using System.Threading.Tasks;

class TaskDemo
{
    static async Task Main()
    {
        Console.WriteLine("开始异步操作...");

        // 方式一:Task.Run
        Task<string> task1 = Task.Run(() =>
        {
            Task.Delay(1000).Wait();
            return "Task.Run 结果";
        });

        // 方式二:async 方法
        string result2 = await DoWorkAsync();

        string result1 = await task1;
        Console.WriteLine(result1);
        Console.WriteLine(result2);

        // 并行等待多个任务
        Task[] tasks = new Task[]
        {
            Task.Delay(500),
            Task.Delay(800),
            Task.Delay(300)
        };
        await Task.WhenAll(tasks);
        Console.WriteLine("所有并行任务完成");
    }

    static async Task<string> DoWorkAsync()
    {
        await Task.Delay(1500);
        return "async 方法结果";
    }
}

Task 常用方法

方法 说明
Task.Run() 将工作排入线程池并返回 Task
Task.Delay() 异步延迟
Task.WhenAll() 等待所有任务完成
Task.WhenAny() 等待任一任务完成
ContinueWith() 任务完成后继续执行
Result 同步获取结果(可能死锁,慎用)

1.5 线程安全与锁

多线程环境下,共享资源的访问需要同步控制,否则会出现数据竞争。

using System;
using System.Threading;
using System.Threading.Tasks;

class LockDemo
{
    private static int _counter = 0;
    private static readonly object _lockObj = new object();

    static async Task Main()
    {
        // 不加锁的情况
        Task[] tasks = new Task[10];
        for (int i = 0; i < 10; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                for (int j = 0; j < 1000; j++)
                {
                    lock (_lockObj) // 加锁保证线程安全
                    {
                        _counter++;
                    }
                }
            });
        }

        await Task.WhenAll(tasks);
        Console.WriteLine($"最终计数: {_counter}"); // 预期 10000
    }
}

常用同步机制

机制 说明
lock 最常用的互斥锁,基于 Monitor
Monitor 提供 Enter/Exit/Pulse/Wait 等方法
Mutex 跨进程互斥锁
Semaphore 信号量,限制并发数量
ReaderWriterLockSlim 读写分离锁,多读单写
Interlocked 原子操作(递增、交换等)
ConcurrentQueue<T> 线程安全集合

二、C# 网络编程 — UDP

2.1 UDP 简介

UDP(User Datagram Protocol,用户数据报协议) 是一种无连接的传输层协议:

  • 无连接:发送数据前不需要建立连接
  • 不可靠:不保证数据送达、不保证顺序、不重传
  • 开销小:头部只有 8 字节,传输效率高
  • 支持广播和多播

适用场景:视频直播、DNS 查询、游戏实时通信、物联网传感器数据等对实时性要求高、可容忍少量丢包的场景。

2.2 UdpClient 类

System.Net.Sockets.UdpClient 是 .NET 中用于 UDP 通信的封装类。

常用方法

方法 说明
UdpClient(int port) 绑定到指定端口
Send(byte[], int, IPEndPoint) 发送数据报
Receive(ref IPEndPoint) 接收数据报(阻塞)
ReceiveAsync() 异步接收数据报
Close() 关闭连接

2.3 UDP 服务端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpServer
{
    static async Task Main(string[] args)
    {
        int port = 8888;
        UdpClient udpServer = new UdpClient(port);
        Console.WriteLine($"UDP 服务端已启动,监听端口 {port}...");

        try
        {
            while (true)
            {
                // 异步接收数据
                UdpReceiveResult result = await udpServer.ReceiveAsync();
                string message = Encoding.UTF8.GetString(result.Buffer);
                IPEndPoint remoteEndPoint = result.RemoteEndPoint;

                Console.WriteLine($"收到来自 {remoteEndPoint} 的消息: {message}");

                // 回复客户端
                string reply = $"服务端已收到: {message}";
                byte[] replyBytes = Encoding.UTF8.GetBytes(reply);
                await udpServer.SendAsync(replyBytes, replyBytes.Length, remoteEndPoint);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"服务端异常: {ex.Message}");
        }
        finally
        {
            udpServer.Close();
        }
    }
}

2.4 UDP 客户端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpClientDemo
{
    static async Task Main(string[] args)
    {
        string serverIp = "127.0.0.1";
        int serverPort = 8888;

        UdpClient client = new UdpClient();
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);

        try
        {
            // 发送消息
            string message = "Hello, UDP Server!";
            byte[] data = Encoding.UTF8.GetBytes(message);
            await client.SendAsync(data, data.Length, serverEndPoint);
            Console.WriteLine($"已发送: {message}");

            // 接收回复(设置超时)
            client.Client.ReceiveTimeout = 3000;
            UdpReceiveResult result = await client.ReceiveAsync();
            string reply = Encoding.UTF8.GetString(result.Buffer);
            Console.WriteLine($"收到回复: {reply}");
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"网络异常: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

2.5 UDP 广播示例

UDP 支持向同一网段内所有主机发送广播消息。

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpBroadcast
{
    static async Task Main(string[] args)
    {
        int port = 9000;
        UdpClient broadcaster = new UdpClient();
        broadcaster.EnableBroadcast = true; // 启用广播

        IPEndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, port);

        for (int i = 0; i < 5; i++)
        {
            string msg = $"广播消息 #{i + 1} - {DateTime.Now:HH:mm:ss}";
            byte[] data = Encoding.UTF8.GetBytes(msg);
            await broadcaster.SendAsync(data, data.Length, broadcastEndPoint);
            Console.WriteLine($"已广播: {msg}");
            await Task.Delay(1000);
        }

        broadcaster.Close();
        Console.WriteLine("广播结束");
    }
}

三、C# 网络编程 — TCP

3.1 TCP 简介

TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的传输层协议:

  • 面向连接:通信前需通过三次握手建立连接
  • 可靠传输:保证数据有序、无差错、不丢失、不重复
  • 流量控制与拥塞控制:自动调整发送速率
  • 全双工:连接建立后双方可同时收发数据

适用场景:文件传输(FTP/HTTP)、邮件(SMTP)、远程登录(SSH)、数据库连接等要求数据完整性的场景。

3.2 TcpListener 与 TcpClient

用途
TcpListener TCP 服务端,监听客户端连接请求
TcpClient TCP 客户端,用于连接服务端并收发数据
NetworkStream 网络数据流,用于读写数据

3.3 TCP 服务端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpServer
{
    static async Task Main(string[] args)
    {
        IPAddress ip = IPAddress.Any;
        int port = 8888;
        TcpListener listener = new TcpListener(ip, port);
        listener.Start();
        Console.WriteLine($"TCP 服务端已启动,监听 {ip}:{port}");

        try
        {
            while (true)
            {
                // 等待客户端连接
                TcpClient client = await listener.AcceptTcpClientAsync();
                Console.WriteLine($"客户端已连接: {client.Client.RemoteEndPoint}");

                // 每个客户端独立线程处理
                _ = HandleClientAsync(client);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"服务端异常: {ex.Message}");
        }
        finally
        {
            listener.Stop();
        }
    }

    static async Task HandleClientAsync(TcpClient client)
    {
        try
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[1024];

            while (true)
            {
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0) // 客户端断开
                {
                    Console.WriteLine("客户端断开连接");
                    break;
                }

                string received = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"收到: {received}");

                // 回显(Echo)
                byte[] reply = Encoding.UTF8.GetBytes($"服务端回显: {received}");
                await stream.WriteAsync(reply, 0, reply.Length);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理客户端异常: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

3.4 TCP 客户端示例

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpClientDemo
{
    static async Task Main(string[] args)
    {
        string serverIp = "127.0.0.1";
        int port = 8888;

        TcpClient client = new TcpClient();
        try
        {
            // 连接服务端
            await client.ConnectAsync(serverIp, port);
            Console.WriteLine($"已连接到服务端 {serverIp}:{port}");

            NetworkStream stream = client.GetStream();

            // 发送消息
            string message = "Hello, TCP Server!";
            byte[] data = Encoding.UTF8.GetBytes(message);
            await stream.WriteAsync(data, 0, data.Length);
            Console.WriteLine($"已发送: {message}");

            // 接收回复
            byte[] buffer = new byte[1024];
            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
            string reply = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"收到: {reply}");
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

3.5 TCP 粘包问题与解决方案

TCP 是面向流的协议,数据没有明确的边界,可能出现粘包(多个数据包合并)或拆包(一个数据包被拆分)的情况。

常见解决方案

  1. 固定长度:每条消息固定长度
  2. 特殊分隔符:用特定字符(如 \n\r\n)分隔
  3. 长度前缀:消息前加 4 字节表示消息长度(推荐)
// 长度前缀方式发送消息
public static async Task SendMessageAsync(NetworkStream stream, string message)
{
    byte[] body = Encoding.UTF8.GetBytes(message);
    byte[] lengthPrefix = BitConverter.GetBytes(body.Length);

    // 先发送长度(4字节),再发送内容
    await stream.WriteAsync(lengthPrefix, 0, 4);
    await stream.WriteAsync(body, 0, body.Length);
}

// 长度前缀方式接收消息
public static async Task<string> ReceiveMessageAsync(NetworkStream stream)
{
    byte[] lengthBuffer = new byte[4];
    int totalRead = 0;

    // 读取4字节长度
    while (totalRead < 4)
    {
        int read = await stream.ReadAsync(lengthBuffer, totalRead, 4 - totalRead);
        if (read == 0) return null;
        totalRead += read;
    }

    int bodyLength = BitConverter.ToInt32(lengthBuffer, 0);
    byte[] bodyBuffer = new byte[bodyLength];
    totalRead = 0;

    // 读取完整消息体
    while (totalRead < bodyLength)
    {
        int read = await stream.ReadAsync(bodyBuffer, totalRead, bodyLength - totalRead);
        if (read == 0) return null;
        totalRead += read;
    }

    return Encoding.UTF8.GetString(bodyBuffer);
}

3.6 UDP 与 TCP 对比

特性 UDP TCP
连接性 无连接 面向连接
可靠性 不可靠 可靠
顺序性 不保证顺序 保证顺序
传输速度 相对较慢
头部开销 8 字节 20 字节(不含选项)
流量控制
拥塞控制
广播/多播 支持 不支持
适用场景 实时音视频、游戏、DNS 文件传输、Web、邮件

四、LINQ 方法详解

4.1 LINQ 简介

LINQ(Language Integrated Query,语言集成查询) 是 C# 3.0 引入的一项特性,允许使用类似 SQL 的语法直接在 C# 代码中查询数据。

LINQ 的两种语法

  1. 查询语法(Query Syntax):类似 SQL,使用 fromwhereselect 等关键字
  2. 方法语法(Method Syntax):使用扩展方法链式调用
// 查询语法
var querySyntax = from s in students
                  where s.Age > 18
                  orderby s.Name
                  select s;

// 方法语法(等价)
var methodSyntax = students
    .Where(s => s.Age > 18)
    .OrderBy(s => s.Name)
    .Select(s => s);

4.2 数据准备

以下示例使用如下测试数据:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Major { get; set; }
    public decimal Score { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机", Score = 85.5m },
    new Student { Id = 2, Name = "李四", Age = 22, Major = "数学", Score = 92.0m },
    new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机", Score = 78.5m },
    new Student { Id = 4, Name = "赵六", Age = 21, Major = "英语", Score = 88.0m },
    new Student { Id = 5, Name = "钱七", Age = 20, Major = "数学", Score = 95.5m },
    new Student { Id = 6, Name = "孙八", Age = 23, Major = "计算机", Score = 67.0m }
};

4.3 筛选与投影

Where — 条件筛选
// 筛选计算机专业且分数大于80的学生
var result = students.Where(s => s.Major == "计算机" && s.Score > 80);

foreach (var s in result)
    Console.WriteLine($"{s.Name} - {s.Major} - {s.Score}");
Select — 投影(转换)
// 只选取姓名和分数,投影为匿名类型
var names = students.Select(s => new { s.Name, s.Score });

foreach (var n in names)
    Console.WriteLine($"{n.Name}: {n.Score}");

// Select 带索引重载
var indexed = students.Select((s, index) => $"{index + 1}. {s.Name}");
SelectMany — 扁平化集合
// 将嵌套集合扁平化为单一序列
List<string[]> courses = new List<string[]>
{
    new string[] { "C#", "Java" },
    new string[] { "Python", "Go", "Rust" }
};

var allCourses = courses.SelectMany(c => c);
// 结果: C#, Java, Python, Go, Rust

4.4 排序

OrderBy / OrderByDescending
// 按分数升序
var ascending = students.OrderBy(s => s.Score);

// 按分数降序
var descending = students.OrderByDescending(s => s.Score);
ThenBy / ThenByDescending — 多级排序
// 先按专业升序,再按分数降序
var multiSort = students
    .OrderBy(s => s.Major)
    .ThenByDescending(s => s.Score);
Reverse — 反转序列
var reversed = students.Reverse();

4.5 分组

GroupBy — 分组
// 按专业分组
var groups = students.GroupBy(s => s.Major);

foreach (var group in groups)
{
    Console.WriteLine($"专业: {group.Key},人数: {group.Count()}");
    foreach (var s in group)
        Console.WriteLine($"  - {s.Name} ({s.Score})");
}
ToLookup — 生成查找表
// ToLookup 生成 ILookup<TKey, TElement>,类似字典但值是集合
var lookup = students.ToLookup(s => s.Major);

foreach (var s in lookup["计算机"])
    Console.WriteLine(s.Name);

4.6 集合操作

Distinct — 去重
int[] numbers = { 1, 2, 2, 3, 3, 3, 4 };
var unique = numbers.Distinct(); // 1, 2, 3, 4
Union / Intersect / Except
int[] a = { 1, 2, 3, 4 };
int[] b = { 3, 4, 5, 6 };

var union = a.Union(b);        // 并集: 1,2,3,4,5,6
var intersect = a.Intersect(b); // 交集: 3,4
var except = a.Except(b);       // 差集(a有b无): 1,2
Concat — 连接序列
var concat = a.Concat(b); // 1,2,3,4,3,4,5,6(不去重)

4.7 聚合操作

方法 说明 示例
Count() 元素数量 students.Count()
Count(predicate) 满足条件的数量 students.Count(s => s.Score > 80)
Sum() 求和 students.Sum(s => s.Score)
Average() 平均值 students.Average(s => s.Score)
Max() 最大值 students.Max(s => s.Score)
Min() 最小值 students.Min(s => s.Score)
Aggregate() 自定义聚合 见下方示例
// Aggregate 自定义聚合:计算所有分数的乘积(示例)
decimal product = students.Aggregate(1m, (acc, s) => acc * s.Score);

// Aggregate 拼接字符串
string allNames = students.Aggregate("", (acc, s) => acc + s.Name + ", ");
// 结果: "张三, 李四, 王五, 赵六, 钱七, 孙八, "

4.8 元素操作

方法 说明 异常行为
First() 第一个元素 无元素抛异常
FirstOrDefault() 第一个元素或默认值 无元素返回默认值
Last() 最后一个元素 无元素抛异常
LastOrDefault() 最后一个或默认值 无元素返回默认值
Single() 唯一元素 0个或多个都抛异常
SingleOrDefault() 唯一元素或默认值 多个抛异常
ElementAt(int) 指定索引元素 越界抛异常
ElementAtOrDefault(int) 指定索引或默认值 越界返回默认值
var first = students.First(s => s.Major == "计算机"); // 张三
var firstOrDefault = students.FirstOrDefault(s => s.Major == "物理"); // null

var single = students.Single(s => s.Id == 1); // 张三
// students.Single(s => s.Major == "计算机") // 抛异常(有多个)

4.9 量词操作

方法 说明
Any() 是否有元素
Any(predicate) 是否有满足条件的元素
All(predicate) 是否所有元素都满足条件
Contains(value) 是否包含指定元素
bool hasHighScore = students.Any(s => s.Score > 90); // true
bool allPassed = students.All(s => s.Score >= 60); // true
bool hasZhang = students.Select(s => s.Name).Contains("张三"); // true

4.10 分区操作

方法 说明
Take(n) 取前 n 个元素
Skip(n) 跳过前 n 个元素
TakeWhile(predicate) 取满足条件的元素,直到不满足
SkipWhile(predicate) 跳过满足条件的元素,直到不满足
var top3 = students.OrderByDescending(s => s.Score).Take(3); // 前三名
var page2 = students.OrderBy(s => s.Id).Skip(3).Take(3); // 分页第二页

4.11 连接操作

Join — 内连接
// 准备第二个数据源
var departments = new List<Department>
{
    new Department { Major = "计算机", DeptName = "信息工程学院" },
    new Department { Major = "数学", DeptName = "理学院" },
    new Department { Major = "英语", DeptName = "外国语学院" }
};

// 内连接:学生与学院信息关联
var joinResult = students.Join(
    departments,
    s => s.Major,          // 左表键
    d => d.Major,          // 右表键
    (s, d) => new { s.Name, s.Major, d.DeptName }
);

foreach (var item in joinResult)
    Console.WriteLine($"{item.Name} - {item.Major} - {item.DeptName}");
GroupJoin — 分组连接(左外连接)
var groupJoin = departments.GroupJoin(
    students,
    d => d.Major,
    s => s.Major,
    (d, studentGroup) => new
    {
        d.DeptName,
        Students = studentGroup.ToList()
    }
);

4.12 转换操作

方法 说明
ToList() 转为 List<T>
ToArray() 转为数组
ToDictionary() 转为字典
ToHashSet() 转为 HashSet
Cast<T>() 强制转换元素类型
OfType<T>() 筛选指定类型元素
AsEnumerable() 转为 IEnumerable<T>
AsQueryable() 转为 IQueryable<T>
// ToDictionary
var dict = students.ToDictionary(s => s.Id, s => s.Name);

// OfType 筛选类型
object[] mixed = { 1, "hello", 2, "world", 3 };
var strings = mixed.OfType<string>(); // "hello", "world"
var ints = mixed.OfType<int>();       // 1, 2, 3

4.13 生成操作

方法 说明
Enumerable.Range(start, count) 生成整数序列
Enumerable.Repeat(element, count) 重复元素生成序列
Enumerable.Empty<T>() 空序列
var range = Enumerable.Range(1, 10);       // 1,2,...,10
var repeat = Enumerable.Repeat("A", 5);    // A,A,A,A,A
var empty = Enumerable.Empty<int>();        // 空序列

4.14 延迟执行 vs 立即执行

延迟执行(Deferred Execution):查询在迭代时才执行,每次迭代重新计算。

var query = students.Where(s => s.Score > 80); // 此时未执行
students.Add(new Student { Id = 7, Name = "周九", Age = 20, Major = "计算机", Score = 90 });
foreach (var s in query) // 迭代时执行,包含新添加的周九
    Console.WriteLine(s.Name);

立即执行(Immediate Execution):调用时立即执行并缓存结果。

var list = students.Where(s => s.Score > 80).ToList(); // 立即执行
students.Add(new Student { Id = 8, Name = "吴十", Age = 20, Major = "计算机", Score = 91 });
// list 中不包含吴十,因为已经执行完毕

判断规则

  • 返回 IEnumerable<T> / IQueryable<T> → 通常延迟执行
  • 返回单个值(CountSumFirstAny 等)或集合(ToListToArrayToDictionary)→ 立即执行

4.15 LINQ 综合实战示例

// 需求:统计每个专业的平均分、最高分、人数,按平均分降序排列
var report = students
    .GroupBy(s => s.Major)
    .Select(g => new
    {
        Major = g.Key,
        Count = g.Count(),
        AvgScore = g.Average(s => s.Score),
        MaxScore = g.Max(s => s.Score),
        TopStudent = g.OrderByDescending(s => s.Score).First().Name
    })
    .OrderByDescending(r => r.AvgScore)
    .ToList();

Console.WriteLine("| 专业 | 人数 | 平均分 | 最高分 | 最高分学生 |");
Console.WriteLine("|------|------|--------|--------|------------|");
foreach (var r in report)
    Console.WriteLine($"| {r.Major} | {r.Count} | {r.AvgScore:F1} | {r.MaxScore} | {r.TopStudent} |");

五、多线程 + 网络编程 + LINQ 综合应用

下面是一个综合示例:使用多线程并发处理多个 TCP 客户端连接,并用 LINQ 统计连接信息。

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class IntegratedServer
{
    // 线程安全的连接信息集合
    private static ConcurrentDictionary<string, ClientInfo> _clients = new();

    static async Task Main(string[] args)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8888);
        listener.Start();
        Console.WriteLine("综合服务端已启动...");

        // 后台定时统计
        _ = Task.Run(async () =>
        {
            while (true)
            {
                await Task.Delay(5000);
                PrintStats();
            }
        });

        while (true)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            string clientId = client.Client.RemoteEndPoint!.ToString()!;
            _clients[clientId] = new ClientInfo { Id = clientId, ConnectTime = DateTime.Now };
            Console.WriteLine($"客户端连接: {clientId}");
            _ = HandleClientAsync(client, clientId);
        }
    }

    static async Task HandleClientAsync(TcpClient client, string clientId)
    {
        try
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[1024];
            int totalBytes = 0;

            while (true)
            {
                int read = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (read == 0) break;

                totalBytes += read;
                string msg = Encoding.UTF8.GetString(buffer, 0, read);
                Console.WriteLine($"[{clientId}] {msg}");

                byte[] reply = Encoding.UTF8.GetBytes($"已收到 {read} 字节");
                await stream.WriteAsync(reply, 0, reply.Length);
            }

            if (_clients.TryGetValue(clientId, out var info))
                info.TotalBytes = totalBytes;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{clientId}] 异常: {ex.Message}");
        }
        finally
        {
            client.Close();
            _clients.TryRemove(clientId, out _);
            Console.WriteLine($"客户端断开: {clientId}");
        }
    }

    static void PrintStats()
    {
        var stats = _clients.Values
            .GroupBy(c => "全部")
            .Select(g => new
            {
                TotalClients = g.Count(),
                TotalBytes = g.Sum(c => c.TotalBytes),
                AvgDuration = g.Average(c => (DateTime.Now - c.ConnectTime).TotalSeconds)
            })
            .FirstOrDefault();

        if (stats != null)
            Console.WriteLine($"[统计] 当前连接: {stats.TotalClients}, 总流量: {stats.TotalBytes}字节, 平均时长: {stats.AvgDuration:F1}秒");
    }
}

class ClientInfo
{
    public string Id { get; set; } = "";
    public DateTime ConnectTime { get; set; }
    public int TotalBytes { get; set; }
}

总结

本文系统讲解了 C# 中的三大核心技术模块:

多线程

  • Thread 适合精细控制,但开销大;
  • ThreadPool 适合短任务,自动管理;
  • Task + async/await 是现代异步编程首选;
  • 共享资源必须使用 lock 等同步机制保证线程安全。

网络编程

  • UDP 无连接、不可靠但高效,适合实时性场景;
  • TCP 面向连接、可靠传输,适合数据完整性要求高的场景;
  • TCP 需注意粘包问题,推荐使用长度前缀方案;
  • 服务端应使用异步 + 多线程处理并发连接。

LINQ

  • 提供查询语法和方法语法两种写法;
  • 掌握 WhereSelectGroupByOrderByJoin 等核心方法;
  • 理解延迟执行与立即执行的区别;
  • 善用聚合方法(CountSumAverage 等)简化统计。

这三大技术在实际开发中经常组合使用,掌握它们能显著提升 C# 开发能力和代码质量。


如果本文对你有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区交流。

C# 核心知识精讲:多线程、网络编程(UDP/TCP)与 LINQ 方法全解析

本文系统梳理 C# 开发中四大核心模块:多线程编程、UDP 网络通信、TCP 网络通信以及 LINQ 数据查询方法。每个模块均配有概念讲解与可运行代码示例,适合学习复习与面试备考。

前言

在 C# 开发中,多线程、网络编程和 LINQ 是三个高频使用且相互关联的核心技术领域:

  • 多线程是提升程序并发性能、避免 UI 阻塞的基础;
  • **网络编程(UDP/TCP)**是实现分布式通信、客户端-服务器架构的关键;
  • LINQ 则极大简化了数据查询与集合操作,是 C# 开发者必备技能。

本文将从基础概念出发,结合实际代码示例,逐一深入讲解这三大模块。


一、C# 多线程编程

1.1 多线程基础概念

进程(Process):操作系统资源分配的基本单位,一个应用程序运行时就是一个进程。

线程(Thread):CPU 调度的最小单位,一个进程可以包含多个线程,它们共享进程的内存资源。

多线程的优势

  • 提高程序响应速度(UI 线程不被阻塞)
  • 充分利用多核 CPU 资源
  • 并行处理耗时任务(IO、计算等)

1.2 Thread 类

System.Threading.Thread 是最基础的线程类,用于创建和控制线程。

using System;
using System.Threading;

class ThreadDemo
{
    static void Main()
    {
        // 创建线程
        Thread t = new Thread(Worker);
        t.Name = "工作线程1";
        t.IsBackground = true; // 设置为后台线程
        t.Start(); // 启动线程

        // 主线程继续执行
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"主线程: {i}");
            Thread.Sleep(100);
        }

        t.Join(); // 等待工作线程完成
        Console.WriteLine("程序结束");
    }

    static void Worker()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}: {i}");
            Thread.Sleep(150);
        }
    }
}

常用属性与方法

成员 说明
Name 获取或设置线程名称
IsBackground 获取或设置是否为后台线程
IsAlive 获取线程是否处于活动状态
Start() 启动线程
Sleep(int) 静态方法,暂停当前线程指定毫秒
Join() 等待线程终止
Abort() 终止线程(已不推荐使用)

1.3 线程池 ThreadPool

线程池维护一组工作线程,避免频繁创建和销毁线程带来的开销。

using System;
using System.Threading;

class ThreadPoolDemo
{
    static void Main()
    {
        // 将工作项排入线程池
        for (int i = 0; i < 5; i++)
        {
            ThreadPool.QueueUserWorkItem(Worker, i);
        }

        Console.WriteLine("主线程继续运行...");
        Thread.Sleep(2000); // 等待线程池任务完成
        Console.WriteLine("所有任务完成");
    }

    static void Worker(object state)
    {
        int taskId = (int)state;
        Console.WriteLine($"任务 {taskId} 在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
        Thread.Sleep(500);
    }
}

线程池特点

  • 线程复用,减少创建销毁开销
  • 自动管理线程数量
  • 适合短时间运行的任务
  • 不适合长时间运行的任务(会占用池资源)

1.4 Task 与 async/await

TaskTask<T> 是 .NET 推荐的异步编程模型,配合 async/await 关键字使用。

using System;
using System.Threading.Tasks;

class TaskDemo
{
    static async Task Main()
    {
        Console.WriteLine("开始异步操作...");

        // 方式一:Task.Run
        Task<string> task1 = Task.Run(() =>
        {
            Task.Delay(1000).Wait();
            return "Task.Run 结果";
        });

        // 方式二:async 方法
        string result2 = await DoWorkAsync();

        string result1 = await task1;
        Console.WriteLine(result1);
        Console.WriteLine(result2);

        // 并行等待多个任务
        Task[] tasks = new Task[]
        {
            Task.Delay(500),
            Task.Delay(800),
            Task.Delay(300)
        };
        await Task.WhenAll(tasks);
        Console.WriteLine("所有并行任务完成");
    }

    static async Task<string> DoWorkAsync()
    {
        await Task.Delay(1500);
        return "async 方法结果";
    }
}

Task 常用方法

方法 说明
Task.Run() 将工作排入线程池并返回 Task
Task.Delay() 异步延迟
Task.WhenAll() 等待所有任务完成
Task.WhenAny() 等待任一任务完成
ContinueWith() 任务完成后继续执行
Result 同步获取结果(可能死锁,慎用)

1.5 线程安全与锁

多线程环境下,共享资源的访问需要同步控制,否则会出现数据竞争。

using System;
using System.Threading;
using System.Threading.Tasks;

class LockDemo
{
    private static int _counter = 0;
    private static readonly object _lockObj = new object();

    static async Task Main()
    {
        // 不加锁的情况
        Task[] tasks = new Task[10];
        for (int i = 0; i < 10; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                for (int j = 0; j < 1000; j++)
                {
                    lock (_lockObj) // 加锁保证线程安全
                    {
                        _counter++;
                    }
                }
            });
        }

        await Task.WhenAll(tasks);
        Console.WriteLine($"最终计数: {_counter}"); // 预期 10000
    }
}

常用同步机制

机制 说明
lock 最常用的互斥锁,基于 Monitor
Monitor 提供 Enter/Exit/Pulse/Wait 等方法
Mutex 跨进程互斥锁
Semaphore 信号量,限制并发数量
ReaderWriterLockSlim 读写分离锁,多读单写
Interlocked 原子操作(递增、交换等)
ConcurrentQueue<T> 线程安全集合

二、C# 网络编程 — UDP

2.1 UDP 简介

UDP(User Datagram Protocol,用户数据报协议) 是一种无连接的传输层协议:

  • 无连接:发送数据前不需要建立连接
  • 不可靠:不保证数据送达、不保证顺序、不重传
  • 开销小:头部只有 8 字节,传输效率高
  • 支持广播和多播

适用场景:视频直播、DNS 查询、游戏实时通信、物联网传感器数据等对实时性要求高、可容忍少量丢包的场景。

2.2 UdpClient 类

System.Net.Sockets.UdpClient 是 .NET 中用于 UDP 通信的封装类。

常用方法

方法 说明
UdpClient(int port) 绑定到指定端口
Send(byte[], int, IPEndPoint) 发送数据报
Receive(ref IPEndPoint) 接收数据报(阻塞)
ReceiveAsync() 异步接收数据报
Close() 关闭连接

2.3 UDP 服务端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpServer
{
    static async Task Main(string[] args)
    {
        int port = 8888;
        UdpClient udpServer = new UdpClient(port);
        Console.WriteLine($"UDP 服务端已启动,监听端口 {port}...");

        try
        {
            while (true)
            {
                // 异步接收数据
                UdpReceiveResult result = await udpServer.ReceiveAsync();
                string message = Encoding.UTF8.GetString(result.Buffer);
                IPEndPoint remoteEndPoint = result.RemoteEndPoint;

                Console.WriteLine($"收到来自 {remoteEndPoint} 的消息: {message}");

                // 回复客户端
                string reply = $"服务端已收到: {message}";
                byte[] replyBytes = Encoding.UTF8.GetBytes(reply);
                await udpServer.SendAsync(replyBytes, replyBytes.Length, remoteEndPoint);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"服务端异常: {ex.Message}");
        }
        finally
        {
            udpServer.Close();
        }
    }
}

2.4 UDP 客户端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpClientDemo
{
    static async Task Main(string[] args)
    {
        string serverIp = "127.0.0.1";
        int serverPort = 8888;

        UdpClient client = new UdpClient();
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);

        try
        {
            // 发送消息
            string message = "Hello, UDP Server!";
            byte[] data = Encoding.UTF8.GetBytes(message);
            await client.SendAsync(data, data.Length, serverEndPoint);
            Console.WriteLine($"已发送: {message}");

            // 接收回复(设置超时)
            client.Client.ReceiveTimeout = 3000;
            UdpReceiveResult result = await client.ReceiveAsync();
            string reply = Encoding.UTF8.GetString(result.Buffer);
            Console.WriteLine($"收到回复: {reply}");
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"网络异常: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

2.5 UDP 广播示例

UDP 支持向同一网段内所有主机发送广播消息。

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpBroadcast
{
    static async Task Main(string[] args)
    {
        int port = 9000;
        UdpClient broadcaster = new UdpClient();
        broadcaster.EnableBroadcast = true; // 启用广播

        IPEndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, port);

        for (int i = 0; i < 5; i++)
        {
            string msg = $"广播消息 #{i + 1} - {DateTime.Now:HH:mm:ss}";
            byte[] data = Encoding.UTF8.GetBytes(msg);
            await broadcaster.SendAsync(data, data.Length, broadcastEndPoint);
            Console.WriteLine($"已广播: {msg}");
            await Task.Delay(1000);
        }

        broadcaster.Close();
        Console.WriteLine("广播结束");
    }
}

三、C# 网络编程 — TCP

3.1 TCP 简介

TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的传输层协议:

  • 面向连接:通信前需通过三次握手建立连接
  • 可靠传输:保证数据有序、无差错、不丢失、不重复
  • 流量控制与拥塞控制:自动调整发送速率
  • 全双工:连接建立后双方可同时收发数据

适用场景:文件传输(FTP/HTTP)、邮件(SMTP)、远程登录(SSH)、数据库连接等要求数据完整性的场景。

3.2 TcpListener 与 TcpClient

用途
TcpListener TCP 服务端,监听客户端连接请求
TcpClient TCP 客户端,用于连接服务端并收发数据
NetworkStream 网络数据流,用于读写数据

3.3 TCP 服务端示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpServer
{
    static async Task Main(string[] args)
    {
        IPAddress ip = IPAddress.Any;
        int port = 8888;
        TcpListener listener = new TcpListener(ip, port);
        listener.Start();
        Console.WriteLine($"TCP 服务端已启动,监听 {ip}:{port}");

        try
        {
            while (true)
            {
                // 等待客户端连接
                TcpClient client = await listener.AcceptTcpClientAsync();
                Console.WriteLine($"客户端已连接: {client.Client.RemoteEndPoint}");

                // 每个客户端独立线程处理
                _ = HandleClientAsync(client);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"服务端异常: {ex.Message}");
        }
        finally
        {
            listener.Stop();
        }
    }

    static async Task HandleClientAsync(TcpClient client)
    {
        try
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[1024];

            while (true)
            {
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0) // 客户端断开
                {
                    Console.WriteLine("客户端断开连接");
                    break;
                }

                string received = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"收到: {received}");

                // 回显(Echo)
                byte[] reply = Encoding.UTF8.GetBytes($"服务端回显: {received}");
                await stream.WriteAsync(reply, 0, reply.Length);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理客户端异常: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

3.4 TCP 客户端示例

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpClientDemo
{
    static async Task Main(string[] args)
    {
        string serverIp = "127.0.0.1";
        int port = 8888;

        TcpClient client = new TcpClient();
        try
        {
            // 连接服务端
            await client.ConnectAsync(serverIp, port);
            Console.WriteLine($"已连接到服务端 {serverIp}:{port}");

            NetworkStream stream = client.GetStream();

            // 发送消息
            string message = "Hello, TCP Server!";
            byte[] data = Encoding.UTF8.GetBytes(message);
            await stream.WriteAsync(data, 0, data.Length);
            Console.WriteLine($"已发送: {message}");

            // 接收回复
            byte[] buffer = new byte[1024];
            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
            string reply = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"收到: {reply}");
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
        }
        finally
        {
            client.Close();
        }
    }
}

3.5 TCP 粘包问题与解决方案

TCP 是面向流的协议,数据没有明确的边界,可能出现粘包(多个数据包合并)或拆包(一个数据包被拆分)的情况。

常见解决方案

  1. 固定长度:每条消息固定长度
  2. 特殊分隔符:用特定字符(如 \n\r\n)分隔
  3. 长度前缀:消息前加 4 字节表示消息长度(推荐)
// 长度前缀方式发送消息
public static async Task SendMessageAsync(NetworkStream stream, string message)
{
    byte[] body = Encoding.UTF8.GetBytes(message);
    byte[] lengthPrefix = BitConverter.GetBytes(body.Length);

    // 先发送长度(4字节),再发送内容
    await stream.WriteAsync(lengthPrefix, 0, 4);
    await stream.WriteAsync(body, 0, body.Length);
}

// 长度前缀方式接收消息
public static async Task<string> ReceiveMessageAsync(NetworkStream stream)
{
    byte[] lengthBuffer = new byte[4];
    int totalRead = 0;

    // 读取4字节长度
    while (totalRead < 4)
    {
        int read = await stream.ReadAsync(lengthBuffer, totalRead, 4 - totalRead);
        if (read == 0) return null;
        totalRead += read;
    }

    int bodyLength = BitConverter.ToInt32(lengthBuffer, 0);
    byte[] bodyBuffer = new byte[bodyLength];
    totalRead = 0;

    // 读取完整消息体
    while (totalRead < bodyLength)
    {
        int read = await stream.ReadAsync(bodyBuffer, totalRead, bodyLength - totalRead);
        if (read == 0) return null;
        totalRead += read;
    }

    return Encoding.UTF8.GetString(bodyBuffer);
}

3.6 UDP 与 TCP 对比

特性 UDP TCP
连接性 无连接 面向连接
可靠性 不可靠 可靠
顺序性 不保证顺序 保证顺序
传输速度 相对较慢
头部开销 8 字节 20 字节(不含选项)
流量控制
拥塞控制
广播/多播 支持 不支持
适用场景 实时音视频、游戏、DNS 文件传输、Web、邮件

四、LINQ 方法详解

4.1 LINQ 简介

LINQ(Language Integrated Query,语言集成查询) 是 C# 3.0 引入的一项特性,允许使用类似 SQL 的语法直接在 C# 代码中查询数据。

LINQ 的两种语法

  1. 查询语法(Query Syntax):类似 SQL,使用 fromwhereselect 等关键字
  2. 方法语法(Method Syntax):使用扩展方法链式调用
// 查询语法
var querySyntax = from s in students
                  where s.Age > 18
                  orderby s.Name
                  select s;

// 方法语法(等价)
var methodSyntax = students
    .Where(s => s.Age > 18)
    .OrderBy(s => s.Name)
    .Select(s => s);

4.2 数据准备

以下示例使用如下测试数据:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Major { get; set; }
    public decimal Score { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机", Score = 85.5m },
    new Student { Id = 2, Name = "李四", Age = 22, Major = "数学", Score = 92.0m },
    new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机", Score = 78.5m },
    new Student { Id = 4, Name = "赵六", Age = 21, Major = "英语", Score = 88.0m },
    new Student { Id = 5, Name = "钱七", Age = 20, Major = "数学", Score = 95.5m },
    new Student { Id = 6, Name = "孙八", Age = 23, Major = "计算机", Score = 67.0m }
};

4.3 筛选与投影

Where — 条件筛选
// 筛选计算机专业且分数大于80的学生
var result = students.Where(s => s.Major == "计算机" && s.Score > 80);

foreach (var s in result)
    Console.WriteLine($"{s.Name} - {s.Major} - {s.Score}");
Select — 投影(转换)
// 只选取姓名和分数,投影为匿名类型
var names = students.Select(s => new { s.Name, s.Score });

foreach (var n in names)
    Console.WriteLine($"{n.Name}: {n.Score}");

// Select 带索引重载
var indexed = students.Select((s, index) => $"{index + 1}. {s.Name}");
SelectMany — 扁平化集合
// 将嵌套集合扁平化为单一序列
List<string[]> courses = new List<string[]>
{
    new string[] { "C#", "Java" },
    new string[] { "Python", "Go", "Rust" }
};

var allCourses = courses.SelectMany(c => c);
// 结果: C#, Java, Python, Go, Rust

4.4 排序

OrderBy / OrderByDescending
// 按分数升序
var ascending = students.OrderBy(s => s.Score);

// 按分数降序
var descending = students.OrderByDescending(s => s.Score);
ThenBy / ThenByDescending — 多级排序
// 先按专业升序,再按分数降序
var multiSort = students
    .OrderBy(s => s.Major)
    .ThenByDescending(s => s.Score);
Reverse — 反转序列
var reversed = students.Reverse();

4.5 分组

GroupBy — 分组
// 按专业分组
var groups = students.GroupBy(s => s.Major);

foreach (var group in groups)
{
    Console.WriteLine($"专业: {group.Key},人数: {group.Count()}");
    foreach (var s in group)
        Console.WriteLine($"  - {s.Name} ({s.Score})");
}
ToLookup — 生成查找表
// ToLookup 生成 ILookup<TKey, TElement>,类似字典但值是集合
var lookup = students.ToLookup(s => s.Major);

foreach (var s in lookup["计算机"])
    Console.WriteLine(s.Name);

4.6 集合操作

Distinct — 去重
int[] numbers = { 1, 2, 2, 3, 3, 3, 4 };
var unique = numbers.Distinct(); // 1, 2, 3, 4
Union / Intersect / Except
int[] a = { 1, 2, 3, 4 };
int[] b = { 3, 4, 5, 6 };

var union = a.Union(b);        // 并集: 1,2,3,4,5,6
var intersect = a.Intersect(b); // 交集: 3,4
var except = a.Except(b);       // 差集(a有b无): 1,2
Concat — 连接序列
var concat = a.Concat(b); // 1,2,3,4,3,4,5,6(不去重)

4.7 聚合操作

方法 说明 示例
Count() 元素数量 students.Count()
Count(predicate) 满足条件的数量 students.Count(s => s.Score > 80)
Sum() 求和 students.Sum(s => s.Score)
Average() 平均值 students.Average(s => s.Score)
Max() 最大值 students.Max(s => s.Score)
Min() 最小值 students.Min(s => s.Score)
Aggregate() 自定义聚合 见下方示例
// Aggregate 自定义聚合:计算所有分数的乘积(示例)
decimal product = students.Aggregate(1m, (acc, s) => acc * s.Score);

// Aggregate 拼接字符串
string allNames = students.Aggregate("", (acc, s) => acc + s.Name + ", ");
// 结果: "张三, 李四, 王五, 赵六, 钱七, 孙八, "

4.8 元素操作

方法 说明 异常行为
First() 第一个元素 无元素抛异常
FirstOrDefault() 第一个元素或默认值 无元素返回默认值
Last() 最后一个元素 无元素抛异常
LastOrDefault() 最后一个或默认值 无元素返回默认值
Single() 唯一元素 0个或多个都抛异常
SingleOrDefault() 唯一元素或默认值 多个抛异常
ElementAt(int) 指定索引元素 越界抛异常
ElementAtOrDefault(int) 指定索引或默认值 越界返回默认值
var first = students.First(s => s.Major == "计算机"); // 张三
var firstOrDefault = students.FirstOrDefault(s => s.Major == "物理"); // null

var single = students.Single(s => s.Id == 1); // 张三
// students.Single(s => s.Major == "计算机") // 抛异常(有多个)

4.9 量词操作

方法 说明
Any() 是否有元素
Any(predicate) 是否有满足条件的元素
All(predicate) 是否所有元素都满足条件
Contains(value) 是否包含指定元素
bool hasHighScore = students.Any(s => s.Score > 90); // true
bool allPassed = students.All(s => s.Score >= 60); // true
bool hasZhang = students.Select(s => s.Name).Contains("张三"); // true

4.10 分区操作

方法 说明
Take(n) 取前 n 个元素
Skip(n) 跳过前 n 个元素
TakeWhile(predicate) 取满足条件的元素,直到不满足
SkipWhile(predicate) 跳过满足条件的元素,直到不满足
var top3 = students.OrderByDescending(s => s.Score).Take(3); // 前三名
var page2 = students.OrderBy(s => s.Id).Skip(3).Take(3); // 分页第二页

4.11 连接操作

Join — 内连接
// 准备第二个数据源
var departments = new List<Department>
{
    new Department { Major = "计算机", DeptName = "信息工程学院" },
    new Department { Major = "数学", DeptName = "理学院" },
    new Department { Major = "英语", DeptName = "外国语学院" }
};

// 内连接:学生与学院信息关联
var joinResult = students.Join(
    departments,
    s => s.Major,          // 左表键
    d => d.Major,          // 右表键
    (s, d) => new { s.Name, s.Major, d.DeptName }
);

foreach (var item in joinResult)
    Console.WriteLine($"{item.Name} - {item.Major} - {item.DeptName}");
GroupJoin — 分组连接(左外连接)
var groupJoin = departments.GroupJoin(
    students,
    d => d.Major,
    s => s.Major,
    (d, studentGroup) => new
    {
        d.DeptName,
        Students = studentGroup.ToList()
    }
);

4.12 转换操作

方法 说明
ToList() 转为 List<T>
ToArray() 转为数组
ToDictionary() 转为字典
ToHashSet() 转为 HashSet
Cast<T>() 强制转换元素类型
OfType<T>() 筛选指定类型元素
AsEnumerable() 转为 IEnumerable<T>
AsQueryable() 转为 IQueryable<T>
// ToDictionary
var dict = students.ToDictionary(s => s.Id, s => s.Name);

// OfType 筛选类型
object[] mixed = { 1, "hello", 2, "world", 3 };
var strings = mixed.OfType<string>(); // "hello", "world"
var ints = mixed.OfType<int>();       // 1, 2, 3

4.13 生成操作

方法 说明
Enumerable.Range(start, count) 生成整数序列
Enumerable.Repeat(element, count) 重复元素生成序列
Enumerable.Empty<T>() 空序列
var range = Enumerable.Range(1, 10);       // 1,2,...,10
var repeat = Enumerable.Repeat("A", 5);    // A,A,A,A,A
var empty = Enumerable.Empty<int>();        // 空序列

4.14 延迟执行 vs 立即执行

延迟执行(Deferred Execution):查询在迭代时才执行,每次迭代重新计算。

var query = students.Where(s => s.Score > 80); // 此时未执行
students.Add(new Student { Id = 7, Name = "周九", Age = 20, Major = "计算机", Score = 90 });
foreach (var s in query) // 迭代时执行,包含新添加的周九
    Console.WriteLine(s.Name);

立即执行(Immediate Execution):调用时立即执行并缓存结果。

var list = students.Where(s => s.Score > 80).ToList(); // 立即执行
students.Add(new Student { Id = 8, Name = "吴十", Age = 20, Major = "计算机", Score = 91 });
// list 中不包含吴十,因为已经执行完毕

判断规则

  • 返回 IEnumerable<T> / IQueryable<T> → 通常延迟执行
  • 返回单个值(CountSumFirstAny 等)或集合(ToListToArrayToDictionary)→ 立即执行

4.15 LINQ 综合实战示例

// 需求:统计每个专业的平均分、最高分、人数,按平均分降序排列
var report = students
    .GroupBy(s => s.Major)
    .Select(g => new
    {
        Major = g.Key,
        Count = g.Count(),
        AvgScore = g.Average(s => s.Score),
        MaxScore = g.Max(s => s.Score),
        TopStudent = g.OrderByDescending(s => s.Score).First().Name
    })
    .OrderByDescending(r => r.AvgScore)
    .ToList();

Console.WriteLine("| 专业 | 人数 | 平均分 | 最高分 | 最高分学生 |");
Console.WriteLine("|------|------|--------|--------|------------|");
foreach (var r in report)
    Console.WriteLine($"| {r.Major} | {r.Count} | {r.AvgScore:F1} | {r.MaxScore} | {r.TopStudent} |");

五、多线程 + 网络编程 + LINQ 综合应用

下面是一个综合示例:使用多线程并发处理多个 TCP 客户端连接,并用 LINQ 统计连接信息。

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class IntegratedServer
{
    // 线程安全的连接信息集合
    private static ConcurrentDictionary<string, ClientInfo> _clients = new();

    static async Task Main(string[] args)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8888);
        listener.Start();
        Console.WriteLine("综合服务端已启动...");

        // 后台定时统计
        _ = Task.Run(async () =>
        {
            while (true)
            {
                await Task.Delay(5000);
                PrintStats();
            }
        });

        while (true)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            string clientId = client.Client.RemoteEndPoint!.ToString()!;
            _clients[clientId] = new ClientInfo { Id = clientId, ConnectTime = DateTime.Now };
            Console.WriteLine($"客户端连接: {clientId}");
            _ = HandleClientAsync(client, clientId);
        }
    }

    static async Task HandleClientAsync(TcpClient client, string clientId)
    {
        try
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[1024];
            int totalBytes = 0;

            while (true)
            {
                int read = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (read == 0) break;

                totalBytes += read;
                string msg = Encoding.UTF8.GetString(buffer, 0, read);
                Console.WriteLine($"[{clientId}] {msg}");

                byte[] reply = Encoding.UTF8.GetBytes($"已收到 {read} 字节");
                await stream.WriteAsync(reply, 0, reply.Length);
            }

            if (_clients.TryGetValue(clientId, out var info))
                info.TotalBytes = totalBytes;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{clientId}] 异常: {ex.Message}");
        }
        finally
        {
            client.Close();
            _clients.TryRemove(clientId, out _);
            Console.WriteLine($"客户端断开: {clientId}");
        }
    }

    static void PrintStats()
    {
        var stats = _clients.Values
            .GroupBy(c => "全部")
            .Select(g => new
            {
                TotalClients = g.Count(),
                TotalBytes = g.Sum(c => c.TotalBytes),
                AvgDuration = g.Average(c => (DateTime.Now - c.ConnectTime).TotalSeconds)
            })
            .FirstOrDefault();

        if (stats != null)
            Console.WriteLine($"[统计] 当前连接: {stats.TotalClients}, 总流量: {stats.TotalBytes}字节, 平均时长: {stats.AvgDuration:F1}秒");
    }
}

class ClientInfo
{
    public string Id { get; set; } = "";
    public DateTime ConnectTime { get; set; }
    public int TotalBytes { get; set; }
}

总结

本文系统讲解了 C# 中的三大核心技术模块:

多线程

  • Thread 适合精细控制,但开销大;
  • ThreadPool 适合短任务,自动管理;
  • Task + async/await 是现代异步编程首选;
  • 共享资源必须使用 lock 等同步机制保证线程安全。

网络编程

  • UDP 无连接、不可靠但高效,适合实时性场景;
  • TCP 面向连接、可靠传输,适合数据完整性要求高的场景;
  • TCP 需注意粘包问题,推荐使用长度前缀方案;
  • 服务端应使用异步 + 多线程处理并发连接。

LINQ

  • 提供查询语法和方法语法两种写法;
  • 掌握 WhereSelectGroupByOrderByJoin 等核心方法;
  • 理解延迟执行与立即执行的区别;
  • 善用聚合方法(CountSumAverage 等)简化统计。

这三大技术在实际开发中经常组合使用,掌握它们能显著提升 C# 开发能力和代码质量。
最后附赠一张思维导图:在这里插入图片描述

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐