SeeSharper - 劉亞坤
type: channel
link: https://www.seesharper.cn/author/1/
description: 劉亞坤
items: 解锁DeepSeek-R1思维链：.NET高效读取技巧
items: 🚀 中文Token文本切分器：递归 + 权重分隔符 + 重叠块
items: C盘空间清理之软链接
items: Frp内网穿透使用和部署
items: 在.NET中使用LaunchDarkly.EventSource进行SSE请求
items: 【ASP.NET Core开发者学习路线】 3.1 SQL基础知识—数据库设计基础和规范
items: 【ASP.NET Core开发者学习路线】——路线总览
items: 【ASP.NET Core开发者学习路线】 1.1 通用开发技能—HTTP/HTTPS协议和TLS/SSL
items: 【ASP.NET Core开发者学习路线】 5.1 设计原则—SOLID
items: 【ASP.NET Core开发者学习路线】 2.1 C#基础—多线程

解锁DeepSeek-R1思维链：.NET高效读取技巧
type: item
date: 2025-12-07T13:33:16+00:00
description: [C#] OpenAI SDK 访问 DeepSeek-R1 推理内容 (reasoning\_content) 的原生实现在使用官方 OpenAI .NET SDK 对接 DeepSeek-R1 等推理模型时，存在一个兼容性问题：标准 SDK 仅映射了 OpenAI 协议的通用字段（如 content），而忽略了 DeepSeek 扩展的 reasoning_content（思维链）字段。在 SDK v2+ 基于 System.ClientModel 重构后，我们无需再通过反射（Reflection）操作私有字段 _rawJson，也无需回退到 HttpClient 手动解析 SSE。利用 SDK 底层提供的 JSON Path 访问能力，即可原生获取非标字段。核心实现ChatCompletionStreamUpdate 对象通过 Patch 属性暴露了底层的 JSON 结构访问入口。结合 C# 11 的 UTF-8 字符串字面量（u8），可以实现零分配（Zero-allocation）的高效读取。代码示例C#using OpenAI; using System.ClientModel; // 必需引用 using System.Text; // ... 初始化 Client 代码略 ... var chatClient = client.GetChatClient("deepseek-ai/DeepSeek-R1"); await foreach (var update in chatClient.CompleteChatStreamingAsync("你好，请解释量子纠缠")) { // 1. 获取 DeepSeek 特有的思维链内容 (Reasoning Content) // 使用 JSON Path 语法直接定位字段，配合 u8 后缀避免 string 内存分配 if (update.Patch.TryGetValue("$.choices[0].delta.reasoning_content"u8, out string? reasoning)) { Console.ForegroundColor = ConsoleColor.Cyan; Console.Write(reasoning); } // 2. 获取标准内容 (Content) if (update.ContentUpdate.Count > 0) { Console.ForegroundColor = ConsoleColor.White; foreach (var content in update.ContentUpdate) { Console.Write(content.Text); } } }技术分析1. System.ClientModel 与 PatchOpenAI SDK v2 构建在 Microsoft 的 System.ClientModel 库之上。此时的 update 对象实现了 IPersistableModel<T> 契约。Patch 属性本质上保留了服务端的原始 JSON 片段（通常映射到底层的 BinaryData 或 JsonElement），允许开发者绕过 SDK 的强类型定义，直接访问 Schema 之外的数据。2. JSON Path 寻址TryGetValue 方法支持简化的 JSON Path 语法。针对 SSE 流式返回的结构：JSON{ "choices": [{ "delta": { "reasoning_content": "...", // 目标字段 "content": "..." } }] }路径 $.choices[0].delta.reasoning_content 精确指向了目标字段。这种方式比手动编写 JsonDocument.Parse 更简洁，且由底层库处理了异常边界。3. 性能优化 (u8 字符串)在流式传输（Streaming）场景下，Token 级的高频调用对性能敏感。反射方案：存在装箱拆箱及访问检查开销，且依赖私有字段名称，极其脆弱。常规字符串 Key：使用 "path" 会导致每次循环都产生 string 实例。UTF-8 字面量："..."u8 编译为 ReadOnlySpan<byte>，直接对应底层 JSON 字节流的查找，完全消除了键名的内存分配。总结对于 DeepSeek-R1 这类兼容 OpenAI 接口但包含自定义扩展字段的模型，利用 SDK 的 Patch 机制是目前最标准、稳健的解法。它既保留了使用 SDK 强类型的便利性，又提供了访问原始协议数据的灵活性。

🚀 中文Token文本切分器：递归 + 权重分隔符 + 重叠块
type: item
date: 2025-09-25T03:17:00+00:00
description: 🚀 中文优化 Token 文本切分器：递归 + 权重分隔符 + 重叠块在构建 知识库检索（RAG）、长文档问答、摘要生成 等 LLM 应用时，如何合理切分文本是一件至关重要的事情。常见的 按字数切分 或 固定长度切分 方法，存在以下不足：❌ 语义割裂：切分点落在句子中间，导致语义残缺；❌ 粒度失衡：有的分块过长超出 Token 限制，有的又过短浪费；❌ 缺乏上下文：块之间完全独立，问答时语义衔接不畅。为此，我实现了一个 针对中文优化的 Token 文本切分器 ——ChineseTokenTextSplitter。它具有以下特性：✅ 递归分割：从大粒度到小粒度逐步切分；✅ 权重分隔符：保证重要段落优先完整；✅ Token 控制：块大小符合模型输入上限；✅ Overlap 重叠：保持上下文连续；✅ 支持自定义正则：灵活适配 Markdown / 特殊文本格式。✨ 设计思路1. 多级分隔符 + 权重每个分隔符带有 权重值，保证优先用高权重（例如段落、标题）切分。如果块仍然过大，则使用低权重分隔符（例如逗号）继续递归切分。2. Token 粒度控制使用 SharpToken 计算 Token 数量。每个分块不会超过设定的 _chunkSize（如 900 Tokens）。3. Overlap 机制在相邻块之间引入 _chunkOverlap 个 Token 重叠（如 160 Tokens），保证跨块语义连续。🔍 流程图下面是一张文本切分流程图，展示了切分器的工作原理：flowchart TD A[输入文本] --> B{是否匹配高权重分隔符?} B -- 否 --> C[返回原文本作为单块] B -- 是 --> D[按分隔符切分文本] D --> E{子块是否超出 Token 限制?} E -- 否 --> F[加入 GoodSplits] E -- 是 --> G{是否有剩余分隔符?} G -- 否 --> H[直接输出超长块] G -- 是 --> I[递归调用SplitTextRecursive] F --> J[合并 GoodSplits] I --> J H --> J J --> K[清理换行与格式] K --> L[应用 Overlap 重叠策略] L --> M[输出最终切分结果]该流程保证了 从大到小分割 → 控制块大小 → 应用重叠 的完整处理链路。🔍 核心代码public class ChineseTokenTextSplitter { private readonly List<(string Pattern, int Weight)> _separators; private readonly bool _keepSeparator; private readonly int _chunkSize; private readonly int _chunkOverlap; private readonly GptEncoding _encoding; public ChineseTokenTextSplitter( string modelName = "gpt-3.5-turbo-0613", List<(string Pattern, int Weight)> separators = null, bool keepSeparator = true, int chunkSize = 900, int chunkOverlap = 160) { _separators = separators ?? new List<(string, int)> { ("\n\n", 10), // 段落 ("^#+\\s.*", 9), // Markdown 标题 ("。|！|？", 8), // 中文句号 ("\\.\\s|\\!\\s|\\?\\s", 8), // 英文句号 ("；|;\\s", 5), // 分号 ("，|,\\s", 3) // 逗号 }; _keepSeparator = keepSeparator; _chunkSize = chunkSize; _chunkOverlap = chunkOverlap; _encoding = GptEncoding.GetEncodingForModel(modelName); } public List<string> SplitText(string text) { return SplitTextRecursive(text, _separators.OrderByDescending(s => s.Weight).ToList()); } // 递归分割逻辑（省略部分...） }🛠️ 使用示例var splitter = new Chinese…

C盘空间清理之软链接
type: item
date: 2024-05-27T03:03:00+00:00
description: 开发过程中，诸如nuget包等会占据大量C盘空间，可以通过创建软链接使其指向其他盘，既不影响软件使用，又不占用C盘空间，C盘有入口，但文件实际存在其他盘。命令 mklink /j sourceDir targetDir以下是一个批量处理脚本: create_links.bat@echo off set "sourceFolder=C:\Users\lyk" set "targetFolder=D:\DiskMap" mklink /j "%sourceFolder%\.abp" "%targetFolder%\.abp" mklink /j "%sourceFolder%\.android" "%targetFolder%\.android" mklink /j "%sourceFolder%\.cache" "%targetFolder%\.cache" mklink /j "%sourceFolder%\.conda" "%targetFolder%\.conda" mklink /j "%sourceFolder%\.config" "%targetFolder%\.config" mklink /j "%sourceFolder%\.crossnote" "%targetFolder%\.crossnote" mklink /j "%sourceFolder%\.docker" "%targetFolder%\.docker" mklink /j "%sourceFolder%\.dotnet" "%targetFolder%\.dotnet" mklink /j "%sourceFolder%\.gnupg" "%targetFolder%\.gnupg" mklink /j "%sourceFolder%\.Ld2VirtualBox" "%targetFolder%\.Ld2VirtualBox" mklink /j "%sourceFolder%\.lemminx" "%targetFolder%\.lemminx" mklink /j "%sourceFolder%\.librarymanager" "%targetFolder%\.librarymanager" mklink /j "%sourceFolder%\.lingma" "%targetFolder%\.lingma" mklink /j "%sourceFolder%\.mume" "%targetFolder%\.mume" mklink /j "%sourceFolder%\.nuget" "%targetFolder%\.nuget" mklink /j "%sourceFolder%\.omnisharp" "%targetFolder%\.omnisharp" mklink /j "%sourceFolder%\.picgo" "%targetFolder%\.picgo" mklink /j "%sourceFolder%\.rdm" "%targetFolder%\.rdm" mklink /j "%sourceFolder%\.redhat" "%targetFolder%\.redhat" mklink /j "%sourceFolder%\.ssh" "%targetFolder%\.ssh" mklink /j "%sourceFolder%\.templateengine" "%targetFolder%\.templateengine" mklink /j "%sourceFolder%\.vscode" "%targetFolder%\.vscode" echo Soft links created successfully. pause >nul 注意创建之前，先把文件剪切到目标路径下，这样就不会提示文件夹已存在。同时也不会丢失文件。

Frp内网穿透使用和部署
type: item
date: 2023-11-16T05:58:00+00:00
description: 基于toml配置文件格式部署frps内网穿透，之前写过一篇NAS之搭建frp内网穿透服务过程记录，当时版本基于0.4x，新版本弃用了.ini配置文件，改为TOML、YAML 和 JSON，TOML格式比较精简，同是也是新版本的默认配置文件格式。所以使用TOML格式重新部署了之前的项目。在这里记录一下部署过程。快速启动通过releases页面下载适合自己系统的包，以linux_amd64平台为例：wget https://github.com/fatedier/frp/releases/download/v0.52.3/frp_0.52.3_linux_amd64.tar.gz解压压缩包tar zxvf frp_0.52.3_linux_amd64.tar.gz解压压缩包cd zxvf frp_0.52.3_linux_amd64.tar.gz #启动frpc ./frpc -c ./frpc.toml #启动frps ./frps -c ./frps.toml（可选）使用pm2守护进程cd zxvf frp_0.52.3_linux_amd64.tar.gz #启动frpc pm2 start "./frpc -c ./frpc.toml" -n frpc #启动frps pm2 start "./frps -c ./frps.toml" -n frps #保存任务列表 pm2 save配置文件​ 配置文件内容上大体和原来差不多，只是写法略有不同，同时命名规则也略有不同，各配置属性名采用了小驼峰命名法。frpc.toml# 远程服务器地址,支持域名和ip serverAddr = "100.100.100.100" # 远程服务器监听端口，可以配合nginx反向代理实现去端口 serverPort = 37257 # 启用quic协议 transport.protocol = "quic" # 认证token，frpc/frps通信认证 auth.token = "abcdefg" # 启用端口复用 transport.tls.disableCustomTLSFirstByte = false # 客户端面板 webServer.addr = "127.0.0.1" webServer.port = 47400 webServer.user = "adminsweb" webServer.password = "adminsweb" # 日志输出文件路径 log.to = "frpc.log" # 日志文件最多保留天数 log.maxDays = 7 # 相对完整的代理示例，一般情况下http代理只需要填写到 customDomains 即可 # proxies # name = "web" # type = "http" # localPort = 80 # customDomains = ["www.yourdomain.com"] # # 代理负载均衡，相同groupKey的代理,会视为负载均衡 # # 对于 tcp 类型代理，需要确保 groupKey 相同以进行权限验证，同时 remotePort 也需一致。 # # 对于 http 类型代理，需要保证 groupKey, customDomains(自定义域名)，subdomain 和 locations 相同。 # loadBalancer.group = "web" # loadBalancer.groupKey = "123" # # 启用健康检查，类型为 http # healthCheck.type = "http" # # 健康检查发送 http 请求的 path，后端服务需要返回 2xx 的 http 状态码 # healthCheck.path = "/status" # # 建立连接超时时间为 3 秒 # healthCheck.timeoutSeconds = 3 # # 连续 3 次检查失败，此 proxy 会被摘除 # healthCheck.maxFailed = 3 # # 每隔 10 秒进行一次健康检查 # healthCheck.intervalSeconds = 10 # # 获取用户真实IP，具体配置见 https://gofrp.org/zh-cn/docs/features/common/realip/ # transport.proxyProtocolVersion = "v2" # # 带宽限速，支持MB,KB # transport.bandwidthLimit = "1MB" proxies name = "wiznote" type = "http" localPort = 29981 customD…

在.NET中使用LaunchDarkly.EventSource进行SSE请求
type: item
date: 2023-09-05T08:01:08+00:00
description: var config = Configuration.Builder(new Uri(apiUrl)) .Method(HttpMethod.Post) .RequestBody(input.ToJson(), "application/json") .RequestHeader("Accept", "text/event-stream") .Build(); var sseClient = new EventSource(config); sseClient.Error += (sender, e) => { _httpContextAccessor.Bah(FrontErrorCode.AIRequestError, e.Exception.Message); }; sseClient.MessageReceived += (sender, e) => { _httpContextAccessor.WriteAsync($"data: {e.Message.Data}").Wait(); }; sseClient.Closed += (sender, e) => { _httpContextAccessor.WriteAsync("[DONE]", "").Wait(); usageDto.IsSuccess = true; sseClient.Dispose();//不在Closed中进行Dispose会导致重复请求 }; await sseClient.StartAsync();

【ASP.NET Core开发者学习路线】 3.1 SQL基础知识—数据库设计基础和规范
type: item
date: 2023-05-22T05:42:00+00:00
description: 数据库设计是构建和组织数据库结构以及定义其关系和约束的过程。下面是数据库设计的基础知识和一些规范的详细总结：基础知识实体和属性：数据库中的实体表示现实世界中的对象，属性表示实体的特征。例如，学生可以是一个实体，而姓名、年龄和学号可以是该实体的属性。关系：关系是实体之间的联系，用于描述实体之间的关联和依赖关系。常见的关系包括一对一、一对多和多对多关系。主键：主键是能够唯一标识实体的属性或属性组合。它具有唯一性和非空性的特点，并用于确保数据的唯一性和关系的完整性。外键：外键是一个实体中引用另一个实体的属性，用于建立实体之间的关联。外键用于维护表之间的关系和数据的完整性。范式：范式是一组规则，用于规范化数据库设计，消除冗余数据并提高数据的一致性和效率。常见的范式包括第一范式（1NF）、第二范式（2NF）和第三范式（3NF）等。索引：索引是一种数据结构，用于提高数据库查询的性能。它可以加快数据的查找速度，但会增加数据插入、更新和删除的开销。事务：事务是一组数据库操作的逻辑单元，要么全部成功执行，要么全部回滚。事务具有原子性、一致性、隔离性和持久性（ACID）的特性，用于维护数据的完整性和一致性。关系型数据库管理系统（RDBMS）：RDBMS是一种管理关系型数据库的软件系统。它提供了对数据库的创建、查询、更新和删除等操作的支持。常见的RDBMS包括MySQL、Oracle、SQL Server等。规范命名规范：采用一致的命名规范可以提高数据库的可读性和维护性。表名、列名、约束名等应具有描述性，并遵循命名约定，如使用小写字母、下划线分隔单词等。数据类型选择：选择适当的数据类型可以有效地使用存储空间，并确保数据的准确性和一致性。常见的数据类型包括整数、浮点数、字符、日期等。范式化设计：根据需求和数据特点，进行合理的范式化设计，以消除冗余数据，提高数据的一致性和查询效率。遵循范式化原则可以减少数据更新异常和数据不一致的问题。索引优化：根据查询需求和数据访问模式，选择合适的列作为索引，以加快查询速度。但索引也会增加数据的存储空间和维护成本，因此需要权衡索引的使用。约束和完整性：使用约束来定义数据的有效性和一致性规则。常见的约束包括主键约束、唯一约束、外键约束和检查约束等。约束能够防止无效的数据插入和更新，保护数据的完整性。备份和恢复策略：制定合理的备份和恢复策略，以保护数据库免受故障和数据丢失的影响。定期备份数据库，并测试备份数据的可恢复性。安全性：数据库安全性是保护数据免受非授权访问和恶意操作的重要方面。采取适当的安全措施，如访问控制、加密、审计等，以保护数据库的机密性和完整性。性能优化：根据数据库的使用情况和性能需求，进行性能优化。优化技术包括查询优化、索引优化、分区、缓存等，以提高数据库的响应时间和吞吐量。这些基础知识和规范是数据库设计的重要组成部分。在实际设计数据库时，根据具体需求和场景，还需要综合考虑性能、扩展性、安全性等因素，并不断优化和调整设计。索引在数据库中，建立索引是为了提高数据检索的速度和效率。当数据量较大，频繁进行查询操作时，索引可以大幅度减少数据库的查询时间。下面从浅入深地介绍数据库中的索引，包括索引的作用和重要性：索引的作用：加快数据检索速度：索引可以提供快速的数据访问路径，通过索引可以直接定位到包含所需数据的存储位置，避免了全表扫描的开销。减少磁盘I/O操作：索引可以将数据存储在有序的结构中，减少了磁盘I/O操作的次数。通过索引的辅助，数据库可以快速定位到存储数据的位置，减少了磁盘寻道时间和数据传输时间。提高数据查询效率：索引能够加速查询语句的执行速度，特别是对于复杂的查询和连接操作。通过使用索引，数据库可以快速定位到满足查询条件的数据，提高查询效率。优化排序和分组操作：当对数据进行排序或分组操作时，索引可以提供有序的数据，减少排序和分组的开销。通过索引可以直接按照索引列的顺序获取数据，避免了额外的排序操作。保证数据的完整性和唯一性：通过在索引列上创建唯一索引或主键索引，可以确保数据的唯一性。索引可以强制执行唯一性约束，防止重复数据的插入和更新。索引的重要性：提高响应时间：索引可以大幅度减少查询语句的执行时间，提高数据库的响应速度。对于大型数据库和复杂查询，索引的使用尤为重要。降低系统负载：索引可以减少数据库系统的负载，通过提供快速的数据访问路径，减少了CPU和存储系统的负荷。索引可以使数据库更高效地处理查询请求。优化应用性能：索引的存在可以提高应用程序的性能和用户体验。查询结果的快速返回可以使应用程序更加响应，并提供更好的用户体验。支持数据完整性：通过在索引列上创建唯一索引或主键索引，可以确保数据的完整性和一致性。索引可以防止重复数据的插入和更新，保证数据的准确性。支持复杂查询和连接操作：索引可以加速复杂查询和连接…

【ASP.NET Core开发者学习路线】——路线总览
type: item
date: 2023-05-22T03:24:00+00:00

【ASP.NET Core开发者学习路线】 1.1 通用开发技能—HTTP/HTTPS协议和TLS/SSL
type: item
date: 2023-05-22T03:01:22+00:00
description: 引言： 在当今数字化时代，网络通信扮演着至关重要的角色。了解通信协议，尤其是HTTP/HTTPS和TLS/SSL，对于理解网络通信原理和保护数据安全至关重要。本篇教程将以通俗易懂的方式介绍HTTP/HTTPS协议和TLS/SSL协议，同时提供一些示例帮助你更好地理解和学习这些通信协议。第一部分：HTTP协议HTTP（超文本传输协议）是用于在客户端和服务器之间传输数据的协议。它是基于请求-响应模型的，客户端发送一个HTTP请求到服务器，服务器返回一个HTTP响应作为回应。在本部分，我将详细讲解HTTP协议的原理和实现，并涵盖以下内容：HTTP请求方法： HTTP定义了多种请求方法，用于指定客户端对服务器的期望操作。常见的请求方法包括：GET：从服务器获取资源。POST：向服务器提交数据，通常用于提交表单或发送数据。PUT：将数据存储到服务器上的指定位置。DELETE：从服务器删除指定资源。HTTP请求结构： 一个HTTP请求由以下部分组成：请求行：包含请求方法、请求的URL和HTTP协议的版本。请求头部：包含关于请求的附加信息，如请求的主机、内容类型等。请求主体：可选部分，用于传输请求的数据，例如POST请求中的表单数据。HTTP响应结构： 一个HTTP响应由以下部分组成：状态行：包含HTTP协议的版本、状态码和状态消息。响应头部：包含关于响应的附加信息，如内容类型、响应的日期等。响应主体：包含响应的实际数据，如HTML页面、图片等。HTTP状态码： 服务器通过状态码向客户端传达请求的处理结果。常见的状态码包括：2xx（成功）：表示请求成功处理，如200表示成功获取请求的资源。3xx（重定向）：表示需要进一步操作以完成请求，如301表示永久重定向。4xx（客户端错误）：表示客户端的请求有误，如404表示未找到请求的资源。5xx（服务器错误）：表示服务器在处理请求时发生错误，如500表示服务器内部错误。HTTP持久连接： HTTP协议默认为短连接，即每个请求/响应周期都需要建立和关闭连接。为了提高性能，引入了HTTP持久连接，允许多个请求/响应共享同一条连接，减少了连接建立的开销。HTTP报文格式： HTTP使用纯文本的报文格式进行通信。每个HTTP请求和响应都以起始行开始，后跟头部和可选的主体部分。报文使用空行分隔头部和主体。HTTP实现方式： HTTP协议可以通过各种编程语言和工具实现。常见的方式包括使用服务器软件（如Apache、Nginx）来处理HTTP请求和响应，或使用编程语言中的HTTP库（如Python的Requests库、Java的HttpURLConnection类）来发送和接收HTTP请求。总结： HTTP协议是互联网上应用最广泛的协议之一，通过请求-响应模型实现客户端和服务器之间的数据传输。理解HTTP协议的原理和实现方式，包括请求方法、请求结构、响应结构、状态码、持久连接、报文格式和实现方式，将帮助你更好地理解和构建基于HTTP的网络应用。第二部分：HTTPS协议HTTPS（超文本传输安全协议）是HTTP的安全版本，通过使用TLS/SSL协议对数据进行加密和身份验证。下面我将详细讲解HTTPS协议的原理和实现，包括以下内容：加密与数据保护： HTTPS使用TLS/SSL协议加密传输的数据，确保在客户端和服务器之间的通信过程中数据不会被窃听或篡改。TLS/SSL使用对称加密和非对称加密相结合的方式来实现数据的加密和解密。数字证书与身份验证： HTTPS使用数字证书对服务器进行身份验证。证书是由受信任的证书颁发机构（CA）颁发的，其中包含服务器的公钥和其他身份信息。客户端可以通过验证证书的合法性和信任度，确保与正确的服务器建立安全连接。HTTPS握手过程： 当客户端与服务器建立HTTPS连接时，会经历以下握手过程：客户端向服务器发送一个客户端Hello消息，其中包含支持的加密算法和其他参数。服务器回应一个服务器Hello消息，选择一个加密算法，并返回服务器的数字证书。客户端验证证书的合法性和可信任性，然后生成一个用于加密通信的会话密钥。客户端将加密的会话密钥发送给服务器，双方使用该密钥进行后续通信的加密和解密。HTTPS证书链验证： 为了验证服务器证书的合法性和可信任性，客户端会构建证书链并进行验证。证书链由服务器证书、中间证书和根证书组成。客户端会检查服务器证书是否由受信任的证书颁发机构签发，以及中间证书是否与根证书相关联，确保整个证书链的可信度。HTTPS实现方式： HTTPS的实现方式涉及以下方面：服务器配置：服务器需要安装有效的数字证书，配置支持TLS/SSL协议的安全套接字层。客户端支持：客户端需要支持TLS/SSL协议，包括验证证书、协商加密算法等。证书管理：服务器证书需要由受信任的证书颁发机构颁发…

【ASP.NET Core开发者学习路线】 5.1 设计原则—SOLID
type: item
date: 2023-05-19T08:49:00+00:00
description: 引言：在软件开发领域，构建稳定可靠的软件是开发人员的终极目标。然而，随着软件规模和复杂性的增加，我们往往面临着代码难以理解、维护困难、可扩展性差等问题。为了解决这些问题，Robert C. Martin（也被称为Uncle Bob）提出了SOLID设计原则，这些原则是帮助我们编写高质量、可维护、可扩展的软件的指导方针。本文将通俗易懂地介绍SOLID设计原则，以帮助读者更好地理解和应用这些原则。1. 单一职责原则（Single Responsibility Principle）单一职责原则是SOLID设计原则中的首要原则。它强调一个类或模块应该只有一个改变的原因。换句话说，一个类应该只负责一项职责或功能。为什么需要单一职责原则？当一个类承担过多的职责时，代码往往会变得复杂、难以理解和维护。一个类涉及多个职责会增加其耦合性，使得对其中一个职责的修改可能影响其他职责的正常运行。这种紧密耦合的设计使得代码脆弱，难以扩展和修改。通过遵循单一职责原则，我们可以降低类的复杂性，提高代码的可读性和可维护性。如何实践单一职责原则?理解职责的定义：在设计一个类时，首先要明确它的职责是什么。一个职责可以定义为一个类需要承担的特定功能或关注点。确保一个类只有一个职责有助于保持代码的清晰和简洁。分离不同的职责：如果一个类承担了多个职责，考虑将这些职责分离成独立的类或模块。每个类应该专注于解决一个问题，并提供清晰的接口和方法。遵循高内聚低耦合的原则：高内聚表示类的成员函数和数据在逻辑上紧密相关，低耦合表示类与其他类之间的依赖关系较弱。通过设计高内聚低耦合的类，我们可以确保每个类都具有清晰的职责，且它们之间的关系松散。使用设计模式：设计模式是一些经过验证的解决方案，可以帮助我们实现单一职责原则。例如，使用策略模式可以将不同的算法或策略分离到独立的类中，每个类负责一个特定的算法。单一职责原则的好处：遵循单一职责原则带来以下好处：提高代码的可读性：每个类只关注一个职责，代码更加清晰，易于理解。提高代码的可维护性：当一个类只负责一项职责时，对其中一个职责的修改不会影响其他职责的代码，减少了引入错误的风险，降低了修改的复杂性。改善代码的复用性：拥有单一职责的类更容易被其他模块或系统复用，因为它们关注的是一个具体的功能或关注点。增强系统的可扩展性：通过将不同职责分离到独立的类中，我们可以更容易地添加新的功能或修改现有功能，而不会影响到其他部分的代码。总结：单一职责原则是SOLID设计原则中的关键原则之一。遵循单一职责原则可以帮助我们构建清晰、可维护和可扩展的软件系统。通过将不同的职责分离到独立的类中，我们可以降低代码的复杂性，提高代码的可读性和可维护性。在实践中，要明确类的职责，合理地分离职责，并遵循高内聚低耦合的设计原则。通过这些实践，我们可以编写出高质量的代码，为软件开发打下坚实的基础。2. 开放封闭原则（Open-Closed Principle）开放封闭原则是SOLID设计原则中的一项重要原则，它要求软件实体（类、模块、函数等）应该对扩展开放，对修改关闭。换句话说，当需要添加新功能时，不应该修改已有的代码，而是通过扩展现有代码来实现。为什么需要开放封闭原则？在软件开发过程中，需求的变化是不可避免的。如果每次需求变更都要修改已有的代码，将会带来一系列问题。首先，修改代码可能会导致原本正常工作的功能出现错误。其次，频繁的修改可能会引入新的错误，并增加代码维护的复杂性。开放封闭原则的目标是通过良好的设计来减少修改的需求，从而提高软件系统的稳定性、可扩展性和可维护性。如何实践开放封闭原则？使用抽象和接口：通过使用抽象类、接口或基类，我们可以定义稳定的API，客户端代码可以依赖于这些抽象而不是具体的实现。当需要添加新功能时，我们可以创建新的实现类来扩展这些抽象，而无需修改现有的代码。使用多态：多态是面向对象编程的重要特性之一。通过多态，我们可以使用父类或接口类型来引用子类的实例，从而实现基于接口的编程。这使得我们可以在不修改现有代码的情况下，通过引入新的子类来添加新功能。使用设计模式：设计模式提供了一些经过验证的解决方案，可以帮助我们实现开放封闭原则。例如，使用策略模式可以通过定义不同的策略类来实现不同的行为，而无需修改客户端代码。其他常用的模式如装饰器模式、观察者模式等也可以用于实现开放封闭原则。封装变化：将可能发生变化的部分封装起来。当需求发生变化时，只需要修改封装的部分而不是整个代码。这样可以降低修改的范围，减少引入错误的风险。开放封闭原则的好处：遵循开放封闭原则带来以下好处：可维护性：通过遵循开放封闭原则，我们可以在不修改现有代码的情况下添加新功能。这使得代码更易于维护，减少了引入错误的风险。可扩展性：通过扩展现有代码而不是修改它，我们可以实现系统的可扩展性。…

【ASP.NET Core开发者学习路线】 2.1 C#基础—多线程
type: item
date: 2023-05-19T07:27:00+00:00
description: 引言在现代软件开发中，多线程编程变得越来越重要。通过充分利用多核处理器的优势，我们可以提高应用程序的性能和响应能力。在.NET平台上，多线程编程变得相对容易，并提供了丰富的工具和库来帮助我们实现并发操作。本篇博客将向您介绍如何在.NET中进行多线程编程，并提供通俗易懂的解释和代码示例。1. 什么是多线程？在计算机科学中，线程是一种执行单元，它可以在程序中独立运行。一个线程可以执行一系列指令，而不受其他线程的干扰。多线程编程指的是同时执行多个线程以实现并发操作。2. 为什么使用多线程？使用多线程可以带来以下好处：提高性能: 多线程允许我们同时执行多个任务，从而加快程序的执行速度。提高响应能力: 在GUI应用程序中，使用多线程可以避免主线程被阻塞，使用户界面保持响应，同时在后台执行任务。提高资源利用率: 当一个线程被阻塞时，另一个线程可以继续执行，充分利用CPU和其他系统资源。3. 在.NET中创建和启动线程在.NET中，我们可以使用System.Threading.Thread类创建和启动线程。以下是创建和启动线程的示例代码：csharpCopy codeusing System; using System.Threading; class Program { static void Main() { // 创建一个新线程并指定执行的方法 Thread thread = new Thread(DoWork); // 启动线程 thread.Start(); // 主线程继续执行其他任务 Console.WriteLine("Main thread is doing some work..."); // 等待线程完成 thread.Join(); Console.WriteLine("Thread completed!"); } static void DoWork() { // 在这里编写线程执行的代码 Console.WriteLine("Thread is doing some work..."); Thread.Sleep(2000); // 模拟线程执行耗时操作 } }上述代码创建了一个新线程，并使用DoWork方法作为线程的执行方法。Thread.Start方法用于启动线程，而Join方法用于等待线程完成。请注意，在主线程中我们可以继续执行其他任务，而不必等待新线程完成。4. 同步与互斥在多线程编程中，同步和互斥是两个重要的概念。同步指的是协调线程之间的执行顺序，而互斥指的是控制线程对共享资源的访问。4.1 同步在.NET中，可以使用lock语句实现线程的同步。lock语句用于获取指定对象的互斥锁，以确保只有一个线程可以访问被保护的代码块。以下是一个使用lock语句实现同步的示例：csharpCopy codeusing System; using System.Threading; class Program { static int count = 0; static object lockObject = new object(); static void Main() { Thread thread1 = new Thread(IncrementCount); Thread thread2 = new Thread(IncrementCount); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine("Count: " + count); } static void IncrementCount() { for (int i = 0; i < 1000000; i++) { lock (lockObject) { count++; } } } }在上述代码中，我们使用了一个静态变量count作为共享资源，并创建了两个线程来增加该计数器的值。通过使用lock (lockObject)语句，我们确保只有一个线程可以同时访问count变量，从而避免竞态条件（race condition）。4.2 互斥除了使用lock语句外，.NET还提供了更高级的互斥机制，如Monitor类和Mutex类。这些类可以用于实现更复杂的同步和互斥需求。下面是一个使用Mutex类实现互斥的示例：csharpCopy codeusing System; using System.Threading; class Program { static int count = 0; static Mutex mutex = new Mutex(); static void Main() { Thread thread1 = new T…