<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>VanishingBlog</title>
  
  
  <link href="http://vanishing.cc/atom.xml" rel="self"/>
  
  <link href="http://vanishing.cc/"/>
  <updated>2026-04-19T10:47:46.499Z</updated>
  <id>http://vanishing.cc/</id>
  
  <author>
    <name>Vanish</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>AI导读</title>
    <link href="http://vanishing.cc/2026/04/19/AI%E5%AF%BC%E8%AF%BB/"/>
    <id>http://vanishing.cc/2026/04/19/AI%E5%AF%BC%E8%AF%BB/</id>
    <published>2026-04-19T10:47:46.000Z</published>
    <updated>2026-04-19T10:47:46.499Z</updated>
    
    <content type="html"><![CDATA[<h1 id="欢迎来到-vanish-的技术花园"><a class="markdownIt-Anchor" href="#欢迎来到-vanish-的技术花园"></a> 🤖 欢迎来到 Vanish 的技术花园</h1><blockquote><p><strong>更新时间</strong>: 2026-04-19 18:47:46<br /><strong>质量概览</strong>: 收录文章 51 篇，平均分 <strong>69.9</strong>。本文由 AI (deepseek-ai/DeepSeek-V3) 自动生成。</p></blockquote><h2 id="博客概览"><a class="markdownIt-Anchor" href="#博客概览"></a> 🧭 博客概览</h2><p><strong>博客概览</strong>：</p><p>这位博主是<strong>图形学与渲染技术</strong>的深度探索者！从标题就能看出，TA专注于<strong>实时渲染、光线追踪、阴影算法</strong>（如DDGI、PBR、Distance Field软阴影），同时涉及<strong>底层原理</strong>（如Split Sum、PCF到MSM的演进）和<strong>工程落地</strong>（Sheen/Anisotropy的实践）。技术栈可能涵盖<strong>Rust</strong>（Rain Rust管线）、<strong>C++/HLSL</strong>（图形API），甚至还有<strong>DotNet</strong>的趣味吐槽。风格硬核但不死板——既有理论推导的严谨，也有“入坑到入坟”的幽默，偶尔还会插播<strong>Git疑难杂症</strong>这类实用技巧。如果你想啃图形学“黑魔法”或学一手骚操作，这里绝对值得蹲守！ 🚀</p><h2 id="关于作者"><a class="markdownIt-Anchor" href="#关于作者"></a> 👨‍💻 关于作者</h2><p><strong>Vanish</strong> 是一位热衷于探索计算机底层奥秘的开发者。他的座右铭是 <code>KeepLearning</code>。从图形学渲染算法到 .NET 框架的源码分析，他总是试图透过现象看本质，用代码构建更真实的世界。</p><h2 id="编辑精选-top-10"><a class="markdownIt-Anchor" href="#编辑精选-top-10"></a> 🏆 编辑精选 (Top 10)</h2><h3 id="-swig0-span-stylefont-size08em-color88888分span"><a class="markdownIt-Anchor" href="#-swig0-span-stylefont-size08em-color88888分span"></a> 🥇 <a href="/2024/11/04/Graphics/Rendering/Split%20Sum%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D/" title="Split Sum原理介绍">Split Sum原理介绍</a> <span style='font-size:0.8em; color:#888;'>(88分)</span></h3><blockquote><p><strong>📝 摘要</strong>: Split Sum通过预计算BRDF滤波和光照滤波加速环境光照积分，显著提升PBS的IBL渲染效率。</p><p><strong>💡 推荐</strong>: 深入解析实时渲染核心技术，实用性强。</p></blockquote><h3 id="-swig1-span-stylefont-size08em-color88885分span"><a class="markdownIt-Anchor" href="#-swig1-span-stylefont-size08em-color88885分span"></a> 🥈 <a href="/2024/07/09/Graphics/Rendering/DDGI%E4%BB%8B%E7%BB%8D/" title="DDGI介绍">DDGI介绍</a> <span style='font-size:0.8em; color:#888;'>(85分)</span></h3><blockquote><p><strong>📝 摘要</strong>: DDGI是NVIDIA基于光线追踪的动态漫反射全局光照技术，实现动态场景实时全局光照，解决传统探针技术问题。</p><p><strong>💡 推荐</strong>: 深入解析前沿实时渲染技术，干货满满。</p></blockquote><h3 id="-swig2-span-stylefont-size08em-color88882分span"><a class="markdownIt-Anchor" href="#-swig2-span-stylefont-size08em-color88882分span"></a> 🥉 <a href="/2026/04/19/Graphics/Rendering/Rain%20Rust%20%E7%9A%84%202D%20%E5%85%89%E8%BF%BD%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/" title="Rain Rust 的 2D 光追渲染管线">Rain Rust 的 2D 光追渲染管线</a> <span style='font-size:0.8em; color:#888;'>(82分)</span></h3><blockquote><p><strong>📝 摘要</strong>: [文章介绍了基于JFA算法的2D光追渲染管线，实现动态光影效果并达到120FPS高性能。]</p><p><strong>💡 推荐</strong>: [创新2D光追方案，兼具性能与视觉效果。]</p></blockquote><h3 id="4-swig3-span-stylefont-size08em-color88882分span"><a class="markdownIt-Anchor" href="#4-swig3-span-stylefont-size08em-color88882分span"></a> 4. <a href="/2024/11/02/Graphics/Theory/%E4%BB%8EPCF%E5%88%B0MSM/" title="从PCF到MSM">从PCF到MSM</a> <span style='font-size:0.8em; color:#888;'>(82分)</span></h3><blockquote><p><strong>📝 摘要</strong>: 从PCF到MSM的软阴影技术演进，通过数学优化在保证视觉效果的同时提升渲染性能。</p><p><strong>💡 推荐</strong>: 深入浅出解析软阴影技术发展，适合图形学开发者。</p></blockquote><h3 id="5-swig4-span-stylefont-size08em-color88878分span"><a class="markdownIt-Anchor" href="#5-swig4-span-stylefont-size08em-color88878分span"></a> 5. <a href="/2025/09/23/Graphics/Rendering/Sheen%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="Sheen从理论到实践">Sheen从理论到实践</a> <span style='font-size:0.8em; color:#888;'>(78分)</span></h3><blockquote><p><strong>📝 摘要</strong>: 详解glTF Sheen材质扩展实现原理，包括Charlie分布BRDF模型及天鹅绒材质模拟。</p><p><strong>💡 推荐</strong>: 深入浅出解析PBR渲染核心技术，实用性强。</p></blockquote><h3 id="6-swig5-span-stylefont-size08em-color88878分span"><a class="markdownIt-Anchor" href="#6-swig5-span-stylefont-size08em-color88878分span"></a> 6. <a href="/2025/09/23/Programming/DotNet/DotNet%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B,%E4%B8%80%E9%94%AE%E5%85%A5%E5%9D%91%E5%88%B0%E5%85%A5%E5%9D%9F/" title="DotNet响应式编程,一键入坑到入坟">DotNet响应式编程,一键入坑到入坟</a> <span style='font-size:0.8em; color:#888;'>(78分)</span></h3><blockquote><p><strong>📝 摘要</strong>: 深入解析Rx.NET响应式编程核心概念与实践指南，涵盖IObservable到最佳实践全流程。</p><p><strong>💡 推荐</strong>: 系统全面，实战导向，助.NET开发者掌握Rx精髓。</p></blockquote><h3 id="7-swig6-span-stylefont-size08em-color88876分span"><a class="markdownIt-Anchor" href="#7-swig6-span-stylefont-size08em-color88876分span"></a> 7. <a href="/2025/03/07/Graphics/Rendering/Anisotropy%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="Anisotropy从理论到实践">Anisotropy从理论到实践</a> <span style='font-size:0.8em; color:#888;'>(76分)</span></h3><blockquote><p><strong>📝 摘要</strong>: 文章详解各向异性渲染实现，涵盖切线空间、纹理映射和GGX分布，对比UE4与glTF方案。</p><p><strong>💡 推荐</strong>: 深入浅出讲解PBR各向异性渲染关键技术。</p></blockquote><h3 id="8-swig7-span-stylefont-size08em-color88875分span"><a class="markdownIt-Anchor" href="#8-swig7-span-stylefont-size08em-color88875分span"></a> 8. <a href="/2024/11/03/Graphics/Rendering/Distance%20Field%20soft%20shadows/" title="Distance Field soft shadows">Distance Field soft shadows</a> <span style='font-size:0.8em; color:#888;'>(75分)</span></h3><blockquote><p><strong>📝 摘要</strong>: [介绍基于SDF的软阴影技术，通过RayMarching计算安全角度并转换为可见性，兼顾实时性与效率。]</p><p><strong>💡 推荐</strong>: [高效实时软阴影方案，适合图形学开发者。]</p></blockquote><h3 id="9-swig8-span-stylefont-size08em-color88872分span"><a class="markdownIt-Anchor" href="#9-swig8-span-stylefont-size08em-color88872分span"></a> 9. <a href="/2024/11/03/Graphics/Rendering/PBR--%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E9%99%85/" title="PBR--从理论到实际">PBR--从理论到实际</a> <span style='font-size:0.8em; color:#888;'>(72分)</span></h3><blockquote><p><strong>📝 摘要</strong>: [PBR技术详解：从辐射度量学到BRDF模型，实现物理准确的光照渲染]</p><p><strong>💡 推荐</strong>: [深入浅出讲解PBR原理，理论与实战结合]</p></blockquote><h3 id="10-swig9-span-stylefont-size08em-color88868分span"><a class="markdownIt-Anchor" href="#10-swig9-span-stylefont-size08em-color88868分span"></a> 10. <a href="/2024/06/14/Tools/%E3%80%90TroubleShooting%E3%80%91git%E6%8F%90%E7%A4%BA%E6%96%87%E4%BB%B6%E8%BF%87%E5%A4%A7/" title="【TroubleShooting】git提示文件过大">【TroubleShooting】git提示文件过大</a> <span style='font-size:0.8em; color:#888;'>(68分)</span></h3><blockquote><p><strong>📝 摘要</strong>: Git处理大文件的三种方法：忽略、删除历史记录或使用git-lfs扩展管理。</p><p><strong>💡 推荐</strong>: 提供实用解决方案，有效解决Git大文件提交问题。</p></blockquote><h2 id="完整归档-按质量排序"><a class="markdownIt-Anchor" href="#完整归档-按质量排序"></a> 📂 完整归档 (按质量排序)</h2><table><thead><tr><th style="text-align:center">评分</th><th style="text-align:left">文章标题</th></tr></thead><tbody><tr><td style="text-align:center">88</td><td style="text-align:left"><a href="/2024/11/04/Graphics/Rendering/Split%20Sum%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D/" title="Split Sum原理介绍">Split Sum原理介绍</a></td></tr><tr><td style="text-align:center">85</td><td style="text-align:left"><a href="/2024/07/09/Graphics/Rendering/DDGI%E4%BB%8B%E7%BB%8D/" title="DDGI介绍">DDGI介绍</a></td></tr><tr><td style="text-align:center">82</td><td style="text-align:left"><a href="/2026/04/19/Graphics/Rendering/Rain%20Rust%20%E7%9A%84%202D%20%E5%85%89%E8%BF%BD%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/" title="Rain Rust 的 2D 光追渲染管线">Rain Rust 的 2D 光追渲染管线</a></td></tr><tr><td style="text-align:center">82</td><td style="text-align:left"><a href="/2024/11/02/Graphics/Theory/%E4%BB%8EPCF%E5%88%B0MSM/" title="从PCF到MSM">从PCF到MSM</a></td></tr><tr><td style="text-align:center">78</td><td style="text-align:left"><a href="/2025/09/23/Graphics/Rendering/Sheen%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="Sheen从理论到实践">Sheen从理论到实践</a></td></tr><tr><td style="text-align:center">78</td><td style="text-align:left"><a href="/2025/09/23/Programming/DotNet/DotNet%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B,%E4%B8%80%E9%94%AE%E5%85%A5%E5%9D%91%E5%88%B0%E5%85%A5%E5%9D%9F/" title="DotNet响应式编程,一键入坑到入坟">DotNet响应式编程,一键入坑到入坟</a></td></tr><tr><td style="text-align:center">76</td><td style="text-align:left"><a href="/2025/03/07/Graphics/Rendering/Anisotropy%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="Anisotropy从理论到实践">Anisotropy从理论到实践</a></td></tr><tr><td style="text-align:center">75</td><td style="text-align:left"><a href="/2024/11/03/Graphics/Rendering/Distance%20Field%20soft%20shadows/" title="Distance Field soft shadows">Distance Field soft shadows</a></td></tr><tr><td style="text-align:center">72</td><td style="text-align:left"><a href="/2024/11/03/Graphics/Rendering/PBR--%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E9%99%85/" title="PBR--从理论到实际">PBR--从理论到实际</a></td></tr><tr><td style="text-align:center">68</td><td style="text-align:left"><a href="/2024/06/14/Tools/%E3%80%90TroubleShooting%E3%80%91git%E6%8F%90%E7%A4%BA%E6%96%87%E4%BB%B6%E8%BF%87%E5%A4%A7/" title="【TroubleShooting】git提示文件过大">【TroubleShooting】git提示文件过大</a></td></tr><tr><td style="text-align:center">65</td><td style="text-align:left"><a href="/2024/10/28/Graphics/Rendering/Early-Z%20and%20Pre-Z%20--%20%E4%BB%8B%E7%BB%8D%E4%B8%8E%E5%8C%BA%E5%88%AB/" title="Early-Z and Pre-Z -- 介绍与区别">Early-Z and Pre-Z -- 介绍与区别</a></td></tr><tr><td style="text-align:center">65</td><td style="text-align:left"><a href="/2024/07/15/Graphics/Rendering/NeRF%E4%BB%8B%E7%BB%8D/" title="NeRF介绍">NeRF介绍</a></td></tr><tr><td style="text-align:center">65</td><td style="text-align:left"><a href="/2024/10/22/Graphics/Rendering/%E5%90%AF%E5%8F%91%E5%BC%8F%E7%AE%97%E6%B3%95/" title="启发式算法">启发式算法</a></td></tr><tr><td style="text-align:center">65</td><td style="text-align:left"><a href="/2024/10/22/Programming/Cpp/C++%20-%20STL%E7%9A%84%E7%A5%9E%E5%A5%87%E7%94%A8%E6%B3%95/" title="C++ - STL的神奇用法">C++ - STL的神奇用法</a></td></tr><tr><td style="text-align:center">65</td><td style="text-align:left"><a href="/2026/03/18/Programming/Cpp/%E4%BB%8E%E7%A1%AC%E4%BB%B6%E5%BA%95%E5%B1%82%E5%BC%80%E5%A7%8B%E7%90%86%E8%A7%A3c++%E5%86%85%E5%AD%98%E5%BA%8F/" title="从硬件底层开始理解c++内存序">从硬件底层开始理解c++内存序</a></td></tr><tr><td style="text-align:center">58</td><td style="text-align:left"><a href="/2025/02/23/GameEngine/Unity/Unity%20Render%20Graph%20%E7%B3%BB%E7%BB%9F%E4%BB%8B%E7%BB%8D/" title="Unity Render Graph 系统介绍">Unity Render Graph 系统介绍</a></td></tr><tr><td style="text-align:center">55</td><td style="text-align:left"><a href="/2025/09/23/Graphics/Rendering/ClearCoat%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="ClearCoat从理论到实践">ClearCoat从理论到实践</a></td></tr><tr><td style="text-align:center">55</td><td style="text-align:left"><a href="/2024/10/23/GameEngine/Unity/%E9%9D%99%E6%80%81%E6%89%B9%E5%A4%84%E7%90%86%20&%20%E5%8A%A8%E6%80%81%E6%89%B9%E5%A4%84%E7%90%86%20&%20GPU%20Instancing/" title="静态批处理 &amp; 动态批处理 &amp; GPU Instancing">静态批处理 &amp; 动态批处理 &amp; GPU Instancing</a></td></tr><tr><td style="text-align:center">52</td><td style="text-align:left"><a href="/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" title="Irridescence从理论到实践">Irridescence从理论到实践</a></td></tr></tbody></table><hr /><p><em>注：所有评分与评语均由 AI 自动生成，仅供参考。</em></p>]]></content>
    
    
    <summary type="html">AI 自动生成的博客导航，带你快速了解本站精华内容。</summary>
    
    
    
    <category term="Blog" scheme="http://vanishing.cc/categories/Blog/"/>
    
    
    <category term="AI" scheme="http://vanishing.cc/tags/AI/"/>
    
    <category term="导读" scheme="http://vanishing.cc/tags/%E5%AF%BC%E8%AF%BB/"/>
    
    <category term="精选" scheme="http://vanishing.cc/tags/%E7%B2%BE%E9%80%89/"/>
    
  </entry>
  
  <entry>
    <title>Rain Rust 的 2D 光追渲染管线</title>
    <link href="http://vanishing.cc/2026/04/19/Graphics/Rendering/Rain%20Rust%20%E7%9A%84%202D%20%E5%85%89%E8%BF%BD%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/"/>
    <id>http://vanishing.cc/2026/04/19/Graphics/Rendering/Rain%20Rust%20%E7%9A%84%202D%20%E5%85%89%E8%BF%BD%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/</id>
    <published>2026-04-19T10:00:00.000Z</published>
    <updated>2026-04-19T10:46:19.579Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：《Rain Rust 的 2D 光追渲染管线》该文介绍了一个基于JFA算法的2D光线追踪渲染管线，包含光源绘制、自发光物体标记和跳转洪水算法(JFA)等核心阶段，实现了动态光影效果，在RTX 5070ti和M4 GPU上均能达到120FPS。</p></blockquote><h1 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h1><blockquote><p>Rain Rust 是作者的毕业设计作品代号, 以下也以Rain Rust作为作品的名称.</p></blockquote><p>Rain Rust 是一款以精确动作、解密、探索为主要玩法的2d平台跳跃游戏.</p><p>游戏中玩家将探索一个被弃用的神秘工厂, 其中存在着各式由一种特殊物质“耦合蜜”驱动的机械造物, 而玩家可以利用自身的能力去控制它们运作.</p><p>而玩家的探索会将自己置身于一场更大的阴谋当中.</p><p>‍</p><p>本项目开源在GitHub, 你可以点击<a href="https://github.com/Vanishing-Games/RainRust">这里</a>进行访问, 欢迎留下一个小星星.</p><p>‍</p><h1 id="画面效果"><a class="markdownIt-Anchor" href="#画面效果"></a> 画面效果</h1><p>你可以观看这个视频来了解画面效果.</p><p>由于平台原因, 此视频可能无法观看, 你也可以点击<a href="https://www.bilibili.com/video/BV12sdZB2EWk/?spm_id_from=333.1387.homepage.video_card.click&amp;vd_source=e83b8f8e3c1120e91a8d3bbe191efb81">这个链接</a>到笔者的bilibili账号中观看视频.</p><p>对于不想观看视频的读者, 也提供了一些游戏截图来了解效果</p><p><video controls="controls" src="2026-03-15 09-18-31-20260419164720-1eiqows.mkv"></video></p><p><img src="PixPin_2026-04-19_16-47-58-20260419164800-9vi3ojh.png" alt="PixPin_2026-04-19_16-47-58" title="Rain Rust 画面效果演示01" /></p><p><img src="PixPin_2026-04-19_16-48-48-20260419164909-24bic6u.png" alt="PixPin_2026-04-19_16-48-48" title="Rain Rust 画面效果演示02" /></p><p><img src="PixPin_2026-04-19_16-49-47-20260419164948-zz01mjr.png" alt="PixPin_2026-04-19_16-49-47" title="Rain Rust 画面效果演示03" /></p><p><img src="PixPin_2026-04-19_16-50-40-20260419165043-svax2ec.png" alt="PixPin_2026-04-19_16-50-40" title="Rain Rust 画面效果演示04" /></p><p><img src="PixPin_2026-03-25_23-58-48-20260419165013-5b1vpys.png" alt="PixPin_2026-03-25_23-58-48" title="Rain Rust 画面效果演示05-出bug版" /></p><h2 id="优点"><a class="markdownIt-Anchor" href="#优点"></a> 优点</h2><ul><li>这是光追</li><li>很cool的画面风格</li><li>完全动态的世界</li></ul><h2 id="缺点"><a class="markdownIt-Anchor" href="#缺点"></a> 缺点</h2><ul><li>这是光追</li><li>jfa算法生成的sdf with true sdf间有偏差, 导致比如物体角落难以采样到光源</li><li>[TODO] 在镜头移动时, 光场会剧烈抖动</li><li>[TODO] 暂时不支持半透明物体</li></ul><h2 id="性能"><a class="markdownIt-Anchor" href="#性能"></a> 性能</h2><p>测试环境1: 120 fps</p><ul><li><p>系统: windows</p></li><li><p>GPU: RTX 5070ti desktop</p></li><li><p>分辨率: 4k</p></li><li><p>sample count: 256</p></li><li><p>resolution scale: 1</p></li></ul><p>测试环境2: 120 fps</p><ul><li>系统: macOS</li><li>GPU: M4</li><li>分辨率: 1080p</li><li>sample count: 128</li><li>resolution scale: 1</li></ul><p>‍</p><h1 id="管线概览"><a class="markdownIt-Anchor" href="#管线概览"></a> 管线概览</h1><h2 id="pass-overview"><a class="markdownIt-Anchor" href="#pass-overview"></a> Pass Overview</h2><p>这是此项目的Render grpah viewer.</p><p>其中绿框部分为 Rain Rust 的部分.</p><p><img src="PixPin_2026-04-19_17-35-01-20260419173504-xeazexq.png" alt="PixPin_2026-04-19_17-35-01" /></p><h2 id="stage-1-绘制光源"><a class="markdownIt-Anchor" href="#stage-1-绘制光源"></a> Stage 1: 绘制光源</h2><ul><li>绘制的所有原始光源, 用于生成光场图</li></ul><p><img src="PixPin_2026-04-19_17-29-05-20260419172915-ert5ml0.png" alt="PixPin_2026-04-19_17-29-05" title="Light Source Pass" /></p><h2 id="stage-2-绘制自发光物体"><a class="markdownIt-Anchor" href="#stage-2-绘制自发光物体"></a> Stage 2: 绘制自发光物体</h2><p>绘制所有自发光物体, 用于标记哪些像素不需要收光场影响</p><p><img src="PixPin_2026-04-19_17-36-41-20260419173643-w4v71ic.png" alt="PixPin_2026-04-19_17-36-41" title="Receiver Pass" /></p><h2 id="stage-3-jfa"><a class="markdownIt-Anchor" href="#stage-3-jfa"></a> Stage 3: JFA</h2><p>使用 Stage 1 的Light Sorce Map 跑一遍JFA算法</p><p><img src="PixPin_2026-04-19_17-36-56-20260419173701-d406bp8.png" alt="PixPin_2026-04-19_17-36-56" title="JFA_Init" /></p><p>‍</p><p><img src="PixPin_2026-04-19_17-40-30-20260419174032-ghsr2kp.gif" alt="PixPin_2026-04-19_17-40-30" title="JFA算法步骤" /></p><h2 id="stage-4-jfa-生成-sdf"><a class="markdownIt-Anchor" href="#stage-4-jfa-生成-sdf"></a> Stage 4: JFA 生成 SDF</h2><p>用 JFA 算法的结果生成 SDF</p><p><img src="PixPin_2026-04-19_17-45-30-20260419174532-ie52a3l.png" alt="PixPin_2026-04-19_17-45-30" title="SDF" /></p><h2 id="stage-5-raytracing-生成光场图"><a class="markdownIt-Anchor" href="#stage-5-raytracing-生成光场图"></a> Stage 5: RayTracing 生成光场图</h2><p>根据 SDF 以及 Light Source Map 来计算光场图</p><p><img src="PixPin_2026-04-19_17-46-57-20260419174700-m1lagmn.png" alt="PixPin_2026-04-19_17-46-57" /></p><h2 id="stage-6-composition"><a class="markdownIt-Anchor" href="#stage-6-composition"></a> Stage 6: Composition</h2><p>组合一系列计算结果, 生成最终结果</p><p><img src="PixPin_2026-04-19_17-47-43-20260419174748-iti5zrk.png" alt="PixPin_2026-04-19_17-47-43" /></p><h1 id="具体细节"><a class="markdownIt-Anchor" href="#具体细节"></a> 具体细节</h1><h2 id="管线假设"><a class="markdownIt-Anchor" href="#管线假设"></a> 管线假设</h2><ul><li>所有sprite的纹理都是自发光贴图</li><li>光线衰减并不遵从平方反比定律, 而是GTR (Generalized Trowbridge-Reitz)函数</li></ul><h2 id="管线切入点"><a class="markdownIt-Anchor" href="#管线切入点"></a> 管线切入点</h2><ul><li>在渲染完urp的所有半透明物体之后</li></ul><p><img src="PixPin_2026-04-19_18-04-21-20260419180428-5c8kdij.png" alt="PixPin_2026-04-19_18-04-21" title="Urp 渲染结果" /></p><h2 id="为什么需要stage-2"><a class="markdownIt-Anchor" href="#为什么需要stage-2"></a> 为什么需要Stage 2</h2><ul><li>由于所有sprite的纹理都是自发光贴图</li><li>所以对于sprite不需要再受光照影响</li><li>因此在Stage 2中, 绘制了一张深度图, 用于Stage 6(合成)的深度测试</li><li>即如果是自发光像素, 直接过, 否则就查询光场进行着色</li></ul><h2 id="光场图缩放以优化性能"><a class="markdownIt-Anchor" href="#光场图缩放以优化性能"></a> 光场图缩放以优化性能</h2><ul><li>对于光场图, 我们完全可以降低分辨率来优化性能</li><li>而且这并不会对画面产生太大影响</li></ul><h2 id="jfa算法"><a class="markdownIt-Anchor" href="#jfa算法"></a> JFA算法</h2><p>可以参考笔者的<a href="http://vanishing.cc/2026/01/08/GameEngine/Unity/%E5%9C%A8%20Unity%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20JFA%20(Jump%20Flood%20Algorithm)%20%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90%20SDF%20(Signed%20Distance%20Field)/">这篇文章</a></p><p>这里直接引用原文:</p><blockquote><h1 id="algorithm"><a class="markdownIt-Anchor" href="#algorithm"></a> Algorithm</h1><p>首先我们有一张 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>∗</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">N*N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 的<strong>种子图</strong></p><p>比如下图:</p><p><img src="image-20260108022110-7itixin.png" alt="image" title="种子" /></p><p>其中有颜色的地方为种子, 没有的则是’未定义’</p><p>随着算法迭代, 最终整张图的想读都会被’定义’</p><p>‍</p><p>伪代码如下:</p><p>对于每个步长 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>∈</mo><mo stretchy="false">{</mo><mfrac><mi>N</mi><mn>2</mn></mfrac><mo separator="true">,</mo><mfrac><mi>N</mi><mn>4</mn></mfrac><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">k \in \{ \frac{N}{2}, \frac{N}{4}, \dots, 1 \}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.73354em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mopen">{</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord">1</span><span class="mclose">}</span></span></span></span>, 执行一次 JFA</p><p>遍历<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mclose">)</span></span></span></span>处的每一个像素<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>p</mi></mrow><annotation encoding="application/x-tex">p</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">p</span></span></span></span></p><pre><code>  对于每一个在$(x+i,y+j)$处的像素$q$ ( $i, j \in \&#123;-k, 0, k\&#125;$)  如果$p$未定义且$q$着色  将$p$的颜色更改为$q$的颜色  如果$p$着色且$q$着色  $p$的颜色使用 `min(dist(p,s),dist(q,s'))`, 其中, $s$ 和 $s'$ 分别是 $p$ 和 $q$ 的种子颜色</code></pre><p><img src="PixPin_2026-01-08_02-53-23-20260108025327-auhf4ph.gif" alt="PixPin_2026-01-08_02-53-23" title="算法示例" /></p><h1 id="jfa-to-sdf"><a class="markdownIt-Anchor" href="#jfa-to-sdf"></a> JFA to SDF</h1><p>我们让JFA种子图中的每个像素存储当前位置的UV坐标，那么最终我们就得到了一张<strong>存储了离该像素最近的“物体像素”的 UV 坐标</strong>的纹理</p><p>那么, 轻易可以得到:<br />​<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mi>D</mi><mi>F</mi><mo>=</mo><mi>d</mi><mi>i</mi><mi>s</mi><mi>t</mi><mi>a</mi><mi>n</mi><mi>c</mi><mi>e</mi><mo stretchy="false">(</mo><mtext>当前像素坐标</mtext><mo separator="true">,</mo><mtext>最近物体像素坐标</mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">SDF = distance(当前像素坐标, 最近物体像素坐标)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">i</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">a</span><span class="mord mathnormal">n</span><span class="mord mathnormal">c</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord cjk_fallback">当</span><span class="mord cjk_fallback">前</span><span class="mord cjk_fallback">像</span><span class="mord cjk_fallback">素</span><span class="mord cjk_fallback">坐</span><span class="mord cjk_fallback">标</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord cjk_fallback">最</span><span class="mord cjk_fallback">近</span><span class="mord cjk_fallback">物</span><span class="mord cjk_fallback">体</span><span class="mord cjk_fallback">像</span><span class="mord cjk_fallback">素</span><span class="mord cjk_fallback">坐</span><span class="mord cjk_fallback">标</span><span class="mclose">)</span></span></span></span></p><blockquote><p>当然, 这里没有考虑屏幕比例</p></blockquote><p><img src="image-20260108030913-zmy1t64.png" alt="image" title="最终结果" /></p></blockquote><h2 id="ray-tracing-2d"><a class="markdownIt-Anchor" href="#ray-tracing-2d"></a> Ray Tracing 2D</h2><p>将三维空间的路径追踪降低为二维, 只需要将原本的向球(或者半球)采样变成向圆采样即可</p><p>伪代码如:</p><pre class="line-numbers language-hlsl" data-language="hlsl"><code class="language-hlsl"><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">float</span> f <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">.</span><span class="token punctuation">;</span> f <span class="token operator">&lt;</span> _Samples<span class="token punctuation">;</span> f<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> <span class="token keyword">float</span> t <span class="token operator">=</span> f <span class="token operator">/</span> _Samples <span class="token operator">*</span> <span class="token keyword">float</span><span class="token punctuation">(</span><span class="token number">3.1415926</span> <span class="token operator">*</span> <span class="token number">2.0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result <span class="token operator">+=</span> <span class="token function">Trace</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span>uv<span class="token punctuation">,</span> <span class="token keyword">float2</span><span class="token punctuation">(</span><span class="token function">cos</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">sin</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> _Aspect<span class="token punctuation">.</span>xy<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>而对于<code>Trace()</code>函数, 由于我们已经拥有了一张SDF, 我们使用Ray Marching 的方式计算结果</p><pre class="line-numbers language-hlsl" data-language="hlsl"><code class="language-hlsl"><span class="token keyword">float3</span> <span class="token function">Trace</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">float2</span> uv<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">float2</span> dir<span class="token punctuation">)</span> <span class="token comment">// Ray Marching</span><span class="token punctuation">&#123;</span>    <span class="token keyword">float2</span> uvPos <span class="token operator">=</span> uv<span class="token punctuation">;</span> <span class="token comment">// 当前采样坐标</span>    <span class="token comment">// 若起始点已在光源上, 直接返回颜色</span>    <span class="token keyword">const</span> <span class="token keyword">float4</span> color <span class="token operator">=</span> <span class="token function">tex2D</span><span class="token punctuation">(</span>_ColorTex<span class="token punctuation">,</span> uv<span class="token punctuation">)</span><span class="token punctuation">.</span>rgba<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>color<span class="token punctuation">.</span>a <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> color<span class="token punctuation">.</span>rgb <span class="token operator">/</span> color<span class="token punctuation">.</span>a<span class="token punctuation">;</span>        <span class="token comment">// 步进</span>    uvPos <span class="token operator">+=</span> dir <span class="token operator">*</span> <span class="token function">tex2D</span><span class="token punctuation">(</span>_DistTex<span class="token punctuation">,</span> uvPos<span class="token punctuation">)</span><span class="token punctuation">.</span>rr<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">NotUVSpace</span><span class="token punctuation">(</span>uvPos<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> _AmbientColor<span class="token punctuation">;</span>                    <span class="token punctuation">[</span>unroll<span class="token punctuation">]</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> n <span class="token operator">&lt;</span> STEPS<span class="token punctuation">;</span> n<span class="token operator">++</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token keyword">const</span> <span class="token keyword">float4</span> color <span class="token operator">=</span> <span class="token function">tex2D</span><span class="token punctuation">(</span>_ColorTex<span class="token punctuation">,</span> uvPos<span class="token punctuation">)</span><span class="token punctuation">.</span>rgba<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>color<span class="token punctuation">.</span>a <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token punctuation">&#123;</span>            <span class="token comment">// 使用 GTR 衰减</span>            <span class="token keyword">float</span> attenuation <span class="token operator">=</span> <span class="token function">GTRAttenuation</span><span class="token punctuation">(</span><span class="token punctuation">(</span>uv <span class="token operator">-</span> uvPos<span class="token punctuation">)</span> <span class="token operator">*</span> _Aspect<span class="token punctuation">.</span>xy<span class="token punctuation">,</span> _LightFalloffAlpha <span class="token operator">*</span> color<span class="token punctuation">.</span>a<span class="token punctuation">,</span> _LightFalloffGamma<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> color<span class="token punctuation">.</span>rgb <span class="token operator">*</span> attenuation<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        uvPos <span class="token operator">+=</span> dir <span class="token operator">*</span> <span class="token function">tex2D</span><span class="token punctuation">(</span>_DistTex<span class="token punctuation">,</span> uvPos<span class="token punctuation">)</span><span class="token punctuation">.</span>rr<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">NotUVSpace</span><span class="token punctuation">(</span>uvPos<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> _AmbientColor<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>        <span class="token keyword">return</span> _AmbientColor<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><h2 id="composition"><a class="markdownIt-Anchor" href="#composition"></a> Composition</h2><p><img src="PixPin_2026-04-19_17-47-43-20260419174748-iti5zrk.png" alt="PixPin_2026-04-19_17-47-43" /></p><p>我们有以下输入:</p><ul><li>Urp绘制结果: 最底层的背景</li><li>Light Map: 光场图</li><li>Emissive: 自发光图</li><li>Emissive Depth: 自发光深度图</li></ul><p>我们的混合方案伪代码如下:</p><pre class="line-numbers language-hlsl" data-language="hlsl"><code class="language-hlsl"><span class="token keyword">if</span> <span class="token punctuation">(</span>hasEmissive<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 直接使用 Emissive 的结果 (与背景进行 Alpha 混合以保证透明物体正确渲染)</span>    finalColor <span class="token operator">=</span> <span class="token function">lerp</span><span class="token punctuation">(</span>background<span class="token punctuation">.</span>rgb<span class="token punctuation">,</span> receiver<span class="token punctuation">.</span>rgb<span class="token punctuation">,</span> receiver<span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">else</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 没有记录的地方: 混合光照结果和 main 的结果</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token function">defined</span><span class="token punctuation">(</span>LIGHTING_BLEND_ADDITIVE<span class="token punctuation">)</span></span></span>    finalColor <span class="token operator">=</span> background<span class="token punctuation">.</span>rgb <span class="token operator">+</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">elif</span> <span class="token expression"><span class="token function">defined</span><span class="token punctuation">(</span>LIGHTING_BLEND_ALPHABLEND<span class="token punctuation">)</span></span></span>    finalColor <span class="token operator">=</span> <span class="token function">lerp</span><span class="token punctuation">(</span>background<span class="token punctuation">.</span>rgb<span class="token punctuation">,</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">,</span> lighting<span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">elif</span> <span class="token expression"><span class="token function">defined</span><span class="token punctuation">(</span>LIGHTING_BLEND_MULTIPLY<span class="token punctuation">)</span></span></span>    finalColor <span class="token operator">=</span> background<span class="token punctuation">.</span>rgb <span class="token operator">*</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">elif</span> <span class="token expression"><span class="token function">defined</span><span class="token punctuation">(</span>LIGHTING_BLEND_SCREEN<span class="token punctuation">)</span></span></span>    finalColor <span class="token operator">=</span> <span class="token number">1.0</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> background<span class="token punctuation">.</span>rgb<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">elif</span> <span class="token expression"><span class="token function">defined</span><span class="token punctuation">(</span>LIGHTING_BLEND_OVERLAY<span class="token punctuation">)</span></span></span>    finalColor <span class="token operator">=</span> <span class="token punctuation">(</span>background<span class="token punctuation">.</span>rgb <span class="token operator">&lt;</span> <span class="token number">0.5</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token number">2.0</span> <span class="token operator">*</span> background<span class="token punctuation">.</span>rgb <span class="token operator">*</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> <span class="token number">2.0</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> background<span class="token punctuation">.</span>rgb<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">else</span></span>    finalColor <span class="token operator">=</span> background<span class="token punctuation">.</span>rgb <span class="token operator">+</span> lighting<span class="token punctuation">.</span>rgb<span class="token punctuation">;</span>    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token keyword">float4</span><span class="token punctuation">(</span>finalColor<span class="token punctuation">,</span> background<span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="时间复杂度"><a class="markdownIt-Anchor" href="#时间复杂度"></a> 时间复杂度</h2><p>定义:</p><ul><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span>: 光线采样数</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>W</mi></mrow><annotation encoding="application/x-tex">W</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span></span></span></span>: 光场图宽度</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>H</mi></mrow><annotation encoding="application/x-tex">H</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span></span></span></span>: 光场图高度</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span>: 迭代深度</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>C</mi><mi>o</mi><mi>s</mi><mi>t</mi></mrow><mrow><mi>i</mi><mi>n</mi><mi>t</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>e</mi><mi>c</mi><mi>t</mi></mrow></msub></mrow><annotation encoding="application/x-tex">{Cost}_{intersect}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>: 相交检测开销</li></ul><p>与传统路径追踪对比:</p><table><thead><tr><th></th><th>RainRust</th><th>Path Tracing</th></tr></thead><tbody><tr><td>时间复杂度</td><td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>S</mi><mo>⋅</mo><mi>W</mi><mo>⋅</mo><mi>H</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(S \cdot W \cdot H)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mclose">)</span></span></span></span></td><td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>S</mi><mo>⋅</mo><mi>W</mi><mo>⋅</mo><mi>H</mi><mo>⋅</mo><mi>D</mi><mo>⋅</mo><msub><mtext>Cost</mtext><mrow><mi>i</mi><mi>n</mi><mi>t</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>e</mi><mi>c</mi><mi>t</mi></mrow></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(S \cdot W \cdot H \cdot D \cdot \text{Cost}_{intersect})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord text"><span class="mord">Cost</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></td></tr><tr><td>迭代深度(<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span>)</td><td>1</td><td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span> 次反弹</td></tr><tr><td>相交检测开销<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>C</mi><mi>o</mi><mi>s</mi><mi>t</mi></mrow><mrow><mi>i</mi><mi>n</mi><mi>t</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>e</mi><mi>c</mi><mi>t</mi></mrow></msub></mrow><annotation encoding="application/x-tex">{Cost}_{intersect}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></td><td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord">1</span><span class="mclose">)</span></span></span></span> (SDF 纹理采样)</td><td><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>log</mi><mo>⁡</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(\log N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span> (BVH 等加速结构) 或 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span></td></tr><tr><td>预处理开销</td><td>JFA 生成 SDF (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>W</mi><mi>H</mi><mi>log</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>max</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>W</mi><mo separator="true">,</mo><mi>H</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(WH \log(\max(W,H)))</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mopen">(</span><span class="mop">max</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mclose">)</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span>)</td><td>需要构建加速结构 (如 BVH)  其中:<br />- 中值划分 (Median Split): <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mi>log</mi><mo>⁡</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span><br />- SAH 启发式 (Surface Area Heuristic): <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mi>log</mi><mo>⁡</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span> 到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><msup><mo><mi>log</mi><mo>⁡</mo></mo><mn>2</mn></msup><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log^2 N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.148448em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8984479999999999em;"><span style="top:-3.1473400000000002em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span><br />- 线性 BVH (LBVH): <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mi>log</mi><mo>⁡</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span><br />- KD-Tree: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><msup><mo><mi>log</mi><mo>⁡</mo></mo><mn>2</mn></msup><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log^2 N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.148448em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8984479999999999em;"><span style="top:-3.1473400000000002em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span> 或 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mi>log</mi><mo>⁡</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span><br />- 均匀网格 (Uniform Grid): <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mo>+</mo><mi>G</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N + G)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mclose">)</span></span></span></span></td></tr></tbody></table>]]></content>
    
    
    <summary type="html">《Rain Rust 的 2D 光追渲染管线》该文介绍了一个基于JFA算法的2D光线追踪渲染管线，包含光源绘制、自发光物体标记和跳转洪水算法(JFA)等核心阶段，实现了动态光影效果，在RTX 5070ti和M4 GPU上均能达到120FPS。</summary>
    
    
    
    <category term="Rendering" scheme="http://vanishing.cc/categories/Rendering/"/>
    
    
    <category term="Graphics" scheme="http://vanishing.cc/tags/Graphics/"/>
    
    <category term="Unity" scheme="http://vanishing.cc/tags/Unity/"/>
    
    <category term="Rendering" scheme="http://vanishing.cc/tags/Rendering/"/>
    
    <category term="RayTracing" scheme="http://vanishing.cc/tags/RayTracing/"/>
    
    <category term="Rust" scheme="http://vanishing.cc/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>从硬件底层开始理解c++内存序</title>
    <link href="http://vanishing.cc/2026/03/18/Programming/Cpp/%E4%BB%8E%E7%A1%AC%E4%BB%B6%E5%BA%95%E5%B1%82%E5%BC%80%E5%A7%8B%E7%90%86%E8%A7%A3c++%E5%86%85%E5%AD%98%E5%BA%8F/"/>
    <id>http://vanishing.cc/2026/03/18/Programming/Cpp/%E4%BB%8E%E7%A1%AC%E4%BB%B6%E5%BA%95%E5%B1%82%E5%BC%80%E5%A7%8B%E7%90%86%E8%A7%A3c++%E5%86%85%E5%AD%98%E5%BA%8F/</id>
    <published>2026-03-18T03:30:00.000Z</published>
    <updated>2026-04-19T10:46:25.054Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文从CPU缓存一致性、内存一致性等硬件基础出发，解析C++三种内存序模型（顺序一致/获取-释放/松散）的工作原理与适用场景，帮助开发者理解底层内存同步机制。</p></blockquote><h1 id="从硬件底层开始理解c内存序"><a class="markdownIt-Anchor" href="#从硬件底层开始理解c内存序"></a> 从硬件底层开始理解c++内存序</h1><h1 id="本文概览"><a class="markdownIt-Anchor" href="#本文概览"></a> 本文概览</h1><blockquote><ul><li>缓存一致性</li><li>内存一致性</li><li>访存一致性</li><li>内存序</li></ul></blockquote><h1 id="intro"><a class="markdownIt-Anchor" href="#intro"></a> Intro</h1><blockquote><p>本文所有代码, 可在这个<a href="https://github.com/Vanish0314/CppConsistenccy">repo</a>查看:</p></blockquote><ul><li>缓存一致性: <strong>CPU硬件</strong>层面, 确保多个处理器<strong>缓存</strong>同一内存地址的数据副本时, 状态是<strong>同步</strong>的</li><li>内存一致性: <strong>架构和语言</strong>层面, 定义一个线程中的多次<strong>内存访问（读/写）操作</strong>，在<strong>什么时间</strong>、以<strong>什么顺序</strong>对<strong>其他线程可见</strong></li><li>访存一致性: 就是内存一致性</li></ul><p>‍</p><p>其中, 为了确保缓存一致性, 有各种协议, 详见下文</p><p>同样的, 为了确保内存一致性,  各语言都有对应的内存模型. 比如由Gemini整理的下表</p><table><thead><tr><th><strong>编程语言</strong></th><th><strong>内存管理机制 (Management)</strong></th><th><strong>并发内存模型 (Concurrency Model)</strong></th><th><strong>核心内存特性 (Key Features)</strong></th></tr></thead><tbody><tr><td><strong>C / C++</strong></td><td><strong>手动管理</strong>(malloc/free, RAII)</td><td><strong>弱一致性 / 硬件级原子操作</strong></td><td>极致的内存控制权；程序员需手动处理生存期，极易产生悬空指针和溢出。</td></tr><tr><td><strong>Rust</strong></td><td><strong>所有权系统</strong>(Ownership/Borrow)</td><td><strong>内存安全并发</strong>(Send/Sync Trait)</td><td><strong>无需 GC</strong>但保证内存安全；通过编译期检查确保没有数据竞争，性能顶级。</td></tr><tr><td><strong>Java</strong></td><td><strong>自动 GC</strong>(分代收集, G1/ZGC)</td><td><strong>JMM (Happens-Before)</strong></td><td>虚拟机屏蔽底层细节；强类型安全，依赖<code>volatile</code>​和<code>synchronized</code>保证可见性。</td></tr><tr><td><strong>C#</strong></td><td><strong>自动 GC</strong>(分代收集, Gen 0/1/2)</td><td><strong>.NET 内存模型 (类似 JMM)</strong></td><td>兼具 Java 的安全与 C++ 的灵活性；支持<strong>值类型 (struct)</strong> 减少堆压力，支持<code>unsafe</code>指针。</td></tr><tr><td><strong>Go</strong></td><td><strong>自动 GC</strong>(三色标记, 低延迟)</td><td><strong>CSP 模型 (Channel)</strong></td><td>提倡“通过通信共享内存”；GC 针对微秒级停顿优化，适合高并发网络服务。</td></tr><tr><td><strong>Haskell</strong></td><td><strong>自动 GC</strong>(针对不可变优化)</td><td><strong>STM (软件事务内存)</strong></td><td><strong>默认不可变</strong>与​<strong>惰性求值</strong>；内存中存储 Thunk（计算延迟），并发编程天然无锁且安全。</td></tr><tr><td><strong>Python</strong></td><td><strong>引用计数 + 循环 GC</strong></td><td><strong>GIL (全局解释器锁)</strong></td><td>开发极其简便；但 GIL 限制了多核物理并行，内存占用随对象头信息膨胀。</td></tr><tr><td><strong>JavaScript</strong></td><td><strong>自动 GC</strong>(分代回收)</td><td><strong>单线程事件循环</strong></td><td>异步非阻塞架构；主线程无竞争压力，多线程依赖<code>SharedArrayBuffer</code>共享内存。</td></tr></tbody></table><p>‍</p><p>C<ins>的内存序是C</ins>内存模型的重要表现, 有三种逻辑模型:</p><ul><li><p>顺序一致性模型 (Sequential Consistency)</p><ul><li>​<code>memory_order_seq_cst</code></li></ul></li><li><p>获取-释放模型 (Acquire-Release Ordering)</p><ul><li>​<code>memory_order_acquire</code>​、<code>memory_order_release</code>​、<code>memory_order_acq_rel</code>​、<code>memory_order_consume</code></li></ul></li><li><p>松散内存序模型 (Relaxed Ordering)</p><ul><li>​<code>memory_order_relaxed</code></li></ul></li></ul><p>‍</p><h1 id="缓存一致性"><a class="markdownIt-Anchor" href="#缓存一致性"></a> 缓存一致性</h1><h2 id="多核cpu架构"><a class="markdownIt-Anchor" href="#多核cpu架构"></a> 多核CPU架构</h2><p>以上是一个多核CPU架构的示意图.</p><p>然而在现实中, 占据芯片85%左右面积的区域是Cache.</p><p>为什么要引入Cache? 根据 Jeff Dean Boer的 “Latency Numbers Every Programmer Should Know”</p><pre class="line-numbers language-伪代码" data-language="伪代码"><code class="language-伪代码">Latency Comparison Numbers (~2012)----------------------------------L1 cache reference                           0.5 nsBranch mispredict                            5   nsL2 cache reference                           7   ns                      14x L1 cacheMutex lock&#x2F;unlock                           25   nsMain memory reference                      100   ns                      20x L2 cache, 200x L1 cacheCompress 1K bytes with Zippy             3,000   ns        3 usSend 1K bytes over 1 Gbps network       10,000   ns       10 usRead 4K randomly from SSD*             150,000   ns      150 us          ~1GB&#x2F;sec SSDRead 1 MB sequentially from memory     250,000   ns      250 usRound trip within same datacenter      500,000   ns      500 usRead 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB&#x2F;sec SSD, 4X memoryDisk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtripRead 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSDSend packet CA-&gt;Netherlands-&gt;CA    150,000,000   ns  150,000 us  150 msNotes-----1 ns &#x3D; 10^-9 seconds1 us &#x3D; 10^-6 seconds &#x3D; 1,000 ns1 ms &#x3D; 10^-3 seconds &#x3D; 1,000 us &#x3D; 1,000,000 nsCredit------By Jeff Dean:               http:&#x2F;&#x2F;research.google.com&#x2F;people&#x2F;jeff&#x2F;Originally by Peter Norvig: http:&#x2F;&#x2F;norvig.com&#x2F;21-days.html#answersContributions-------------&#39;Humanized&#39; comparison:    https:&#x2F;&#x2F;gist.github.com&#x2F;hellerbarde&#x2F;2843375Visual comparison chart:   http:&#x2F;&#x2F;i.imgur.com&#x2F;k0t1e.pngInteractive Prezi version: https:&#x2F;&#x2F;prezi.com&#x2F;pdkvgys-r0y6&#x2F;latency-numbers-for-programmers-web-development&#x2F;latency.txt<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到, 速度上, CPU和内存间有几个数量级的差距</p><p>因此,现代CPU 有多级缓存, 一般来说:</p><ul><li>共享的L3 缓存</li><li>独享的L2 缓存</li><li>独享的L1 缓存, 又分为 <strong>d-cache</strong> 和 <strong>i-cache</strong></li></ul><p>然而多级缓存的存在会引入 多级存储器的基本问题:</p><ol><li><strong>映像规则</strong>: 当把一个块调入高一层级的存储器时, 可以放到哪些位置?</li><li><strong>查找方法</strong>: 如何找到该块?</li><li><strong>替换策略</strong>: 当发生失效(高层级存储器不够用)时, 应该替换哪一块?<br />当cpu访问某一个内存地址, cache miss从而要加载新的块时, 发现容量无法满足了, 就会发生替换</li><li><strong>写策略</strong>: 当进行写操作时, 应该进行哪些操作?</li></ol><h3 id="写策略"><a class="markdownIt-Anchor" href="#写策略"></a> 写策略</h3><p>在介绍具体策略前, 可以大致地了解一下各个存储器访问的时间片消耗</p><table><thead><tr><th>从CPU到</th><th>大约需要的CPU周期</th><th>大约需要的时间(单位ns)</th></tr></thead><tbody><tr><td>寄存器</td><td>1 cycle</td><td></td></tr><tr><td>L1 Cache</td><td>~3-4 cycles</td><td>~0.5-1 ns</td></tr><tr><td>L2 Cache</td><td>~10-20 cycles</td><td>~3-7 ns</td></tr><tr><td>L3 Cache</td><td>~40-45 cycles</td><td>~15 ns</td></tr><tr><td>内存</td><td>~120-240 cycles</td><td>~60-120ns</td></tr></tbody></table><h4 id="write-through写直达"><a class="markdownIt-Anchor" href="#write-through写直达"></a> Write Through(写直达)</h4><p>每一次cpu写数据时, 都写回内存</p><p>由于写直达每一次都会写回内存, 导致消耗时间片太多了, 因此有了写回策略</p><h4 id="write-back写回"><a class="markdownIt-Anchor" href="#write-back写回"></a> Write Back(写回)</h4><p>只把数据写回cache, 只当缓存行被替换时才更新到内存中</p><p>但是这种策略在多核处理器中就会导致缓存不一致</p><p>比如两个核心都去操作共同的变量 a, b.</p><p>由于数据没有被写回到内存中, 就会导致cpu各干各的, 导致结果可能让人意外</p><p>这就是缓存一致性问题</p><h3 id="缓存一致性解决措施"><a class="markdownIt-Anchor" href="#缓存一致性解决措施"></a> 缓存一致性解决措施</h3><p>核心问题是: <strong>如何同步两个核心的缓存数据</strong></p><p><strong>一致性的内存系统: 所有处理器在任何时刻, 对每一个</strong>​<strong>​<code>数据项</code>​</strong>​<strong>的最后一个全局写入值, 有一个一致的视角</strong></p><blockquote><p>值得注意的是, 这里说的是数据项, 即逻辑上的 变量 <code>a</code>​ 这样的.<br />而非内存地址,  因为变量<code>a</code>在不同的内存地址上可能出现值不同,  需要区分</p></blockquote><p>即我们不希望发生下面这种情况:</p><p>CPU1和CPU4都在写<code>a</code>​, CPU2 和 CPU3 都在读<code>a</code></p><p>一致性的内存系统中, 2和3 读到的<code>a</code>的顺序一定是相同的 100 200或者200 100</p><p>而实现3的<strong>关键</strong>在于:</p><ul><li><strong>相关性</strong>: 一个数据项的任何读写均可得到该数据最近被写的值</li><li><strong>一致性</strong>: 一个处理器何时读到另一个处理器最近更新的内容<br />即当数据不一致时,什么时候同步数据</li></ul><p>当前主流有两种协议</p><h4 id="监听协议"><a class="markdownIt-Anchor" href="#监听协议"></a> 监听协议</h4><ul><li><strong>工作机制</strong>：所有物理核心的缓存都通过一个共享的**总线（Bus）**或互连网络进行通信，并时刻监控（监听）总线上的数据事务。当某个核心（如 CPU1）修改了本地缓存中的数据时，它必须向总线广播这一修改请求</li><li><strong>一致性维护</strong>：其他核心通过总线“监听”到该变量已被修改。如果它们也持有该数据的副本，则会根据协议将其缓存中的对应**缓存行（Cache Line）**标记为无效</li><li><strong>性能特征</strong>：由于所有核心都能看到总线上的每一笔交易，这种协议在核心数量较少时非常高效。但其代价是会引发**缓存乒乓（Cache Ping-pong）**效应：当多个核心频繁写入同一缓存行时，该行数据会在核心之间反复传输，导致处理器因等待同步信号而停顿（Stall），严重影响性能</li></ul><h4 id="目录协议"><a class="markdownIt-Anchor" href="#目录协议"></a> 目录协议</h4><ul><li><strong>工作机制</strong>：在核心数量众多的现代多处理器系统中，总线广播会占用过多带宽。目录协议通过维护一个**全局目录（Directory）**来解决此问题，该目录记录了每个缓存行当前被哪些核心的缓存所持有及其读写状态</li><li><strong>精准通知</strong>：当一个核心需要修改某个数据项时，它不再向全网广播，而是首先查阅目录。由硬件逻辑根据目录记录，仅向真正持有该数据副本的核心发送失效指令</li><li><strong>应用场景</strong>：这种协议通过减少不必要的广播通信，极大地提高了系统的<strong>扩展性（Scalability）</strong> ，是解决大规模并行计算中数据一致性瓶颈的关键技术。尽管其内部逻辑比监听协议复杂，但它能更有效地管理跨核心的数据可见性</li></ul><p>‍</p><h1 id="内存一致性"><a class="markdownIt-Anchor" href="#内存一致性"></a> 内存一致性</h1><h2 id="为什么要有c的内存模型"><a class="markdownIt-Anchor" href="#为什么要有c的内存模型"></a> 为什么要有C++的内存模型</h2><p>由于现代多核cpu以及缓存架构的引入, 为了确保Cache一致性, 各家CPU都有自己的解决方案, 从而导致软件层面(编译器)也会有对应的优化.</p><p>同时, 为了提高CPU的执行效率, 硬件上也有一些设计(见下文), 同样的, 传导到编译器也有对应的优化.</p><p>这些CPU机制与编译器优化共同造成了一个问题: <strong>程序并不按我写的那样执行, 但它保证单线程下结果一致</strong></p><p>比如<code>clip1</code>中所演示:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;int main() &#123;    int a &#x3D; 1;    int b &#x3D; 2;    int c &#x3D; a + b;   &#x2F;&#x2F; (1)    int d &#x3D; a * b;   &#x2F;&#x2F; (2)    std::cout &lt;&lt; c &lt;&lt; &quot; &quot; &lt;&lt; d &lt;&lt; std::endl;&#125;&#x2F;* Release 下生成的汇编代码:Line 10: std::cout &lt;&lt; c &lt;&lt; &quot; &quot; &lt;&lt; d &lt;&lt; std::endl;  mov    edx, 3    ; 直接把计算结果 3 (a+b) 放入寄存器  lea    rcx, OFFSET FLAT:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A  call   ??6...    ; 输出 3  lea    rdx, OFFSET FLAT:??_C@_01CLKCMJKC@?5@ ; 加载空格 &quot; &quot;  mov    rcx, rax  call   ??$...    ; 输出空格  mov    edx, 2    ; 直接把计算结果 2 (a*b) 放入寄存器  mov    rcx, rax  call   ??6...    ; 输出 2*&#x2F;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到, 除了输出一致, 基本上是两个程序了.</p><p>‍</p><p>我们先看一下CPU与编译器会做哪些优化</p><ul><li><p>编译器</p><ul><li>指令重排(Reordering)</li><li>冗余与死代码删除(Remove)</li><li>Invention</li><li>变量寄存器化/缓存化</li></ul></li><li><p>CPU</p><ul><li>指令流水线(Pipelining)与超标量执行(Superscalar)</li><li>乱序执行(Out-of-Order Execution, OoO)</li><li>寄存器重命名（Register Renaming）</li><li>分支预测（Branch Prediction）</li></ul></li><li><p>Cache</p><ul><li>Store Buffers</li></ul></li></ul><p>‍</p><h3 id="编译器"><a class="markdownIt-Anchor" href="#编译器"></a> 编译器</h3><h4 id="指令重排"><a class="markdownIt-Anchor" href="#指令重排"></a> 指令重排</h4><p>编译器会根据寄存器占用情况和指令流水线效率，调整不具依赖关系的语句顺序</p><p>例如，在代码中先后写入变量 <code>A</code>​ 和 <code>B</code>​，编译器可能认为先写 <code>B</code> 更快，从而在生成的汇编代码中调换顺序</p><p>像<code>compiler_reorder</code>中的示例:</p><pre class="line-numbers language-伪代码" data-language="伪代码"><code class="language-伪代码">int A &#x3D; 0, B &#x3D; 0;void foo()&#123;    A &#x3D; B + 1;  &#x2F;&#x2F;(1)    B &#x3D; 1;      &#x2F;&#x2F;(2) &#125;int main()&#123;    foo();    return 0;&#125;&#x2F;* Release 下生成的汇编代码: ?foo@@YAXXZ PROC    ; foo 函数开始 ; Line 12: A &#x3D; B + 1;     mov    eax, DWORD PTR ?B@@3HA    ; 将 B 的值读入 eax     inc    eax                       ; eax &#x3D; eax + 1 (即 B + 1)  ; Line 13: B &#x3D; 1;     mov    DWORD PTR ?B@@3HA, 1      ; 把 1 写入 B (!!! 注意这里)     mov    DWORD PTR ?A@@3HA, eax    ; 把 eax 的值写入 A     ret    0 ?foo@@YAXXZ ENDP逻辑顺序：    * 在 C++ 源码中，先给 A 赋值，再给 B 赋值    * 但在汇编中，编译器执行了 mov DWORD PTR ?B@@3HA, 1（给 B 赋值），然后才执行 mov DWORD PTR ?A@@3HA, eax（给 A 赋值） *&#x2F;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="冗余与死代码删除remove"><a class="markdownIt-Anchor" href="#冗余与死代码删除remove"></a> 冗余与死代码删除(Remove)</h4><p>如果编译器通过静态分析认为某个循环内的检查是多余的（例如单线程下循环条件始终为真），它可能会直接优化掉该检查。这会导致多线程发出的修改信号（如 <code>done</code> 标志位）被循环体忽略</p><p>比如<code>compiler_remove</code>中的示例:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">int flag &#x3D; 0;&#x2F;&#x2F; 示例 1: 冗余写入删除void redundant_store()&#123;    flag &#x3D; 1; &#x2F;&#x2F; 编译器认为这个写入是“冗余”的，因为紧接着就被覆盖了    flag &#x3D; 2; &#x2F;&#x2F; 最终生效的值&#125;&#x2F;* Release 下生成的汇编代码: ?redundant_store@@YAXXZ PROC     mov    DWORD PTR ?flag@@3HA, 2  ; 直接把 2 写入 flag，完全跳过了写入 1 的指令     ret    0 ?redundant_store@@YAXXZ ENDP编译器发现 flag &#x3D; 1 之后没有任何读取操作就直接被 flag &#x3D; 2 覆盖了，因此认为第一步是徒劳的，直接将其删除。*&#x2F;&#x2F;&#x2F; 示例 2: 死代码删除void dead_code()&#123;    int local_var &#x3D; 100;    local_var &#x3D; local_var * 2; &#x2F;&#x2F; 这个计算虽然发生了，但结果从未被使用    &#x2F;&#x2F; 函数结束，计算结果被直接丢弃&#125;&#x2F;* Release 下生成的汇编代码: ?dead_code@@YAXXZ PROC ; Line 22     ret    0    ; 什么都没做，直接返回 ?dead_code@@YAXXZ ENDPlocal_var 是一个局部变量，它的计算结果没有通过返回值、全局变量或函数调用传递出去。编译器判定这段代码对外部世界“毫无贡献”，因此整段逻辑被移除。*&#x2F;int main()&#123;    redundant_store();    dead_code();    return 0;&#125;&#x2F;*这种优化在单线程中是完美的，但在多线程中可能导致严重的并发问题：    假设一个线程（生产者）正在执行 redundant_store，而另一个线程（消费者）正在轮询 flag 的值：        1 &#x2F;&#x2F; 消费者线程        2 while (flag !&#x3D; 1) &#123;        3     &#x2F;&#x2F; 等待状态 1 的出现，以执行某些中间初始化逻辑        4 &#125;    * 由于编译器的 Remove 优化，flag &#x3D; 1 这个中间状态在机器码层面根本不存在    * 消费者线程将永远阻塞在 while 循环中，或者直接跳过状态 1 看到状态 2。这种“状态丢失”会导致多线程状态机的逻辑崩溃    可以查看 src&#x2F;compiler_remove_concurrency.cpp 以了解这个问题的实际演示。*&#x2F;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="invention"><a class="markdownIt-Anchor" href="#invention"></a> Invention</h4><p>由于CPU的Speculative execution</p><p>可能导致如下代码优化:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">&#x2F;&#x2F; Invention示例代码&#x2F;&#x2F; 原始代码if( cond ) x &#x3D; 42;&#x2F;&#x2F; 优化后代码r1 &#x3D; x;&#x2F;&#x2F; read what&#39;s therex &#x3D; 42;&#x2F;&#x2F; oops: optimistic write is not conditionalif( !cond)&#x2F;&#x2F; check if we guessed wrong    x &#x3D; r1;&#x2F;&#x2F; oops: back-out write is not SC<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然而在<code>compiler_invention</code>示例中可以看到输出:</p><p>没能成功复现这个现象</p><p>‍</p><h3 id="cpu"><a class="markdownIt-Anchor" href="#cpu"></a> CPU</h3><h4 id="指令流水线pipelining-超标量执行superscalar-乱序执行out-of-order-execution-ooo"><a class="markdownIt-Anchor" href="#指令流水线pipelining-超标量执行superscalar-乱序执行out-of-order-execution-ooo"></a> 指令流水线(Pipelining) &amp; 超标量执行(Superscalar) &amp; 乱序执行（Out-of-Order Execution, OoO）</h4><p>在CPU中, 执行一条指令有三个阶段: 取指, 译码, 执行</p><p>在早期的CPU中, 一条指令必须等待前一条指令执行完毕才会被取指</p><p>现代CPU中,引入了Pipeline与超标量执行(一个cycle可以执行多个指令)与乱序执行, 这里可以看<a href="https://www.youtube.com/watch?v=QAzuAn3nFGo">The 80’s Algorithm to Avoid Race Conditions (and Why It Failed)</a>中15:45的一个片段</p><p><video controls="controls" src="assets/PixPin_2026-03-17_12-33-50-20260317123422-lcenbeu.mp4"></video></p><h3 id="什么是内存一致性"><a class="markdownIt-Anchor" href="#什么是内存一致性"></a> 什么是内存一致性</h3><blockquote><p>内存一致性（或访存一致性）是<strong>架构或编程语言层面</strong>的协议</p><p>它定义了在一个线程中进行的多次内存访问（读/写）操作，在什么时间、以什么顺序对其他线程可见</p></blockquote><p>内存一致性的基础是<strong>修改顺序.</strong></p><p>修改顺序, 就是程序中所有线程对一个对象的写入操作的列表</p><p>内存一致性要求:</p><ul><li><strong>一致性</strong>: 虽然修改顺序在不同运行中可能变化，但在单次执行中，<strong>所有线程必须对每个独立变量的修改顺序达成一致</strong></li><li><strong>可见性约束</strong>: 一旦某个线程看到过修改顺序中的某个值，它随后的读取操作必须返回该值或更靠后的值；随后的写入操作必须排在修改顺序的更后面</li></ul><h3 id="什么是内存模型"><a class="markdownIt-Anchor" href="#什么是内存模型"></a> 什么是内存模型</h3><p>内存模型（Memory Model）是多线程编程的<strong>底层契约</strong></p><p>规定了<strong>编译器和硬件在处理内存访问时必须遵守的规则</strong>，以确保内存一致性</p><p>在逻辑层面, 有以下几个概念:</p><ul><li>顺序一致</li><li>Hapens-before</li><li>Synchronizes-with</li></ul><h4 id="顺序一致"><a class="markdownIt-Anchor" href="#顺序一致"></a> 顺序一致</h4><p><strong>硬件架构</strong>根据其对访存指令重排的容忍程度，主要分为以下几种模型：</p><ul><li><strong>弱内存模型 (Weak Memory Model)</strong> ：以 <strong>DEC Alpha</strong> 为代表。这是最宽松的模型，可能经历所有四种访存乱序（LoadLoad, LoadStore, StoreLoad, StoreStore），只要不改变单线程行为，任何 Load/Store 都能重排</li><li><strong>带数据依赖序的弱模型 (Weak With Data Dependency Ordering)</strong> ：以 <strong>ARM、PowerPC 和 Itanium</strong> 为代表。在弱序基础上增加了约束，能保证具有数据依赖的操作（如 C++ 中的 <code>A-&gt;B</code>）不会被乱序，加载 B 时必定已经加载了最新的 A</li><li><strong>强内存模型 (Strong Memory Model)</strong> ：以 <strong>X86/X64</strong> 架构为代表。它能够保证绝大多数指令的获取和释放（Acquire and Release）语义，本质上使用了 LoadLoad、LoadStore 和 StoreStore 三种内存屏障，<strong>仅保留了 StoreLoad 的重排可能性</strong></li><li><strong>顺序一致性 (Sequential Consistency, SC)</strong> ：这是最强且最理想的模型，没有任何乱序存在。现代主流硬件体系结构（如 Intel 或 ARM 芯片）基本<strong>不直接支持 SC</strong>，因为它会严重限制 CPU 的执行效率（如寄存器、缓存和流水线的优化使用）</li></ul><p><strong>语言层面</strong>, 对底层硬件一致性模型（如 TSO、ARM 的弱序模型等）的<strong>高级抽象</strong>则有:</p><ul><li><strong>SC-DRF (Sequential Consistency for Data Race Free)</strong> : 只要程序员在编写代码时通过同步手段（如锁、原子变量）消除了<strong>数据竞争（Data Race Free）</strong> ，编译器和硬件就必须共同保证程序的执行结果看起来就像在理想的<strong>顺序一致性（SC）</strong> 模型下一样</li><li><strong>CSP 与消息传递 (Go, Erlang)</strong> ：这类模型认为共享内存是复杂性的根源。<strong>Erlang</strong> 的 Actor 模型通过进程隔离确保了鲁棒性；<strong>Go</strong> 则通过通道将同步与数据传输结合，简化了并发逻辑</li><li><strong>不可变模型 (Haskell)</strong> ：由于函数不产生副效应，多个线程可以安全地同时读取任何数据，彻底消除了<strong>修改顺序 (Modification Orders)</strong>  带来的冲突</li></ul><h4 id="hapens-before"><a class="markdownIt-Anchor" href="#hapens-before"></a> Hapens-before</h4><p>按代码顺序，若语句A在语句B的前面，则语句A <code>Happens Before</code> 语句B</p><p>值得注意的是, <code>Hapens-before</code> 并不意味着 A 一定会在 B之前执行(可能被重排), 比如下面这段代码:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;thread&gt;int x &#x3D; 0;int y &#x3D; 0;int r1 &#x3D; 0;int r2 &#x3D; 0;void thread1() &#123;    x &#x3D; 1;          &#x2F;&#x2F; Store X    &#x2F;&#x2F; --- 编译器或 CPU 可能在这里插入重排 ---    r1 &#x3D; y;         &#x2F;&#x2F; Load Y&#125;void thread2() &#123;    y &#x3D; 1;          &#x2F;&#x2F; Store Y    &#x2F;&#x2F; --- 编译器或 CPU 可能在这里插入重排 ---    r2 &#x3D; x;         &#x2F;&#x2F; Load X&#125;int main() &#123;    int iterations &#x3D; 0;    int detected_count &#x3D; 0;    std::cout &lt;&lt; &quot;Starting Memory Consistency Experiment...&quot; &lt;&lt; std::endl;    &#x2F;&#x2F; 运行多次实验以捕捉“不可能”的瞬间    while (true) &#123;        iterations++;        x &#x3D; 0; y &#x3D; 0; r1 &#x3D; 0; r2 &#x3D; 0;        &#x2F;&#x2F; 使用两个线程同时运行        std::thread t1(thread1);        std::thread t2(thread2);        t1.join();        t2.join();        &#x2F;&#x2F; 检查是否发生了违反“顺序一致性”的情况        &#x2F;&#x2F; 理论上，如果 x&#x3D;1 发生在 r2&#x3D;x 之前，或者 y&#x3D;1 发生在 r1&#x3D;y 之前，        &#x2F;&#x2F; 那么 r1 和 r2 就不可能同时为 0。        if (r1 &#x3D;&#x3D; 0 &amp;&amp; r2 &#x3D;&#x3D; 0) &#123;            detected_count++;            std::cout &lt;&lt; &quot;Iteration &quot; &lt;&lt; iterations                       &lt;&lt; &quot;: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!&quot; &lt;&lt; std::endl;                        if (detected_count &gt;&#x3D; 5) break; &#x2F;&#x2F; 抓到 5 次就停止        &#125;        if (iterations &gt; 1000000) &#123;            std::cout &lt;&lt; &quot;Executed 1,000,000 times, no violation detected (Luck or strong hardware).&quot; &lt;&lt; std::endl;            break;        &#125;    &#125;    return 0;&#125;&#x2F;&#x2F;&#x2F; 运行结果Starting Memory Consistency Experiment...Iteration 144387: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!Iteration 261936: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!Iteration 265633: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!Iteration 319644: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!Iteration 415395: [CONSISTENCY VIOLATION] r1&#x3D;0, r2&#x3D;0 detected!<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="synchronizes-with"><a class="markdownIt-Anchor" href="#synchronizes-with"></a> Synchronizes-with</h4><p>线程间的同步, 我们可以使用原子变量等.</p><p>如果线程1对原子变量x进行写操作，线程2对原子变量x进行读操作且读到了线程1的写结果，则线程1的写操作<code>Synchronizes-with</code>线程2的读操作</p><p>比如下面这段代码:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;vector&gt;#include &lt;atomic&gt;#include &lt;iostream&gt;std::vector&lt;int&gt; data; &#x2F;&#x2F; 非原子变量std::atomic&lt;bool&gt; data_ready(false); &#x2F;&#x2F; 原子同步变量&#x2F;&#x2F; 线程 1：生产者&#x2F;写入者void writer_thread() &#123;    data.push_back(42);           &#x2F;&#x2F; (a) 修改非原子数据 [1]    data_ready.store(true);       &#x2F;&#x2F; (b) 对原子变量进行写操作 [1]&#125;&#x2F;&#x2F; 线程 2：消费者&#x2F;读取者void reader_thread() &#123;    while(!data_ready.load());    &#x2F;&#x2F; (c) 对原子变量进行读操作，直到读到 true [1]    std::cout &lt;&lt; data &lt;&lt; &quot;\n&quot;; &#x2F;&#x2F; (d) 读取非原子数据 [1]&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中,  <strong>(b) 同步于 ©</strong></p><p>​<code>Synchronizes-with</code>​可以让我们推导出跨线程的<code>Happens Before</code>关系</p><p>上例中:</p><ul><li>在线程 1 内部，根据代码顺序，<code>(a)</code>​ <strong>Happens-before</strong> <code>(b)</code></li><li>由于<code> (b)</code>​ 与 <code>(c)</code>​ 建立了 <strong>Synchronizes-with</strong> 关系，因此<code>(b)</code>​<strong>Inter-thread happens-before</strong> <code>(c)</code></li><li>在线程 2 内部，<code>(c)</code>​ <strong>Happens-before</strong> <code>(d)</code></li><li><strong>最终结论</strong>：根据传递性，<code>(a)</code>​ <strong>Happens-before</strong> <code>(d)</code>​。这意味着当线程 2 看到 <code>data_ready</code>​ 变为 <code>true</code>​ 后，它<strong>绝对能看到</strong>线程 1 对 <code>data</code> 向量所做的修改（即能正确读到 42），从而避免了数据竞争和未定义行为</li></ul><h2 id="临界区critical-section"><a class="markdownIt-Anchor" href="#临界区critical-section"></a> 临界区（Critical Section）</h2><p>多线程编程，<strong>临界区</strong>是一个很重要的概念</p><p>临界区是一段访问共享资源（如全局变量、数据结构或文件）的代码，为了保证程序的正确性，同一时刻只能有一个线程执行该段代码</p><p>比如下面这段代码</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;list&gt;#include &lt;mutex&gt;#include &lt;algorithm&gt;std::list&lt;int&gt; some_list;    &#x2F;&#x2F; 共享资源std::mutex some_mutex;       &#x2F;&#x2F; 保护资源的互斥锁void add_to_list(int new_value) &#123;    &#x2F;&#x2F; --- 临界区开始 ---    std::lock_guard&lt;std::mutex&gt; guard(some_mutex);     some_list.push_back(new_value);     &#x2F;&#x2F; --- 临界区结束 --- [6]&#125;bool list_contains(int value_to_find) &#123;    &#x2F;&#x2F; --- 临界区开始 ---    std::lock_guard&lt;std::mutex&gt; guard(some_mutex);     return std::find(some_list.begin(), some_list.end(), value_to_find) !&#x3D; some_list.end();    &#x2F;&#x2F; --- 临界区结束 --- [6]&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>逻辑视角：单向屏障</strong></p><p>从编译器和硬件优化的角度看，临界区的边界具有特殊的语义：</p><ul><li><strong>获取操作 (Lock/Acquire)</strong> ：是一个“向下”的单向屏障。代码可以从锁外面移进临界区，但临界区内的指令不能移到锁之前</li><li><strong>释放操作 (Unlock/Release)</strong> ：是一个“向上”的单向屏障。代码可以移入临界区，但临界区内的指令不能重排到解锁之后</li></ul><p>比如下面这段伪代码:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">&#x2F;&#x2F; --- 原始代码逻辑 ---local_A &#x3D; 100;          &#x2F;&#x2F; (1) 外部指令：在锁之前mutex.lock();           &#x2F;&#x2F; --- ACQUIRE 屏障 (向下单向) ---shared_data +&#x3D; 1;       &#x2F;&#x2F; (2) 临界区内指令mutex.unlock();         &#x2F;&#x2F; --- RELEASE 屏障 (向上单向) ---local_B &#x3D; 200;          &#x2F;&#x2F; (3) 外部指令：在锁之后&#x2F;&#x2F; --- 编译器&#x2F;硬件优化后的合法执行序列 ---mutex.lock();           &#x2F;&#x2F; 获取锁local_A &#x3D; 100;          &#x2F;&#x2F; 指令(1) 被“拉入”了临界区 (向下移动)shared_data +&#x3D; 1;       &#x2F;&#x2F; 指令(2) 保持在原位local_B &#x3D; 200;          &#x2F;&#x2F; 指令(3) 被“拉入”了临界区 (向上移动)mutex.unlock();         &#x2F;&#x2F; 释放锁<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>简单来说, lock和unlock可以看作两个单方向的屏障，lock对应的屏障，只允许代码往下方向移动，而unlock则只允许上方向移动</p><p>c++的内存顺序借鉴lock/unlock，引入了两个等效的概念，Acquire（类似lock）和Release（类似unlock），这两个都是<strong>单向屏障 (One-way Barriers)</strong></p><p>这两个单向屏障协同工作，建立了 <strong>Synchronizes-with（同步）</strong>  关系, 比如：</p><ul><li>如果线程 A 执行了一个 <strong>Release</strong> 存储操作，而线程 B 执行了一个 <strong>Acquire</strong> 加载操作，并读到了线程 A 写入的值，那么 A 与 B 之间就建立了同步</li><li>由于 <strong>Release</strong> 保证了之前的修改不会“掉”到屏障之后，而 <strong>Acquire</strong> 保证了之后的读取不会“跑到”屏障之前，这种成对的关系确保了线程 A 在释放前所做的所有内存修改，在线程 B 获取后都是绝对可见的</li></ul><p>像下面这段代码:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;atomic&gt;#include &lt;thread&gt;#include &lt;assert.h&gt;int x &#x3D; 0;                         &#x2F;&#x2F; 非原子变量（共享数据）std::atomic&lt;bool&gt; flag(false);      &#x2F;&#x2F; 原子变量（同步标志）&#x2F;&#x2F; 线程 A：执行写入与“释放”操作void thread_A() &#123;    x &#x3D; 42;                         &#x2F;&#x2F; (1) 在屏障之前的内存修改    &#x2F;&#x2F; RELEASE 屏障：确保 (1) 不会重排到 (2) 之后    flag.store(true, std::memory_order_release); &#x2F;&#x2F; (2) 原子存储-释放&#125;&#x2F;&#x2F; 线程 B：执行“获取”与读取操作void thread_B() &#123;    &#x2F;&#x2F; ACQUIRE 屏障：确保 (4) 不会重排到 (3) 之前    &#x2F;&#x2F; 循环直到读到线程 A 写入的 true    while (!flag.load(std::memory_order_acquire)); &#x2F;&#x2F; (3) 原子加载-获取        &#x2F;&#x2F; 一旦 (3) 读到了 (2) 写入的值，同步关系建立    assert(x &#x3D;&#x3D; 42);                &#x2F;&#x2F; (4) 在屏障之后的内存读取&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>当线程 B 的加载操作 (3) 读到了线程 A 的存储操作 (2) 写入的值时，这两个操作之间就建立了 <strong>Synchronizes-with</strong> 关系</li><li>由于<strong>Release 屏障（向上单向）</strong> 和 <strong>Acquire 屏障（向下单向）</strong> 保证的<code>Happens-before</code>​关系, 我们可以知道指令执行顺序为:  <strong>(1) → (2) → (3) → (4)</strong></li></ul><p>事实上, 这里用到的<code>memory_order_release</code>​和<code>memory_order_acquire</code>，其实是比SC-DRF更为宽松的模型(Acquire-Release 模型)</p><p>C++默认的SC-DRF对应为<code>memory_order_seq_cst</code>，使用该默认模型的操作，上移下移都不被允许，相当于双向屏障</p><p>比如:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;atomic&gt;std::atomic&lt;bool&gt; sync(false);int a &#x3D; 0, b &#x3D; 0;&#x2F;&#x2F; 线程 Avoid thread_A() &#123;    a &#x3D; 1;                                 &#x2F;&#x2F; (1) 普通写操作    &#x2F;&#x2F; --- SEQ_CST 双向屏障（不可逾越的墙） ---    sync.store(true, std::memory_order_seq_cst); &#x2F;&#x2F; (2) 原子存储    &#x2F;&#x2F; ------------------------------------    b &#x3D; 2;                                 &#x2F;&#x2F; (3) 普通写操作&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这种模型下，编译器和 CPU 被施加了最严格的约束：</p><ul><li><strong>禁止向上移动</strong>：指令 3，<strong>不能</strong>被重排到该原子操作之前</li><li><strong>禁止向下移动</strong>：指令 1，<strong>不能</strong>被重排到该原子操作之后</li></ul><p><strong>结果</strong>：对于任何观察者线程，指令的执行序列在逻辑上被锁定为  <strong>(1)</strong>  →  <strong>(2)</strong>  →  <strong>(3)</strong></p><h2 id="c-的六种内存序"><a class="markdownIt-Anchor" href="#c-的六种内存序"></a> C++ 的六种内存序</h2><p>现代C++的内存模型，主要通过原子操作（<code>std::atomic</code>​）中的<strong>内存序参数（<strong>​</strong>​<code>Memory Order</code>​</strong>​ <strong>)</strong> 来约束编译器重排和CPU乱序执行，从而确保跨线程的可见性</p><p>C++将六种内存序归纳为三类强度不同的逻辑模型：</p><ul><li><p><strong>顺序一致模型（Sequential Consistency）</strong> ：</p><ul><li>​<code>memory_order_seq_cst</code>。</li><li>要求所有线程看到的执行顺序就像在一个全局唯一的序列中，且操作不能跨越此边界重排。它是默认且最强的模型，满足 <strong>SC-DRF</strong> 契约</li></ul></li><li><p>​<strong>获取-发布模型（Acquire-Release Ordering）</strong> ：</p><ul><li>​<code>memory_order_consume</code>​, <code>memory_order_acquire</code>​, <code>memory_order_release</code>​, <code>memory_order_acq_rel</code>。</li><li>建立线程间的配对同步。它通过“单向屏障”工作：<strong>Release</strong> 阻止指令上移（向上屏障），<strong>Acquire</strong> 阻止指令下移（向下屏障）</li></ul></li><li><p>​<strong>松散模型（Relaxed Ordering）</strong> ：</p><ul><li>​<code>memory_order_relaxed</code>。</li><li>仅保证操作的原子性，不提供任何跨变量的同步或可见性保证</li></ul></li></ul><h3 id="六种内存序枚举值"><a class="markdownIt-Anchor" href="#六种内存序枚举值"></a> 六种内存序枚举值</h3><table><thead><tr><th>枚举值</th><th>核心意义与重排限制</th></tr></thead><tbody><tr><td><strong>relaxed</strong></td><td><strong>最弱保证</strong><br />不对其他读写操作有同步或重排限制，仅保证操作本身是原子的，且所有线程对该单一变量的修改顺序达成一致<br /></td></tr><tr><td><strong>consume</strong></td><td><strong>细粒度优化版 Acquire</strong><br />它是为了优化性能，仅针对具有<strong>数据依赖关系（Data Dependency）</strong> 的写操作结果可见<br />例如加载一个指针，仅保证该指针指向的内容可见，而不保证其他无关变量可见<br /></td></tr><tr><td><strong>acquire</strong></td><td><strong>向下单向屏障（Load操作）</strong> <br />严禁此操作之后的指令重排到其前面<br /></td></tr><tr><td><strong>release</strong></td><td><strong>向上单向屏障（Store操作）</strong> <br />严禁此操作之前的指令重排到其后面<br /></td></tr><tr><td><strong>acq_rel</strong></td><td><strong>双向语义（RMW操作）</strong> <br />它在逻辑上兼具获取（Acquire）和释放（Release）的功能：后续指令不能上移，前序指令不能下移<br />它作为同步链的中间环节，既同步于前序的<code>release</code>​，又同步于后续的<code>acquire</code><br /></td></tr><tr><td><strong>seq_cst</strong></td><td><strong>双向强力屏障</strong><br />除了具备 Acquire/Release 语义外，它还保证了<strong>全局全序</strong><br />所有线程观察到的<code>eq_cst</code>操作顺序必须完全一致<br /></td></tr></tbody></table><p>其中, 不是所有枚举值都适用于所有原子操作</p><p>适配关系如下:</p><table><thead><tr><th>操作类型</th><th>有效的 Memory Order 枚举值</th><th>逻辑归属</th></tr></thead><tbody><tr><td><strong>Load</strong>(如<code>load()</code>)</td><td>​<code>relaxed</code>​,<code>consume</code>​,<code>acquire</code>​,<code>seq_cst</code></td><td>获取语义</td></tr><tr><td><strong>Store</strong>(如<code>store()</code>​,<code>clear()</code>)</td><td>​<code>relaxed</code>​,<code>release</code>​,<code>seq_cst</code></td><td>释放语义</td></tr><tr><td><strong>RMW</strong>(如<code>fetch_xxx()</code>​,<code>exchange()</code>)</td><td><strong>全部 6 种</strong>均可</td><td>获取 + 释放语义</td></tr></tbody></table><h3 id="内存模型的使用"><a class="markdownIt-Anchor" href="#内存模型的使用"></a> 内存模型的使用</h3><h4 id="松散模型relaxed-ordering"><a class="markdownIt-Anchor" href="#松散模型relaxed-ordering"></a> 松散模型（Relaxed Ordering）</h4><p>可以查看<code>mo_relaxed</code>示例</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;atomic&gt;#include &lt;thread&gt;std::atomic&lt;int&gt; data&#123;0&#125;;std::atomic&lt;bool&gt; ready&#123;false&#125;;std::atomic&lt;int&gt; counter&#123;0&#125;;void producer() &#123;    &#x2F;&#x2F; 1. 验证原子性：每个线程加 1000 次    for (int i &#x3D; 0; i &lt; 1000; ++i) &#123;        counter.fetch_add(1, std::memory_order_relaxed);    &#125;    &#x2F;&#x2F; 2. 验证可见性顺序：    &#x2F;&#x2F; 在 relaxed 模式下，下面两行可能会被编译器或 CPU 重排    data.store(42, std::memory_order_relaxed);     ready.store(true, std::memory_order_relaxed); &#125;void consumer() &#123;    &#x2F;&#x2F; 等待标志位    while (!ready.load(std::memory_order_relaxed));    &#x2F;&#x2F; 虽然 ready 已经是 true 了，但由于不保证可见性顺序，    &#x2F;&#x2F; 在某些硬件架构（如 ARM）或极其激进的编译器优化下，    &#x2F;&#x2F; 这里读到的 data 可能是 0 而不是 42。    if (data.load(std::memory_order_relaxed) &#x3D;&#x3D; 0) &#123;        std::cout &lt;&lt; &quot;[Relaxed] GLITCH: Saw ready&#x3D;true but data&#x3D;0! (Visibility Order Violation)&quot; &lt;&lt; std::endl;    &#125;&#125;int main() &#123;    std::cout &lt;&lt; &quot;Starting Relaxed test...&quot; &lt;&lt; std::endl;    &#x2F;&#x2F; 运行多次实验    for (int i &#x3D; 0; i &lt; 100; ++i) &#123;        data &#x3D; 0;        ready &#x3D; false;        counter &#x3D; 0;        std::thread t1(producer);        std::thread t2(consumer);        t1.join();        t2.join();        &#x2F;&#x2F; 验证原子性        if (counter.load() !&#x3D; 1000) &#123;            std::cout &lt;&lt; &quot;[Relaxed] Atomicity Error! Counter: &quot; &lt;&lt; counter.load() &lt;&lt; std::endl;        &#125;    &#125;    std::cout &lt;&lt; &quot;Test finished. (On x86, glitches are rare due to TSO hardware, but allowed by C++ standard)&quot; &lt;&lt; std::endl;        return 0;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><h4 id="顺序一致模型sequential-consistency"><a class="markdownIt-Anchor" href="#顺序一致模型sequential-consistency"></a> 顺序一致模型（Sequential Consistency）</h4><p>可以查看<code>mo_seq_cst</code>示例</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;atomic&gt;#include &lt;thread&gt;std::atomic&lt;int&gt; x&#123;0&#125;;std::atomic&lt;int&gt; y&#123;0&#125;;int r1 &#x3D; 0;int r2 &#x3D; 0;void thread1() &#123;    x.store(1, std::memory_order_seq_cst); &#x2F;&#x2F; Store X    r1 &#x3D; y.load(std::memory_order_seq_cst); &#x2F;&#x2F; Load Y&#125;void thread2() &#123;    y.store(1, std::memory_order_seq_cst); &#x2F;&#x2F; Store Y    r2 &#x3D; x.load(std::memory_order_seq_cst); &#x2F;&#x2F; Load X&#125;int main() &#123;    int iterations &#x3D; 0;    int violation_count &#x3D; 0;    std::cout &lt;&lt; &quot;[Seq_Cst] Starting experiment. This will run 500,000 times...&quot; &lt;&lt; std::endl;    for (int i &#x3D; 0; i &lt; 500000; ++i) &#123;        x &#x3D; 0; y &#x3D; 0; r1 &#x3D; 0; r2 &#x3D; 0;        std::thread t1(thread1);        std::thread t2(thread2);        t1.join();        t2.join();        &#x2F;&#x2F; 在 seq_cst 下，r1&#x3D;0 且 r2&#x3D;0 是绝对不可能发生的        if (r1 &#x3D;&#x3D; 0 &amp;&amp; r2 &#x3D;&#x3D; 0) &#123;            violation_count++;        &#125;    &#125;    std::cout &lt;&lt; &quot;[Seq_Cst] Completed 500,000 iterations.&quot; &lt;&lt; std::endl;    std::cout &lt;&lt; &quot;[Seq_Cst] Violations (r1&#x3D;0, r2&#x3D;0) detected: &quot; &lt;&lt; violation_count &lt;&lt; std::endl;    return 0;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="获取-发布模型acquire-release-ordering"><a class="markdownIt-Anchor" href="#获取-发布模型acquire-release-ordering"></a> 获取-发布模型（Acquire-Release Ordering）</h4><p>可以查看<code>mo_acq_rel</code>示例</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;atomic&gt;#include &lt;thread&gt;#include &lt;string&gt;std::atomic&lt;bool&gt; ready&#123;false&#125;;std::string data;void producer() &#123;    data &#x3D; &quot;Payload delivered safely!&quot;; &#x2F;&#x2F; (1) 写普通数据    &#x2F;&#x2F; release: 严禁 (1) 被重排到 store 之后    &#x2F;&#x2F; 保证之前的写入对 acquire 它的线程可见    ready.store(true, std::memory_order_release);    std::cout &lt;&lt; &quot;[Producer] Flag set to ready.&quot; &lt;&lt; std::endl;&#125;void consumer() &#123;    &#x2F;&#x2F; acquire: 严禁下面的 (2) 被重排到 load 之前    while (!ready.load(std::memory_order_acquire));        &#x2F;&#x2F; (2) 读普通数据    std::cout &lt;&lt; &quot;[Consumer] Data observed: &quot; &lt;&lt; data &lt;&lt; std::endl;&#125;int main() &#123;    std::thread t1(producer);    std::thread t2(consumer);    t1.join();    t2.join();    return 0;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="memory_order_consume"><a class="markdownIt-Anchor" href="#memory_order_consume"></a> <code>memory_order_consume</code></h4><ul><li><p>是 <code>memory_order_acquire</code>​ 的一个特例，专门用于处理<strong>数据依赖性（Data Dependency）</strong></p></li><li><p>限制同步仅发生在该原子变量指向的<strong>直接依赖数据</strong>上<br />如果线程 A 使用 <code>release</code>​ 发布一个指针，线程 B 使用 <code>consume</code> 加载该指针，则只有通过该指针访问的数据能保证一致性，而指针之外的无关变量不保证同步</p></li><li><p>主要用于<strong>读取很少更改的指针（如 RCU(Read-Copy-Update) 模式）</strong><br />它可以避免在某些弱序硬件（如 ARM）上生成不必要的内存屏障指令，从而提升性能</p></li><li><p><strong>对于不相关的代码（即没有数据依赖的代码），<strong>​</strong>​<code>memory_order_consume</code>​</strong>​<strong>不提供排序约束，允许上下重排</strong></p></li></ul><p>你可以查看<code>mo_consume</code>示例:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;atomic&gt;#include &lt;thread&gt;#include &lt;string&gt;struct Config &#123;    int update_id;    std::string name;&#125;;std::atomic&lt;Config*&gt; g_config_ptr&#123;nullptr&#125;;int g_unrelated_global &#x3D; 0;void producer() &#123;    &#x2F;&#x2F; 准备数据    Config* new_cfg &#x3D; new Config&#123;1, &quot;RCU_Config_V1&quot;&#125;;        &#x2F;&#x2F; (A) 修改一个完全不相关的变量    g_unrelated_global &#x3D; 100;     &#x2F;&#x2F; (B) 使用 release 发布指针    &#x2F;&#x2F; release 确保 (A) 和 new_cfg 的初始化在 store 之前完成    g_config_ptr.store(new_cfg, std::memory_order_release);        std::cout &lt;&lt; &quot;[Producer] Config published.&quot; &lt;&lt; std::endl;&#125;void consumer() &#123;    Config* cfg;    &#x2F;&#x2F; (C) 使用 consume 加载指针    while (!(cfg &#x3D; g_config_ptr.load(std::memory_order_consume)));    &#x2F;&#x2F; 下面两行代码通过 cfg 指针访问成员，与 cfg 有“数据依赖”。    &#x2F;&#x2F; C++ 标准保证：通过 consume 同步后的指针访问其成员，一定能看到最新值。    std::cout &lt;&lt; &quot;[Consumer] Dependent Data: ID&#x3D;&quot; &lt;&lt; cfg-&gt;update_id               &lt;&lt; &quot;, Name&#x3D;&quot; &lt;&lt; cfg-&gt;name &lt;&lt; std::endl;    &#x2F;&#x2F; g_unrelated_global 与 cfg 指针没有任何数据依赖。    &#x2F;&#x2F; 虽然生产者先写了它，但由于这里使用的是 consume 而非 acquire，    &#x2F;&#x2F; 在底层语义上，并不保证这里一定能看到 100。    &#x2F;&#x2F; 在 ARM 架构上，这里的读取可能被重排到 (C) 之前，读到旧值 0。    std::cout &lt;&lt; &quot;[Consumer] Unrelated Data (No Dependency): &quot; &lt;&lt; g_unrelated_global &lt;&lt; std::endl;    &#x2F;&#x2F; 注意：在 x86-64 (TSO) 或 MSVC&#x2F;GCC 下，consume 通常会被自动提升为 acquire，    &#x2F;&#x2F; 且硬件本身保证了 Load-Load 顺序，因此你很难在 X86 PC 上观察到 g_unrelated_global 为 0。&#125;int main() &#123;    std::thread t1(producer);    std::thread t2(consumer);    t1.join();    t2.join();    return 0;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="memory_order_acq_rel"><a class="markdownIt-Anchor" href="#memory_order_acq_rel"></a> <code>memory_order_acq_rel</code></h4><ul><li><p>它在逻辑上兼具获取（Acquire）和释放（Release）的双重功能</p></li><li><p>它将当前操作标记为既是一个同步点的“终点”（获取前序的发布效果），也是下一个同步点的“起点”（为后续获取操作发布当前修改）</p></li><li><p>它主要用于 <strong>RMW 操作</strong>。例如，在一个同步链条中，中间线程对原子变量执行 RMW 操作并使用 <code>acq_rel</code>，可以确保第一线程的修改能通过它“传递”给第三线程</p></li></ul><p>你可以查看<code>mo_acq_rel_rmw</code>示例:</p><pre class="line-numbers language-C++" data-language="C++"><code class="language-C++">#include &lt;iostream&gt;#include &lt;atomic&gt;#include &lt;thread&gt;#include &lt;vector&gt;std::atomic&lt;int&gt; turn&#123;0&#125;;int shared_data &#x3D; 0;void worker(int id) &#123;    &#x2F;&#x2F; 线程通过 acq_rel 操作参与接力    &#x2F;&#x2F; 逻辑：读 turn (获取同步)，加 1，写 turn (发布同步)    while (turn.fetch_add(0, std::memory_order_acquire) !&#x3D; id);    &#x2F;&#x2F; 此时已经获得“同步”    shared_data +&#x3D; id;    std::cout &lt;&lt; &quot;[Worker &quot; &lt;&lt; id &lt;&lt; &quot;] Accessing shared_data. Current: &quot; &lt;&lt; shared_data &lt;&lt; std::endl;    &#x2F;&#x2F; 移交接力棒给下一个 ID    turn.store(id + 1, std::memory_order_release);&#125;int main() &#123;    std::vector&lt;std::thread&gt; threads;    for (int i &#x3D; 0; i &lt; 5; ++i) &#123;        threads.emplace_back(worker, i);    &#125;    for (auto&amp; t : threads) t.join();    std::cout &lt;&lt; &quot;Final shared_data: &quot; &lt;&lt; shared_data &lt;&lt; std::endl;    return 0;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><h1 id="reference"><a class="markdownIt-Anchor" href="#reference"></a> Reference</h1><blockquote><p><a href="https://www.bilibili.com/video/BV1iA4m137Xr/?spm_id_from=333.337.search-card.all.click&amp;vd_source=e83b8f8e3c1120e91a8d3bbe191efb81">【技术杂谈】缓存一致性</a></p><p><a href="https://luzhixing12345.github.io/klinux/articles/arch/cpu/">https://luzhixing12345.github.io/klinux/articles/arch/cpu/</a></p><p><a href="https://luzhixing12345.github.io/klinux/articles/arch/cache/">https://luzhixing12345.github.io/klinux/articles/arch/cache/</a></p><p><a href="https://luzhixing12345.github.io/klinux/articles/arch/multicore/">https://luzhixing12345.github.io/klinux/articles/arch/multicore/</a></p><p><a href="https://luzhixing12345.github.io/klinux/articles/arch/cache-coherence/">https://luzhixing12345.github.io/klinux/articles/arch/cache-coherence/</a></p><p><a href="https://luzhixing12345.github.io/klinux/articles/arch/memory-coherence/">https://luzhixing12345.github.io/klinux/articles/arch/memory-coherence/</a></p><p><a href="https://blinkfox.com/2018/11/18/ruan-jian-gong-ju/cpu-duo-ji-huan-cun/">https://blinkfox.com/2018/11/18/ruan-jian-gong-ju/cpu-duo-ji-huan-cun/</a></p><p><a href="https://www.cnblogs.com/yanlong300/p/8986041.html">https://www.cnblogs.com/yanlong300/p/8986041.html</a></p><p><a href="https://herbsutter.com/2012/08/02/strong-and-weak-hardware-memory-models/">https://herbsutter.com/2012/08/02/strong-and-weak-hardware-memory-models/</a></p><p><a href="https://en.cppreference.com/w/cpp/atomic/memory_order.html">https://en.cppreference.com/w/cpp/atomic/memory_order.html</a></p><p><a href="https://www.ramtintjb.com/blog/memory-ordering">https://www.ramtintjb.com/blog/memory-ordering</a></p><p><a href="https://arxiv.org/pdf/1803.04432">https://arxiv.org/pdf/1803.04432</a></p><p><a href="https://paul.pub/cpp-memory-model/#:~:text=seq%2Dcst%20%E6%A8%A1%E5%9E%8B%20%E5%BD%93%E4%BD%BF%E7%94%A8%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C%E8%80%8C%E5%8F%88%E4%B8%8D%E6%8C%87%E5%AE%9A%20memory_order%20%E6%97%B6%E5%B0%86%E4%BD%BF%E7%94%A8%E9%BB%98%E8%AE%A4%E7%9A%84%E5%86%85%E5%AD%98%E9%A1%BA%E5%BA%8F%EF%BC%9A%20memory_order_seq_cst%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B0%83%E7%94%A8%E8%BF%99%E4%BA%9B%E5%87%BD%E6%95%B0%E6%97%B6%E6%8C%87%E5%AE%9A%E6%88%96%E8%80%85%E4%B8%8D%E6%8C%87%E5%AE%9A%E8%BF%99%E4%B8%AA%E5%80%BC%E6%95%88%E6%9E%9C%E6%98%AF%E4%B8%80%E6%A0%B7%E7%9A%84%E3%80%82%20%E8%BF%99%E6%98%AF%E6%9C%80%E4%B8%A5%E6%A0%BC%E7%9A%84%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%EF%BC%8Cseq%2Dcst%20%E6%9C%89%E4%B8%A4%E4%B8%AA%E4%BF%9D%E8%AF%81%EF%BC%9A%20%E7%A8%8B%E5%BA%8F%E6%8C%87%E4%BB%A4%E4%B8%8E%E6%BA%90%E7%A0%81%E9%A1%BA%E5%BA%8F%E4%B8%80%E8%87%B4%20%E6%89%80%E6%9C%89%E7%BA%BF%E7%A8%8B%E7%9A%84%E6%89%80%E6%9C%89%E6%93%8D%E4%BD%9C%E5%AD%98%E5%9C%A8%E4%B8%80%E4%B8%AA%E5%85%A8%E5%B1%80%E7%9A%84%E9%A1%BA%E5%BA%8F">c++内存模型</a></p><p>[图解Modern Cpp内存序](<a href="https://www.arong-xu.com/modern-cpp/concurrency/modern-cpp-memory-order-explained/#:~:text=C++%20%E5%86%85%E5%AD%98%E5%BA%8F(Memory%20Order,%E5%AE%89%E5%85%A8%E4%B9%8B%E9%97%B4%E8%BF%9B%E8%A1%8C%E6%9D%83%E8%A1%A1.)">https://www.arong-xu.com/modern-cpp/concurrency/modern-cpp-memory-order-explained/#:~:text=C++ 内存序(Memory Order,安全之间进行权衡.)</a></p><p><a href="https://learn.arm.com/learning-paths/servers-and-cloud-computing/arm-cpp-memory-model/1/">https://learn.arm.com/learning-paths/servers-and-cloud-computing/arm-cpp-memory-model/1/</a></p><p><a href="https://wudaijun.com/2019/04/cache-coherence-and-memory-consistency/">https://wudaijun.com/2019/04/cache-coherence-and-memory-consistency/</a></p><p><a href="https://zhuanlan.zhihu.com/p/647681258">https://zhuanlan.zhihu.com/p/647681258</a></p><p><a href="https://www.cnblogs.com/Alfred-HOO/articles/19093800">https://www.cnblogs.com/Alfred-HOO/articles/19093800</a></p><p><a href="https://shili2017.github.io/posts/MCCC1/">https://shili2017.github.io/posts/MCCC1/</a></p><p><a href="https://pages.cs.wisc.edu/~markhill/papers/primer2020_2nd_edition.pdf">https://pages.cs.wisc.edu/~markhill/papers/primer2020_2nd_edition.pdf</a></p><p><a href="https://www.youtube.com/watch?v=QAzuAn3nFGo">The 80’s Algorithm to Avoid Race Conditions (and Why It Failed)</a></p><p><a href="https://www.youtube.com/watch?v=bhpzTWtee2A">The Weirdest Bug in Programming - Race Conditions</a></p><p><a href="https://www.youtube.com/watch?v=EzEKGlO9w4Y&amp;t=740s">https://www.youtube.com/watch?v=EzEKGlO9w4Y&amp;t=740s</a></p><p><a href="https://wudaijun.com/2019/04/cache-coherence-and-memory-consistency/">https://wudaijun.com/2019/04/cache-coherence-and-memory-consistency/</a></p><p><a href="https://zhuanlan.zhihu.com/p/382372072?share_code=19rJ7MIPasNTy&amp;utm_psn=2017183491448653656">https://zhuanlan.zhihu.com/p/382372072?share_code=19rJ7MIPasNTy&amp;utm_psn=2017183491448653656</a></p><p><a href="https://zhuanlan.zhihu.com/p/422848235">https://zhuanlan.zhihu.com/p/422848235</a></p></blockquote><p>‍</p><h1 id=""><a class="markdownIt-Anchor" href="#"></a> </h1><p>‍</p>]]></content>
    
    
    <summary type="html">本文从CPU缓存一致性、内存一致性等硬件基础出发，解析C++三种内存序模型（顺序一致/获取-释放/松散）的工作原理与适用场景，帮助开发者理解底层内存同步机制。</summary>
    
    
    
    <category term="Programming" scheme="http://vanishing.cc/categories/Programming/"/>
    
    <category term="Cpp" scheme="http://vanishing.cc/categories/Programming/Cpp/"/>
    
    
    <category term="Cpp" scheme="http://vanishing.cc/tags/Cpp/"/>
    
    <category term="Memory Model" scheme="http://vanishing.cc/tags/Memory-Model/"/>
    
    <category term="Concurrency" scheme="http://vanishing.cc/tags/Concurrency/"/>
    
  </entry>
  
  <entry>
    <title>在 Unity 中使用 JFA (Jump Flood Algorithm) 快速生成 SDF (Signed Distance Field)</title>
    <link href="http://vanishing.cc/2026/01/08/GameEngine/Unity/%E5%9C%A8%20Unity%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20JFA%20(Jump%20Flood%20Algorithm)%20%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90%20SDF%20(Signed%20Distance%20Field)/"/>
    <id>http://vanishing.cc/2026/01/08/GameEngine/Unity/%E5%9C%A8%20Unity%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20JFA%20(Jump%20Flood%20Algorithm)%20%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90%20SDF%20(Signed%20Distance%20Field)/</id>
    <published>2026-01-07T18:21:00.000Z</published>
    <updated>2026-01-12T11:18:42.365Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文介绍在Unity中使用Jump Flood Algorithm(JFA)高效生成Signed Distance Field(SDF)的方法，通过GPU并行处理种子图的迭代传播，最终通过像素坐标差计算SDF图，显著提升距离场生成效率。</p></blockquote><h1 id="what-is-jfa"><a class="markdownIt-Anchor" href="#what-is-jfa"></a> What is JFA</h1><p>这是一个用于构建 Voronoi 图和 SDF 图的算法.</p><p>其在GPU上效率很高</p><h1 id="algorithm"><a class="markdownIt-Anchor" href="#algorithm"></a> Algorithm</h1><p>首先我们有一张 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>∗</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">N*N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 的<strong>种子图</strong></p><p>比如下图:</p><p><img src="image-20260108022110-7itixin.png" alt="image" title="种子" /></p><p>其中有颜色的地方为种子, 没有的则是’未定义’</p><p>随着算法迭代, 最终整张图的想读都会被’定义’</p><p>‍</p><p>伪代码如下:</p><p>对于每个步长 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>∈</mo><mo stretchy="false">{</mo><mfrac><mi>N</mi><mn>2</mn></mfrac><mo separator="true">,</mo><mfrac><mi>N</mi><mn>4</mn></mfrac><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">k \in \{ \frac{N}{2}, \frac{N}{4}, \dots, 1 \}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.73354em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mopen">{</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord">1</span><span class="mclose">}</span></span></span></span>, 执行一次 JFA</p><pre><code>遍历$(x,y)$处的每一个像素$p$对于每一个在$(x+i,y+j)$处的像素$q$ ( $i, j \in \&#123;-k, 0, k\&#125;$)如果$p$未定义且$q$着色将$p$的颜色更改为$q$的颜色如果$p$着色且$q$着色$p$的颜色使用 `min(dist(p,s),dist(q,s'))`, 其中, $s$ 和 $s'$ 分别是 $p$ 和 $q$ 的种子颜色</code></pre><p><img src="PixPin_2026-01-08_02-53-23-20260108025327-auhf4ph.gif" alt="PixPin_2026-01-08_02-53-23" title="算法示例" /></p><h1 id="jfa-to-sdf"><a class="markdownIt-Anchor" href="#jfa-to-sdf"></a> JFA to SDF</h1><p>我们让JFA种子图中的每个像素存储当前位置的UV坐标，那么最终我们就得到了一张<strong>存储了离该像素最近的“物体像素”的 UV 坐标</strong>的纹理</p><p>那么, 轻易可以得到:<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mi>D</mi><mi>F</mi><mo>=</mo><mi>d</mi><mi>i</mi><mi>s</mi><mi>t</mi><mi>a</mi><mi>n</mi><mi>c</mi><mi>e</mi><mo stretchy="false">(</mo><mtext>当前像素坐标</mtext><mo separator="true">,</mo><mtext>最近物体像素坐标</mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">SDF = distance(当前像素坐标, 最近物体像素坐标)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">i</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">a</span><span class="mord mathnormal">n</span><span class="mord mathnormal">c</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord cjk_fallback">当</span><span class="mord cjk_fallback">前</span><span class="mord cjk_fallback">像</span><span class="mord cjk_fallback">素</span><span class="mord cjk_fallback">坐</span><span class="mord cjk_fallback">标</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord cjk_fallback">最</span><span class="mord cjk_fallback">近</span><span class="mord cjk_fallback">物</span><span class="mord cjk_fallback">体</span><span class="mord cjk_fallback">像</span><span class="mord cjk_fallback">素</span><span class="mord cjk_fallback">坐</span><span class="mord cjk_fallback">标</span><span class="mclose">)</span></span></span></span></p><blockquote><p>当然, 这里没有考虑屏幕比例</p></blockquote><p><img src="image-20260108030913-zmy1t64.png" alt="image" title="最终结果" /></p><h1 id="unity-implementation"><a class="markdownIt-Anchor" href="#unity-implementation"></a> Unity Implementation</h1><p>可以看<a href="https://github.com/Vanishing-Games/Unity-Template">这个仓库</a></p>]]></content>
    
    
    <summary type="html">本文介绍在Unity中使用Jump Flood Algorithm(JFA)高效生成Signed Distance Field(SDF)的方法，通过GPU并行处理种子图的迭代传播，最终通过像素坐标差计算SDF图，显著提升距离场生成效率。</summary>
    
    
    
    <category term="Unity" scheme="http://vanishing.cc/categories/Unity/"/>
    
    
    <category term="Graphics" scheme="http://vanishing.cc/tags/Graphics/"/>
    
    <category term="Unity" scheme="http://vanishing.cc/tags/Unity/"/>
    
    <category term="Shader" scheme="http://vanishing.cc/tags/Shader/"/>
    
  </entry>
  
  <entry>
    <title>Sheen从理论到实践</title>
    <link href="http://vanishing.cc/2025/09/23/Graphics/Rendering/Sheen%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/"/>
    <id>http://vanishing.cc/2025/09/23/Graphics/Rendering/Sheen%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/</id>
    <published>2025-09-23T11:29:16.000Z</published>
    <updated>2026-04-19T10:46:17.962Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文详解glTF光泽(Sheen)材质扩展的实现原理，涵盖光泽颜色/粗糙度的纹理映射与独立控制，重点解析基于Charlie分布的光泽BRDF模型及其微表面理论实现，适用于模拟天鹅绒类材质的背向散射效果。(79字)</p></blockquote><blockquote><p>本文为<a href="https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_sheen/README.md">这篇文章</a>的翻译与整理</p></blockquote><h1 id="光泽sheen"><a class="markdownIt-Anchor" href="#光泽sheen"></a> 光泽（Sheen）</h1><hr /><h2 id="属性"><a class="markdownIt-Anchor" href="#属性"></a> 属性</h2><table><thead><tr><th>类型</th><th>描述</th><th>必需</th></tr></thead><tbody><tr><td><code>sheenColorFactor</code></td><td>线性空间中的光泽颜色。</td><td>否，默认值：[0.0, 0.0, 0.0]</td></tr><tr><td><code>sheenColorTexture</code></td><td>光泽颜色纹理（RGB，sRGB传输函数）。</td><td>否</td></tr><tr><td><code>sheenRoughnessFactor</code></td><td>光泽粗糙度。</td><td>否，默认值：0.0</td></tr><tr><td><code>sheenRoughnessTexture</code></td><td>光泽粗糙度alpha纹理。</td><td>否</td></tr></tbody></table><hr /><h2 id="光泽计算"><a class="markdownIt-Anchor" href="#光泽计算"></a> 光泽计算</h2><h3 id="颜色和粗糙度"><a class="markdownIt-Anchor" href="#颜色和粗糙度"></a> 颜色和粗糙度</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">if</span> <span class="token punctuation">(</span>sheenColorTexture<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    sheenColor <span class="token operator">=</span> sheenColorFactor <span class="token operator">*</span> <span class="token function">sampleLinear</span><span class="token punctuation">(</span>sheenColorTexture<span class="token punctuation">)</span><span class="token punctuation">.</span>rgb<span class="token punctuation">;</span>    sheenRoughness <span class="token operator">=</span> sheenRoughnessFactor <span class="token operator">*</span> <span class="token keyword">sample</span><span class="token punctuation">(</span>sheenRoughnessTexture<span class="token punctuation">)</span><span class="token punctuation">.</span>a<span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    sheenColor <span class="token operator">=</span> sheenColorFactor<span class="token punctuation">;</span>    sheenRoughness <span class="token operator">=</span> sheenRoughnessFactor<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>光泽模拟<strong>天鹅绒类材质的背向散射</strong>。</p></li><li><p>主要沿<strong>表面法线</strong>定向的微纤维产生镜面响应。</p></li><li><p><code>sheenRoughness</code>控制微纤维的发散：</p><ul><li><strong>小</strong>粗糙度 → 锐利的掠射高光</li><li><strong>大</strong>粗糙度 → 平滑的掠射高光</li></ul></li><li><p>粗糙度映射为：</p></li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">r <span class="token operator">=</span> sheenRoughness<span class="token operator">^</span><span class="token number">2</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>光泽粗糙度与基础材质粗糙度<strong>独立</strong>。</li></ul><hr /><h2 id="光泽brdf"><a class="markdownIt-Anchor" href="#光泽brdf"></a> 光泽BRDF</h2><p>光泽BRDF基于<strong>微表面理论</strong>和<strong>Charlie分布</strong>（Conty &amp; Kulla, 2017）。</p><h3 id="光泽分布"><a class="markdownIt-Anchor" href="#光泽分布"></a> 光泽分布</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">alpha_g <span class="token operator">=</span> sheenRoughness <span class="token operator">*</span> sheenRoughness<span class="token punctuation">;</span>inv_r <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> alpha_g<span class="token punctuation">;</span>cos2h <span class="token operator">=</span> NdotH <span class="token operator">*</span> NdotH<span class="token punctuation">;</span>sin2h <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">-</span> cos2h<span class="token punctuation">;</span>sheen_distribution <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">+</span> inv_r<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">pow</span><span class="token punctuation">(</span>sin2h<span class="token punctuation">,</span> inv_r <span class="token operator">*</span> <span class="token number">0.5</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> PI<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="光泽可见性charlie"><a class="markdownIt-Anchor" href="#光泽可见性charlie"></a> 光泽可见性（Charlie）</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">float</span> <span class="token function">l</span><span class="token punctuation">(</span><span class="token keyword">float</span> x<span class="token punctuation">,</span> <span class="token keyword">float</span> alpha_g<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">float</span> one_minus_alpha_sq <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> alpha_g<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> alpha_g<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">float</span> a <span class="token operator">=</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token number">21.5473</span><span class="token punctuation">,</span> <span class="token number">25.3245</span><span class="token punctuation">,</span> one_minus_alpha_sq<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">float</span> b <span class="token operator">=</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token number">3.82987</span><span class="token punctuation">,</span> <span class="token number">3.32435</span><span class="token punctuation">,</span> one_minus_alpha_sq<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">float</span> c <span class="token operator">=</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token number">0.19823</span><span class="token punctuation">,</span> <span class="token number">0.16801</span><span class="token punctuation">,</span> one_minus_alpha_sq<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">float</span> d <span class="token operator">=</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1.97760</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1.27393</span><span class="token punctuation">,</span> one_minus_alpha_sq<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">float</span> e <span class="token operator">=</span> <span class="token function">mix</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">4.32054</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">4.85967</span><span class="token punctuation">,</span> one_minus_alpha_sq<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> a <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">+</span> b <span class="token operator">*</span> <span class="token function">pow</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> c<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> d <span class="token operator">*</span> x <span class="token operator">+</span> e<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">float</span> <span class="token function">lambda_sheen</span><span class="token punctuation">(</span><span class="token keyword">float</span> cos_theta<span class="token punctuation">,</span> <span class="token keyword">float</span> alpha_g<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">abs</span><span class="token punctuation">(</span>cos_theta<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0.5</span> <span class="token operator">?</span> <span class="token function">exp</span><span class="token punctuation">(</span><span class="token function">l</span><span class="token punctuation">(</span>cos_theta<span class="token punctuation">,</span> alpha_g<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">exp</span><span class="token punctuation">(</span><span class="token number">2.0</span> <span class="token operator">*</span> <span class="token function">l</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">,</span> alpha_g<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">l</span><span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> cos_theta<span class="token punctuation">,</span> alpha_g<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>sheen_visibility <span class="token operator">=</span> <span class="token number">1.0</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">+</span> <span class="token function">lambda_sheen</span><span class="token punctuation">(</span>NdotV<span class="token punctuation">,</span> alpha_g<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">lambda_sheen</span><span class="token punctuation">(</span>NdotL<span class="token punctuation">,</span> alpha_g<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">4.0</span> <span class="token operator">*</span> NdotV <span class="token operator">*</span> NdotL<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>简化可见性</strong>（Ashikhmin &amp; Premoze, 2007）：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">sheen_visibility <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">4</span> <span class="token operator">*</span> <span class="token punctuation">(</span>NdotL <span class="token operator">+</span> NdotV <span class="token operator">-</span> NdotL <span class="token operator">*</span> NdotV<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><hr /><h2 id="光泽分层"><a class="markdownIt-Anchor" href="#光泽分层"></a> 光泽分层</h2><ul><li>使用<strong>反照率缩放</strong>与基础材质结合（Conty &amp; Kulla, 2017）。</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">float</span> <span class="token function">max3</span><span class="token punctuation">(</span><span class="token keyword">vec3</span> v<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token keyword">return</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">max</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span>x<span class="token punctuation">,</span> v<span class="token punctuation">.</span>y<span class="token punctuation">)</span><span class="token punctuation">,</span> v<span class="token punctuation">.</span>z<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>sheen_albedo_scaling <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> <span class="token function">max3</span><span class="token punctuation">(</span>sheenColor<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">E</span><span class="token punctuation">(</span>VdotN<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1.0</span> <span class="token operator">-</span> <span class="token function">max3</span><span class="token punctuation">(</span>sheenColor<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">E</span><span class="token punctuation">(</span>LdotN<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>sheen_material <span class="token operator">=</span> sheenColor <span class="token operator">*</span> sheen_brdf <span class="token operator">+</span> material <span class="token operator">*</span> sheen_albedo_scaling<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>为了性能，可以跳过<code>E(LdotN)</code>：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">sheen_albedo_scaling <span class="token operator">=</span> <span class="token number">1.0</span> <span class="token operator">-</span> <span class="token function">max3</span><span class="token punctuation">(</span>sheenColor<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">E</span><span class="token punctuation">(</span>VdotN<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>缩放应用于<strong>直接和间接光照</strong>：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">specular_direct <span class="token operator">*=</span> sheen_albedo_scaling<span class="token punctuation">;</span>diffuse_direct <span class="token operator">*=</span> sheen_albedo_scaling<span class="token punctuation">;</span>environment_irradiance_indirect <span class="token operator">*=</span> sheen_albedo_scaling<span class="token punctuation">;</span>specular_environment_reflectance_indirect <span class="token operator">*=</span> sheen_albedo_scaling<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><hr /><h2 id="参考文献"><a class="markdownIt-Anchor" href="#参考文献"></a> 参考文献</h2><ul><li>Westin, S. H., Arvo, J. R., Torrance, K. E. (1992): <em>从复杂表面预测反射函数</em>，SIGGRAPH</li><li>Conty Estevez, A., Kulla, C. (2017): <em>生产友好的微表面光泽BRDF</em>，SIGGRAPH</li><li>Ashikhmin, M., Premoze, S. (2007): <em>基于分布的BRDF</em></li><li>Filament材质模型 - 布料模型</li><li>Filament的cmgen工具</li><li>Neubelt, D., Pettineo, M. (2013): <em>为The Order: 1886打造次世代材质管线</em>，SIGGRAPH</li><li>企业PBR着色模型 - 光泽</li></ul>]]></content>
    
    
    <summary type="html">本文详解glTF光泽(Sheen)材质扩展的实现原理，涵盖光泽颜色/粗糙度的纹理映射与独立控制，重点解析基于Charlie分布的光泽BRDF模型及其微表面理论实现，适用于模拟天鹅绒类材质的背向散射效果。(79字)</summary>
    
    
    
    <category term="Rendering - Computer Graphics" scheme="http://vanishing.cc/categories/Rendering-Computer-Graphics/"/>
    
    
    <category term="PBR" scheme="http://vanishing.cc/tags/PBR/"/>
    
    <category term="Rendering" scheme="http://vanishing.cc/tags/Rendering/"/>
    
    <category term="Computer-Graphics" scheme="http://vanishing.cc/tags/Computer-Graphics/"/>
    
    <category term="Shading" scheme="http://vanishing.cc/tags/Shading/"/>
    
    <category term="Sheen" scheme="http://vanishing.cc/tags/Sheen/"/>
    
  </entry>
  
  <entry>
    <title>Irridescence从理论到实践</title>
    <link href="http://vanishing.cc/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/"/>
    <id>http://vanishing.cc/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/</id>
    <published>2025-09-23T11:24:42.000Z</published>
    <updated>2026-04-19T10:46:15.033Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文解析基于薄膜干涉的彩虹色渲染技术，详细介绍了KHR_materials_iridescence扩展的参数体系（厚度、折射率）及实现原理，包括BRDF模型在金属/电介质材质上的具体应用方法。</p></blockquote><blockquote><p>本文为<a href="https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_iridescence/README.md">这篇文章</a>的翻译与整理</p></blockquote><h1 id="彩虹色iridescence"><a class="markdownIt-Anchor" href="#彩虹色iridescence"></a> 彩虹色（Iridescence）</h1><p>彩虹色描述了一种<strong>色调随观察角和光照角变化</strong>的效果。</p><ul><li>半透明层的薄膜产生<strong>相互反射</strong>，由于<strong>薄膜干涉</strong>，某些波长被吸收或放大。</li><li>例子包括肥皂泡、油膜和昆虫翅膀。</li><li>此扩展允许指定薄膜的<strong>厚度</strong>和<strong>折射率（IOR）</strong>，实现彩虹色材质。</li></ul><hr /><h2 id="属性"><a class="markdownIt-Anchor" href="#属性"></a> 属性</h2><table><thead><tr><th>类型</th><th>描述</th><th>必需</th></tr></thead><tbody><tr><td><code>iridescenceFactor</code></td><td>彩虹色强度因子。</td><td>否，默认值：0.0</td></tr><tr><td><code>iridescenceTexture</code></td><td>彩虹色强度纹理。</td><td>否</td></tr><tr><td><code>iridescenceIor</code></td><td>薄膜层的折射率。</td><td>否，默认值：1.3</td></tr><tr><td><code>iridescenceThicknessMinimum</code></td><td>最小薄膜厚度（纳米）。</td><td>否，默认值：100.0</td></tr><tr><td><code>iridescenceThicknessMaximum</code></td><td>最大薄膜厚度（纳米）。</td><td>否，默认值：400.0</td></tr><tr><td><code>iridescenceThicknessTexture</code></td><td>厚度纹理。</td><td>否</td></tr></tbody></table><ul><li><strong>强度计算</strong>：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">iridescence = iridescenceFactor * iridescenceTexture.r<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li><p>如果未设置纹理，假设值为<code>1.0</code>。</p></li><li><p>如果<code>iridescenceFactor</code>为零（默认），效果被禁用。</p></li><li><p><strong>厚度计算</strong>：</p></li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">thickness = mix(    iridescenceThicknessMinimum,    iridescenceThicknessMaximum,    iridescenceThicknessTexture.g)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>如果缺少厚度纹理，默认为<code>iridescenceThicknessMaximum</code>。</li><li>色调变化源于反射波长的<strong>相长和相消干涉</strong>。</li><li>接近**可见光波长一半（380–750 nm）**的薄膜厚度产生最强的效果。</li><li>增加厚度由于混合干涉产生<strong>柔和色彩图案</strong>。</li></ul><img src="/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/%E5%8E%9F%E7%90%86%E8%A7%A3%E9%87%8A.png" class="" title="原理解释"><hr /><h2 id="折射率ior"><a class="markdownIt-Anchor" href="#折射率ior"></a> 折射率（IOR）</h2><ul><li>薄膜折射率（<code>iridescenceIor</code>）可以与基础材质不同。</li><li>薄膜折射率与基础材质折射率之间的差异越大，<strong>彩虹色效果越强</strong>。</li><li>折射率影响<strong>光程差</strong>和颜色偏移。</li></ul><img src="/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/%E4%B8%8D%E5%90%8Cthickness.png" class="" title="不同厚度效果"><img src="/2025/09/23/Graphics/Rendering/Irridescence%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/%E4%B8%8D%E5%90%8CIOR.png" class="" title="不同折射率效果"><hr /><h2 id="彩虹色brdf"><a class="markdownIt-Anchor" href="#彩虹色brdf"></a> 彩虹色BRDF</h2><p>效果使用<strong>微表面BRDF</strong>和修改的菲涅尔反射项建模。</p><h3 id="金属基础材质"><a class="markdownIt-Anchor" href="#金属基础材质"></a> 金属基础材质</h3><pre class="line-numbers language-text" data-language="text"><code class="language-text">metal_brdf =  iridescent_conductor_layer(    iridescence_strength = iridescence,    iridescence_thickness = iridescenceThickness,    iridescence_ior = iridescenceIor,    outside_ior = 1.0,    base_f0 = baseColor,    specular_brdf = specular_brdf(α = roughness^2)  )<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="电介质基础材质"><a class="markdownIt-Anchor" href="#电介质基础材质"></a> 电介质基础材质</h3><pre class="line-numbers language-text" data-language="text"><code class="language-text">dielectric_brdf =  iridescent_dielectric_layer(    iridescence_strength = iridescence,    iridescence_thickness = iridescenceThickness,    iridescence_ior = iridescenceIor,    outside_ior = 1.0,    base_ior = 1.5,    base = diffuse_brdf(color = baseColor),    specular_brdf = specular_brdf(α = roughness^2)  )<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>对于<strong>透射材质</strong>，<code>diffuse_brdf</code>被与<code>specular_btdf</code>的混合替换（见<code>KHR_materials_transmission</code>）。</p></li><li><p><code>base_ior</code>可以通过<code>KHR_materials_ior</code>修改。</p></li><li><p>与<strong>KHR_materials_specular</strong>结合时：</p></li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">dielectric_brdf =  iridescent_dielectric_layer(    iridescence_strength = iridescence,    iridescence_thickness = iridescenceThickness,    iridescence_ior = iridescenceIor,    outside_ior = 1.0,    base_ior = 1.5,    base_f0_color = specularColor.rgb,    specular_weight = specular,    base = diffuse_brdf(color = baseColor),    specular_brdf = specular_brdf(α = roughness^2)  )<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>清漆层<strong>忽略与薄膜的界面</strong>以简化。</li></ul><hr /><h2 id="实现说明"><a class="markdownIt-Anchor" href="#实现说明"></a> 实现说明</h2><h3 id="金属基础材质-2"><a class="markdownIt-Anchor" href="#金属基础材质-2"></a> 金属基础材质</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">function <span class="token function">iridescent_conductor_layer</span><span class="token punctuation">(</span>iridescence_strength<span class="token punctuation">,</span> iridescence_thickness<span class="token punctuation">,</span> iridescence_ior<span class="token punctuation">,</span> outside_ior<span class="token punctuation">,</span> base_f0<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">mix</span><span class="token punctuation">(</span>    <span class="token function">conductor_fresnel</span><span class="token punctuation">(</span>base_f0<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">)</span><span class="token punctuation">,</span>    specular_brdf <span class="token operator">*</span> <span class="token function">iridescent_fresnel</span><span class="token punctuation">(</span>outside_ior<span class="token punctuation">,</span> iridescence_ior<span class="token punctuation">,</span> base_f0<span class="token punctuation">,</span> iridescence_thickness<span class="token punctuation">)</span><span class="token punctuation">,</span>    iridescence_strength  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="电介质基础材质-2"><a class="markdownIt-Anchor" href="#电介质基础材质-2"></a> 电介质基础材质</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">function <span class="token function">iridescent_dielectric_layer</span><span class="token punctuation">(</span>iridescence_strength<span class="token punctuation">,</span> iridescence_thickness<span class="token punctuation">,</span> iridescence_ior<span class="token punctuation">,</span> outside_ior<span class="token punctuation">,</span> base_ior<span class="token punctuation">,</span> base<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  base_f0 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> base_ior<span class="token punctuation">)</span><span class="token operator">/</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> base_ior<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">^</span><span class="token number">2</span>  iridescent_f <span class="token operator">=</span> <span class="token function">iridescent_fresnel</span><span class="token punctuation">(</span>outside_ior<span class="token punctuation">,</span> iridescence_ior<span class="token punctuation">,</span> base_f0<span class="token punctuation">,</span> iridescence_thickness<span class="token punctuation">)</span>  <span class="token keyword">return</span> <span class="token function">mix</span><span class="token punctuation">(</span>    <span class="token function">fresnel_mix</span><span class="token punctuation">(</span>base_ior<span class="token punctuation">,</span> base<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">rgb_mix</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">,</span> iridescent_f<span class="token punctuation">)</span><span class="token punctuation">,</span>    iridescence_strength  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>rgb_mix</code>确保<strong>能量守恒</strong>：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">function <span class="token function">rgb_mix</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">,</span> rgb_alpha<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  rgb_alpha_max <span class="token operator">=</span> <span class="token function">max</span><span class="token punctuation">(</span>rgb_alpha<span class="token punctuation">.</span>r<span class="token punctuation">,</span> rgb_alpha<span class="token punctuation">.</span>g<span class="token punctuation">,</span> rgb_alpha<span class="token punctuation">.</span>b<span class="token punctuation">)</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> rgb_alpha_max<span class="token punctuation">)</span> <span class="token operator">*</span> base <span class="token operator">+</span> rgb_alpha <span class="token operator">*</span> specular_brdf<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>与<strong>KHR_materials_specular</strong>结合时，添加base_f0_color和specular_weight：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">function <span class="token function">iridescent_dielectric_layer</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  base_f0 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">-</span>base_ior<span class="token punctuation">)</span><span class="token operator">/</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">+</span>base_ior<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">^</span><span class="token number">2</span> <span class="token operator">*</span> base_f0_color  base_f0 <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span>base_f0<span class="token punctuation">,</span> <span class="token function">float3</span><span class="token punctuation">(</span><span class="token number">1.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  fr <span class="token operator">=</span> base_f0 <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> base_f0<span class="token punctuation">)</span><span class="token operator">*</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> <span class="token function">abs</span><span class="token punctuation">(</span>VdotH<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">^</span><span class="token number">5</span>  iridescent_f <span class="token operator">=</span> <span class="token function">iridescent_fresnel</span><span class="token punctuation">(</span>outside_ior<span class="token punctuation">,</span> iridescence_ior<span class="token punctuation">,</span> specular_weight <span class="token operator">*</span> base_f0<span class="token punctuation">,</span> iridescence_thickness<span class="token punctuation">)</span>  <span class="token keyword">return</span> <span class="token function">rgb_mix</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> specular_brdf<span class="token punctuation">,</span> <span class="token function">mix</span><span class="token punctuation">(</span>specular_weight <span class="token operator">*</span> fr<span class="token punctuation">,</span> iridescent_f<span class="token punctuation">,</span> iridescence_strength<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr /><h2 id="彩虹色菲涅尔"><a class="markdownIt-Anchor" href="#彩虹色菲涅尔"></a> 彩虹色菲涅尔</h2><ul><li><p>建模两个<strong>材质界面</strong>：</p><ol><li>外部 → 薄膜</li><li>薄膜 → 基础材质</li></ol></li></ul><h3 id="菲涅尔方程"><a class="markdownIt-Anchor" href="#菲涅尔方程"></a> 菲涅尔方程</h3><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">float</span> <span class="token function">IorToFresnel0</span><span class="token punctuation">(</span><span class="token keyword">float</span> transmittedIor<span class="token punctuation">,</span> <span class="token keyword">float</span> incidentIor<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">pow</span><span class="token punctuation">(</span><span class="token punctuation">(</span>transmittedIor <span class="token operator">-</span> incidentIor<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>transmittedIor <span class="token operator">+</span> incidentIor<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2.0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>使用<strong>斯涅尔定律</strong>计算折射角的余弦：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">float</span> sinTheta2Sq <span class="token operator">=</span> <span class="token function">pow</span><span class="token punctuation">(</span>outsideIor <span class="token operator">/</span> iridescenceIor<span class="token punctuation">,</span> <span class="token number">2.0</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">-</span> <span class="token function">pow</span><span class="token punctuation">(</span>cosTheta1<span class="token punctuation">,</span> <span class="token number">2.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> cosTheta2Sq <span class="token operator">=</span> <span class="token number">1.0</span> <span class="token operator">-</span> sinTheta2Sq<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>cosTheta2Sq <span class="token operator">&lt;</span> <span class="token number">0.0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">1.0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 全内反射</span><span class="token keyword">float</span> cosTheta2 <span class="token operator">=</span> <span class="token function">sqrt</span><span class="token punctuation">(</span>cosTheta2Sq<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="相移"><a class="markdownIt-Anchor" href="#相移"></a> 相移</h3><pre class="line-numbers language-text" data-language="text"><code class="language-text">Δφ = (2π / λ) * OPDOPD = 2 * iridescenceIor * iridescenceThickness * cosTheta2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>每个界面的基础相位根据折射率关系近似为0.0或π。</li><li>忽略偏振。</li></ul><hr /><h2 id="解析光谱积分"><a class="markdownIt-Anchor" href="#解析光谱积分"></a> 解析光谱积分</h2><ul><li>在<strong>傅里叶空间</strong>中计算反射项：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">vec3</span> R123 <span class="token operator">=</span> <span class="token function">clamp</span><span class="token punctuation">(</span>R12 <span class="token operator">*</span> R23<span class="token punctuation">,</span> <span class="token number">1e-5</span><span class="token punctuation">,</span> <span class="token number">0.9999</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">vec3</span> r123 <span class="token operator">=</span> <span class="token function">sqrt</span><span class="token punctuation">(</span>R123<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">vec3</span> Rs <span class="token operator">=</span> <span class="token function">sq</span><span class="token punctuation">(</span>T121<span class="token punctuation">)</span> <span class="token operator">*</span> R23 <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">1.0</span><span class="token punctuation">)</span> <span class="token operator">-</span> R123<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">vec3</span> C0 <span class="token operator">=</span> R12 <span class="token operator">+</span> Rs<span class="token punctuation">;</span>I <span class="token operator">=</span> C0<span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> m <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> m <span class="token operator">&lt;=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token operator">++</span>m<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    Cm <span class="token operator">*=</span> r123<span class="token punctuation">;</span>    <span class="token keyword">vec3</span> Sm <span class="token operator">=</span> <span class="token number">2.0</span> <span class="token operator">*</span> <span class="token function">evalSensitivity</span><span class="token punctuation">(</span><span class="token keyword">float</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token operator">*</span> OPD<span class="token punctuation">,</span> <span class="token keyword">float</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token operator">*</span> phi<span class="token punctuation">)</span><span class="token punctuation">;</span>    I <span class="token operator">+=</span> Cm <span class="token operator">*</span> Sm<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>F_iridescence <span class="token operator">=</span> <span class="token function">max</span><span class="token punctuation">(</span>I<span class="token punctuation">,</span> <span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">0.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>敏感性函数：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">vec3</span> <span class="token function">evalSensitivity</span><span class="token punctuation">(</span><span class="token keyword">float</span> OPD<span class="token punctuation">,</span> <span class="token keyword">vec3</span> shift<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">float</span> phase <span class="token operator">=</span> <span class="token number">2.0</span> <span class="token operator">*</span> M_PI <span class="token operator">*</span> OPD <span class="token operator">*</span> <span class="token number">1.0e-9</span><span class="token punctuation">;</span>    <span class="token keyword">vec3</span> val <span class="token operator">=</span> <span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">5.4856e-13</span><span class="token punctuation">,</span> <span class="token number">4.4201e-13</span><span class="token punctuation">,</span> <span class="token number">5.2481e-13</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">vec3</span> pos <span class="token operator">=</span> <span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">1.6810e+06</span><span class="token punctuation">,</span> <span class="token number">1.7953e+06</span><span class="token punctuation">,</span> <span class="token number">2.2084e+06</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">vec3</span> var <span class="token operator">=</span> <span class="token keyword">vec3</span><span class="token punctuation">(</span><span class="token number">4.3278e+09</span><span class="token punctuation">,</span> <span class="token number">9.3046e+09</span><span class="token punctuation">,</span> <span class="token number">6.6121e+09</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">vec3</span> xyz <span class="token operator">=</span> val <span class="token operator">*</span> <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token number">2.0</span> <span class="token operator">*</span> M_PI <span class="token operator">*</span> var<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">cos</span><span class="token punctuation">(</span>pos <span class="token operator">*</span> phase <span class="token operator">+</span> shift<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">exp</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token function">sq</span><span class="token punctuation">(</span>phase<span class="token punctuation">)</span> <span class="token operator">*</span> var<span class="token punctuation">)</span><span class="token punctuation">;</span>    xyz<span class="token punctuation">.</span>x <span class="token operator">+=</span> <span class="token number">9.7470e-14</span> <span class="token operator">*</span> <span class="token function">sqrt</span><span class="token punctuation">(</span><span class="token number">2.0</span> <span class="token operator">*</span> M_PI <span class="token operator">*</span> <span class="token number">4.5282e+09</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">cos</span><span class="token punctuation">(</span><span class="token number">2.2399e+06</span> <span class="token operator">*</span> phase <span class="token operator">+</span> shift<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">exp</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">4.5282e+09</span> <span class="token operator">*</span> <span class="token function">sq</span><span class="token punctuation">(</span>phase<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    xyz <span class="token operator">/=</span> <span class="token number">1.0685e-7</span><span class="token punctuation">;</span>    <span class="token keyword">vec3</span> rgb <span class="token operator">=</span> XYZ_TO_REC709 <span class="token operator">*</span> xyz<span class="token punctuation">;</span>    <span class="token keyword">return</span> rgb<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>颜色空间转换矩阵：</li></ul><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl"><span class="token keyword">const</span> <span class="token keyword">mat3</span> XYZ_TO_REC709 <span class="token operator">=</span> <span class="token keyword">mat3</span><span class="token punctuation">(</span>     <span class="token number">3.2404542</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">0.9692660</span><span class="token punctuation">,</span>  <span class="token number">0.0556434</span><span class="token punctuation">,</span>    <span class="token operator">-</span><span class="token number">1.5371385</span><span class="token punctuation">,</span>  <span class="token number">1.8760108</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">0.2040259</span><span class="token punctuation">,</span>    <span class="token operator">-</span><span class="token number">0.4985314</span><span class="token punctuation">,</span>  <span class="token number">0.0415560</span><span class="token punctuation">,</span>  <span class="token number">1.0572252</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>完整推导在<strong>Belcour &amp; Barla, 2017, section 4</strong>（解析光谱积分）中描述。</p></blockquote><hr /><h2 id="参考文献"><a class="markdownIt-Anchor" href="#参考文献"></a> 参考文献</h2><ul><li>Belcour, L. and Barla, P. (2017): <em>微表面理论在变化彩虹色建模中的实用扩展</em></li><li>Autodesk: Arnold for Maya用户指南 - 薄膜</li><li>Drobot, M. and Micciulla, A. (2017): <em>使命召唤：无限战争中的实用多层材质</em></li><li>Kneiphof, T., Golla, T., Klein, R. (2019): <em>变化彩虹色的微表面BRDF实时基于图像照明</em></li><li>Akenine-Möller, T. et al. (2018): <em>实时渲染，第四版</em>，第361页及以后</li><li>Sussenbach, M. (2013): <em>实时渲染彩虹色物体</em></li></ul>]]></content>
    
    
    <summary type="html">本文解析基于薄膜干涉的彩虹色渲染技术，详细介绍了KHR_materials_iridescence扩展的参数体系（厚度、折射率）及实现原理，包括BRDF模型在金属/电介质材质上的具体应用方法。</summary>
    
    
    
    <category term="Rendering - Computer Graphics" scheme="http://vanishing.cc/categories/Rendering-Computer-Graphics/"/>
    
    
    <category term="PBR" scheme="http://vanishing.cc/tags/PBR/"/>
    
    <category term="Rendering" scheme="http://vanishing.cc/tags/Rendering/"/>
    
    <category term="Computer-Graphics" scheme="http://vanishing.cc/tags/Computer-Graphics/"/>
    
    <category term="Shading" scheme="http://vanishing.cc/tags/Shading/"/>
    
    <category term="Iridescence" scheme="http://vanishing.cc/tags/Iridescence/"/>
    
  </entry>
  
  <entry>
    <title>ClearCoat从理论到实践</title>
    <link href="http://vanishing.cc/2025/09/23/Graphics/Rendering/ClearCoat%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/"/>
    <id>http://vanishing.cc/2025/09/23/Graphics/Rendering/ClearCoat%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/</id>
    <published>2025-09-23T11:21:21.000Z</published>
    <updated>2026-04-19T10:46:10.191Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文解析glTF清漆层(ClearCoat)技术实现，涵盖参数配置、BRDF分层模型及菲涅尔混合计算，重点阐述强度/粗糙度因子与纹理的线性叠加方式，以及清漆层与基础材质的微表面BRDF合成原理。</p></blockquote><blockquote><p>本文为<a href="https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md">这篇文章</a>的翻译与整理</p></blockquote><h1 id="清漆层clearcoat"><a class="markdownIt-Anchor" href="#清漆层clearcoat"></a> 清漆层（Clearcoat）</h1><table><thead><tr><th>类型</th><th>描述</th><th>必需</th></tr></thead><tbody><tr><td><code>clearcoatFactor</code></td><td>清漆层强度。</td><td>否，默认值：0.0</td></tr><tr><td><code>clearcoatTexture</code></td><td>清漆层强度纹理。</td><td>否</td></tr><tr><td><code>clearcoatRoughnessFactor</code></td><td>清漆层粗糙度。</td><td>否，默认值：0.0</td></tr><tr><td><code>clearcoatRoughnessTexture</code></td><td>清漆层粗糙度纹理。</td><td>否</td></tr><tr><td><code>clearcoatNormalTexture</code></td><td>清漆层法线贴图纹理。</td><td>否</td></tr></tbody></table><blockquote><p>如果<code>clearcoatFactor</code>为零，整个清漆层将被禁用。</p></blockquote><h2 id="值计算"><a class="markdownIt-Anchor" href="#值计算"></a> 值计算</h2><p>清漆层强度和粗糙度可以使用<strong>因子</strong>、<strong>纹理</strong>或两者来定义。</p><ul><li>如果未提供<code>clearcoatTexture</code>或<code>clearcoatRoughnessTexture</code>，则假设其纹理分量的值为<code>1.0</code>。</li><li>所有清漆层纹理在<strong>线性空间</strong>中包含RGB分量。</li><li>如果同时存在因子和纹理，因子作为<strong>线性乘数</strong>：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">clearcoat = clearcoatFactor * clearcoatTexture.rclearcoatRoughness = clearcoatRoughnessFactor * clearcoatRoughnessTexture.g<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>如果未提供<code>clearcoatNormalTexture</code>，则不对清漆层应用法线映射。</li><li>它可以引用与基础材质相同的法线贴图或任何兼容的法线贴图。</li></ul><h3 id="切线空间要求"><a class="markdownIt-Anchor" href="#切线空间要求"></a> 切线空间要求</h3><p>使用带有清漆层法线纹理的清漆层材质的网格基元<strong>必须</strong>具有定义的切线空间：</p><ul><li>必须具有<code>NORMAL</code>和<code>TANGENT</code>属性，或者</li><li>基础材质必须具有法线纹理。</li></ul><p>如果缺失，切线向量将按照glTF 2.0的定义进行计算。</p><ul><li>网格基元<strong>应该</strong>提供<code>NORMAL</code>和<code>TANGENT</code>向量。</li><li>如果同时定义了<code>normalTexture</code>和<code>clearcoatNormalTexture</code>，它们<strong>应该</strong>使用相同的纹理坐标。</li></ul><hr /><h2 id="brdf建模"><a class="markdownIt-Anchor" href="#brdf建模"></a> BRDF建模</h2><p>清漆层效果通过<strong>微表面BRDF</strong>建模，层叠在glTF 2.0金属-粗糙度材质之上：</p><pre class="line-numbers language-text" data-language="text"><code class="language-text"># 基础glTF 2.0金属-粗糙度material = mix(dielectric_brdf, metal_brdf, metallic)# 清漆层clearcoat_brdf = specular_brdf(  normal = clearcoatNormal,  α = clearcoatRoughness^2)# 分层coated_material =  fresnel_coat(    normal = clearcoatNormal,    ior = 1.5,    weight = clearcoat,    base = material,    layer = clearcoat_brdf  )<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>fresnel_coat</code>使用菲涅尔项将BRDF作为<strong>层添加在另一个BSDF之上</strong>：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">weight_layer = weight * fresnel(ior)weight_base = 1 - weight_layer<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li><code>normal</code>仅影响顶层。</li></ul><hr /><h2 id="实现非规范性"><a class="markdownIt-Anchor" href="#实现非规范性"></a> 实现（非规范性）</h2><ul><li>实现可能因<strong>设备性能</strong>和<strong>资源</strong>而异。</li></ul><h3 id="清漆层brdf"><a class="markdownIt-Anchor" href="#清漆层brdf"></a> 清漆层BRDF</h3><p>镜面BRDF使用<strong>glTF 2.0金属-粗糙度镜面项</strong>：</p><ul><li>粗糙度和法线来自<code>clearcoatNormal</code>和<code>clearcoatRoughness</code>。</li></ul><h3 id="分层"><a class="markdownIt-Anchor" href="#分层"></a> 分层</h3><p><code>fresnel_coat</code>函数使用<strong>Schlick菲涅尔项</strong>：</p><pre class="line-numbers language-glsl" data-language="glsl"><code class="language-glsl">function <span class="token function">fresnel_coat</span><span class="token punctuation">(</span>normal<span class="token punctuation">,</span> ior<span class="token punctuation">,</span> weight<span class="token punctuation">,</span> base<span class="token punctuation">,</span> layer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  f0 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">-</span>ior<span class="token punctuation">)</span><span class="token operator">/</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">+</span>ior<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">^</span><span class="token number">2</span>  fr <span class="token operator">=</span> f0 <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> f0<span class="token punctuation">)</span><span class="token operator">*</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> <span class="token function">abs</span><span class="token punctuation">(</span><span class="token function">dot</span><span class="token punctuation">(</span>V<span class="token punctuation">,</span> normal<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">^</span><span class="token number">5</span>  <span class="token keyword">return</span> <span class="token function">mix</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> layer<span class="token punctuation">,</span> weight <span class="token operator">*</span> fr<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>应用函数：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">coated_material = mix(  material,  clearcoat_brdf(clearcoatRoughness^2),  clearcoat * (0.04 + (1 - 0.04) * (1 - VdotNc)^5))<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>使用附录B中的符号：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">clearcoat_fresnel = 0.04 + (1 - 0.04) * (1 - abs(VdotNc))^5clearcoat_alpha = clearcoatRoughness^2clearcoat_brdf = D(clearcoat_alpha) * G(clearcoat_alpha) / (4 * abs(VdotNc) * abs(LdotNc))coated_material = mix(material, clearcoat_brdf, clearcoat * clearcoat_fresnel)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="发射"><a class="markdownIt-Anchor" href="#发射"></a> 发射</h3><ul><li>清漆层位于<strong>发射之上</strong>。</li><li>因此，发射被菲涅尔项变暗：</li></ul><pre class="line-numbers language-text" data-language="text"><code class="language-text">coated_emission = emission * (1 - clearcoat * clearcoat_fresnel)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><hr /><h2 id="讨论"><a class="markdownIt-Anchor" href="#讨论"></a> 讨论</h2><p>为了通过简单的分层函数保持<strong>能量守恒</strong>：</p><ul><li>微表面菲涅尔项使用<code>NdotV</code>而不是<code>VdotH</code>。</li><li>忽略微表面的方向。</li><li>低清漆层粗糙度近似为<code>NdotV ≈ NdotL</code>。</li></ul><p>局限性：</p><ul><li>假设清漆层无限薄（无折射）。</li><li>层的折射率独立计算。</li><li>层间无散射。</li><li>无衍射。</li></ul><hr /><h2 id="参考文献"><a class="markdownIt-Anchor" href="#参考文献"></a> 参考文献</h2><ul><li>理论、文档和实现</li><li>Autodesk标准表面 - 涂层</li><li>AxF - 外观交换格式</li><li>Blender原理化BSDF</li><li>Disney BRDF探索器</li><li>企业PBR着色模型 - 清漆层</li><li>Filament材质模型 - 清漆层</li><li>Disney的基于物理着色</li><li>Substance Painter - 更新的清漆着色器</li><li>ALLEGORITHMIC的PBR指南（第1部分和第2部分）</li><li>虚幻引擎4材质 - 清漆层</li></ul>]]></content>
    
    
    <summary type="html">本文解析glTF清漆层(ClearCoat)技术实现，涵盖参数配置、BRDF分层模型及菲涅尔混合计算，重点阐述强度/粗糙度因子与纹理的线性叠加方式，以及清漆层与基础材质的微表面BRDF合成原理。</summary>
    
    
    
    <category term="Rendering - Computer Graphics" scheme="http://vanishing.cc/categories/Rendering-Computer-Graphics/"/>
    
    
    <category term="PBR" scheme="http://vanishing.cc/tags/PBR/"/>
    
    <category term="Rendering" scheme="http://vanishing.cc/tags/Rendering/"/>
    
    <category term="Computer-Graphics" scheme="http://vanishing.cc/tags/Computer-Graphics/"/>
    
    <category term="Shading" scheme="http://vanishing.cc/tags/Shading/"/>
    
    <category term="ClearCoat" scheme="http://vanishing.cc/tags/ClearCoat/"/>
    
  </entry>
  
  <entry>
    <title>DotNet响应式编程,一键入坑到入坟</title>
    <link href="http://vanishing.cc/2025/09/23/Programming/DotNet/DotNet%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B,%E4%B8%80%E9%94%AE%E5%85%A5%E5%9D%91%E5%88%B0%E5%85%A5%E5%9D%9F/"/>
    <id>http://vanishing.cc/2025/09/23/Programming/DotNet/DotNet%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B,%E4%B8%80%E9%94%AE%E5%85%A5%E5%9D%91%E5%88%B0%E5%85%A5%E5%9D%9F/</id>
    <published>2025-09-23T10:58:44.000Z</published>
    <updated>2026-03-18T03:40:42.967Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>摘要：本文系统介绍Rx.NET响应式编程核心：IObservable/IObserver基础、Subject类型、冷/热源处理、调度器与线程模型、时间操作符及错误处理，并附最佳实践指南，助.NET开发者安全落地Rx。（79字）</p></blockquote><h1 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h1><p>这是一份面向 .NET 工程师的 <a href="http://Rx.NET">Rx.NET</a> 学习与实践指南：从 IObservable/IObserver 基础，到 Subject 类型、冷/热源、多订阅控制，再到调度器与线程模型、时间类操作符、错误处理与测试，最后给出落地的最佳实践清单。适合希望系统性掌握响应式编程并在实际项目中安全使用 Rx 的读者。</p><p>你可以在<a href="https://github.com/Vanish0314/Rxxxxxxx">Rxxxxxxx</a>中找到一些代码示例</p><p>对于调度器一章节,为了更好理解,可以查看这个仓库:<a href="https://github.com/Vanish0314/VaniRx">VaniRx</a></p><!-- more --><h1 id="part1-why-rx"><a class="markdownIt-Anchor" href="#part1-why-rx"></a> Part1 - Why Rx</h1><h2 id="span-id20250817153337-jeg08gz-styledisplay-nonespannet事件的问题"><a class="markdownIt-Anchor" href="#span-id20250817153337-jeg08gz-styledisplay-nonespannet事件的问题"></a> <span id="20250817153337-jeg08gz" style="display: none;"></span>.NET事件的问题</h2><blockquote><p>讽刺的是,如果没有event关键字,c#的对事件的处理会更好</p></blockquote><p>.NET <code>event</code>关键字基本问题是: <strong>它们在 .NET 类型系统中得到了特殊处理</strong>, 使得没法像对象一样操作事件,比如存储在字段中,作为参数传递,不能使用LINQ,不能拓展等等.</p><p>event唯一的优势在于: <code>+=</code> &amp; <code>-=</code>​</p><h2 id="iobservablet"><a class="markdownIt-Anchor" href="#iobservablet"></a> IObservable<T></h2><h3 id="ienumerablet-vs-iobservablet"><a class="markdownIt-Anchor" href="#ienumerablet-vs-iobservablet"></a> <strong>IEnumerable&lt;T&gt; vs.</strong>  <strong>IObservable&lt;T&gt;</strong></h3><ul><li><strong>IEnumerable&lt;T&gt;</strong>  让代码(IEnumerator)可以主动获取值（通常通过 <code>foreach</code> 循环），</li><li><strong>IObservable&lt;T&gt;</strong>  则在值可用时主动推送给代码(IObserver)。</li></ul><p>这种区别通常被称为 <strong>拉取（pull）与推送（push）</strong> ：</p><ul><li>我们通过执行 <code>foreach</code> 循环从 <code>IEnumerable&lt;T&gt;</code> 中“拉取”值，</li><li>而 <code>IObservable&lt;T&gt;</code> 会把值“推送”(通过<strong>Subscribe</strong>)到我们的代码中。</li></ul><p>‍</p><h3 id="接口定义"><a class="markdownIt-Anchor" href="#接口定义"></a> 接口定义</h3><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">public interface IObservable&lt;out T&gt;&#123;    IDisposable Subscribe(IObserver&lt;T&gt; observer);&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>这个接口唯一的方法很清楚地表明：</p><ul><li>如果我们想要接收事件，就必须 <strong>订阅</strong> 它。</li><li>我们也可以 <strong>取消订阅</strong>：<code>Subscribe</code> 方法返回一个 <code>IDisposable</code>，调用它的 <code>Dispose</code> 方法就能取消订阅。</li></ul><p>‍</p><h2 id="iobservert"><a class="markdownIt-Anchor" href="#iobservert"></a> IObserver<T></h2><h3 id="接口定义-2"><a class="markdownIt-Anchor" href="#接口定义-2"></a> 接口定义</h3><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">public interface IObserver&lt;in T&gt;&#123;    void OnNext(T value);    void OnError(Exception error);    void OnCompleted();&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><h2 id="subjectt"><a class="markdownIt-Anchor" href="#subjectt"></a> Subject<T></h2><blockquote><p>同时实现<code>IObserver&lt;T&gt;</code>和 <code>IObservable&lt;T&gt;</code>​</p><p>Rx与外界的桥梁</p><p>大多数情况下，并<strong>不推荐</strong>使用 Subjects</p></blockquote><h3 id="subjectt-2"><a class="markdownIt-Anchor" href="#subjectt-2"></a> Subject&lt;T&gt;</h3><p>立即将对其 <code>IObserver&lt;T&gt;</code> 方法的调用转发给当前所有订阅它的观察者</p><p>有新的订阅者加入，它们只能看到<strong>订阅之后</strong>发生的事件</p><h3 id="replaysubjectt"><a class="markdownIt-Anchor" href="#replaysubjectt"></a> ReplaySubject<T></h3><p>有新的订阅者加入，它们会收到迄今为止的所有历史事件</p><p>可以限制内存消耗,防止事件太多干爆内存</p><h3 id="behaviorsubjectt"><a class="markdownIt-Anchor" href="#behaviorsubjectt"></a> BehaviorSubject&lt;T&gt;</h3><p>只记住<strong>一个值</strong></p><h3 id="asyncsubjectt"><a class="markdownIt-Anchor" href="#asyncsubjectt"></a> AsyncSubject&lt;T&gt;</h3><p>把它接收到的<strong>最后一个值</strong>提供给所有观察者</p><p>在调用 <code>OnCompleted</code> 或 <code>OnError</code> 之前，根本不会给任何观察者发出通知</p><p>如果调用了 <code>OnError</code>，它只会把错误转发给所有当前和未来的订阅者</p><p>如果在调用 <code>OnCompleted</code> 之前从未调用过 <code>OnNext</code>，那么它没有最终值，只会完成所有观察者，而不会提供值</p><h2 id="hot-cold-source"><a class="markdownIt-Anchor" href="#hot-cold-source"></a> Hot &amp;  Cold Source</h2><ul><li><p>hot source 只提供订阅后的信息,比如鼠标移动</p></li><li><p>cold source 不管何时订阅,提供相同信息,比如<code>IEnumerable&lt;T&gt;.ToObservable()</code>​</p></li><li><p>source 不是非冷即热,而是有温度</p></li></ul><p>‍</p><h3 id="特殊情况-cold-then-hot"><a class="markdownIt-Anchor" href="#特殊情况-cold-then-hot"></a> 特殊情况: Cold-then-Hot</h3><blockquote><p>Windows 消息队列：如果你点击或输入时程序没反应，消息会先被排队，稍后再处理</p></blockquote><p>可以看作是 <strong>冷-然后-热</strong>：</p><ul><li>像冷源一样，你不会因为订阅得晚而错过过去的事件，</li><li>但一旦开始消费数据，就无法回到最开始。</li></ul><p>‍</p><h3 id="多订阅问题与-iconnectableobservablet"><a class="markdownIt-Anchor" href="#多订阅问题与-iconnectableobservablet"></a> 多订阅问题与 IConnectableObservable&lt;T&gt;</h3><p>这种 <strong>冷-然后-热</strong> 源在多订阅场景下会出问题：</p><ul><li>第一个订阅者可能会独占所有缓冲区里的旧事件，</li><li>后续订阅者则会错过。</li></ul><p>解决办法:</p><ul><li>在发送事件流前挂好所有订阅者</li></ul><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">public interface IConnectableObservable&lt;out T&gt; : IObservable&lt;T&gt;&#123;    IDisposable Connect();&#x2F;&#x2F;调用Connect 后, 事件才会开始发送&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="rx-序列的基本规则"><a class="markdownIt-Anchor" href="#rx-序列的基本规则"></a> Rx 序列的基本规则</h2><h3 id="1-onnext的内建背压机制"><a class="markdownIt-Anchor" href="#1-onnext的内建背压机制"></a> 1. OnNext的内建背压机制</h3><p>信息源必须等待观察者OnNext()方法结束后才能再次调用OnNext()方法</p><h3 id="2-订阅的生命周期"><a class="markdownIt-Anchor" href="#2-订阅的生命周期"></a> 2. 订阅的生命周期</h3><blockquote><p>一般情况下, .NET API 如果返回了一个实现了 <code>IDisposable</code> 的对象, 你却不调用 <code>Dispose()</code>, 那通常是一个错误.<br />但 <strong>Rx 的订阅是个例外:</strong>  只有当你希望提前停止时, 才需要主动调用 <code>Dispose()</code>​</p></blockquote><h4 id="rx退订时的规则"><a class="markdownIt-Anchor" href="#rx退订时的规则"></a> Rx退订时的规则:</h4><p><strong>一旦</strong> <strong>​<code>Dispose()</code>​</strong>  <strong>调用返回，信息源将不再对其观察者调用任何方法</strong></p><blockquote><p>不过，在 <code>Dispose()</code> <strong>尚未返回</strong>的那段时间内，信息源可能还会继续发事件.比如多线程情况下.</p></blockquote><p>‍</p><h4 id="取消订阅可能很慢甚至没用"><a class="markdownIt-Anchor" href="#取消订阅可能很慢甚至没用"></a> 取消订阅可能很慢甚至没用</h4><p>​<strong>​<code>Dispose</code>​</strong> <strong>不会等待关闭完成</strong>，它只是发出取消请求就立即返回。</p><p>比如某源创建了线程, 这个线程需要时间来关闭或者不会关闭.</p><h4 id="不管是源主动结束-还是手动退订-整个链路包括中间的-operator都会被关闭"><a class="markdownIt-Anchor" href="#不管是源主动结束-还是手动退订-整个链路包括中间的-operator都会被关闭"></a> 不管是源主动结束, 还是手动退订, 整个链路(包括中间的 operator)都会被关闭.</h4><h2 id="创建rx序列"><a class="markdownIt-Anchor" href="#创建rx序列"></a> 创建Rx序列</h2><h3 id="observablecreate"><a class="markdownIt-Anchor" href="#observablecreate"></a> Observable.Create</h3><ul><li><p>Rx的包装方法,用于让用户不需要关心并发模型,订阅释放等复杂问题</p><blockquote><p>使用<code>Observable.Create</code>的<code>Subscribe</code>方法返回时, Create内可能还没有运行.<br />这时订阅者取消, Create会保证不调用OnNext等规则</p></blockquote></li><li><p>Create方法是延迟执行的. 只有真正订阅时, 委托才会被调用.</p></li><li><p>多次订阅会多次执行委托</p></li></ul><h3 id="observabledefer"><a class="markdownIt-Anchor" href="#observabledefer"></a> Observable.Defer</h3><blockquote><p>​#TODO#: 暂时不明白:<br />Create本身也有延迟调用机制,为什么要使用Defer?<br />这两个之间的区别是什么?</p></blockquote><h3 id="observablegenerate"><a class="markdownIt-Anchor" href="#observablegenerate"></a> Observable.Generate</h3><h4 id="为什么使用"><a class="markdownIt-Anchor" href="#为什么使用"></a> 为什么使用</h4><blockquote><p>可以更方便处理订阅取消</p></blockquote><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">&#x2F;&#x2F; 不是最佳写法！IObservable&lt;int&gt; Range(int start, int count) &#x3D;&gt;    Observable.Create&lt;int&gt;(observer &#x3D;&gt;        &#123;            for (int i &#x3D; 0; i &lt; count; ++i)            &#123;                observer.OnNext(start + i);            &#125;            return Disposable.Empty;        &#125;);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用Create创建一个无线序列. 这里没有处理订阅取消,虽然由于Create方法不会出现错误.但是其会在后台一直生成新的数字浪费CPU时间片.</p><p>‍</p><h1 id="part2-处理数据流"><a class="markdownIt-Anchor" href="#part2-处理数据流"></a> Part2 - 处理数据流</h1><blockquote><p>这一章节和<a href="Linq.md">Linq</a>基本重合</p></blockquote><h2 id="过滤器"><a class="markdownIt-Anchor" href="#过滤器"></a> 过滤器</h2><h3 id="ignoreelements"><a class="markdownIt-Anchor" href="#ignoreelements"></a> IgnoreElements()</h3><p>总返回false的Where</p><h3 id="oftype"><a class="markdownIt-Anchor" href="#oftype"></a> OfType()</h3><p>由于<code>Where()</code>的返回类型与输入类型相同.</p><p>当需要过滤类型时,就需要使用<code>OfType()</code>​</p><h3 id="元素位置过滤"><a class="markdownIt-Anchor" href="#元素位置过滤"></a> 元素位置过滤</h3><h4 id="firstasync"><a class="markdownIt-Anchor" href="#firstasync"></a> FirstAsync()</h4><p>返回源序列第一个值,如果没有值就complete了就返回error</p><p>如果想要返回一个默认值使用<code>FirstOrDefaultAsync</code>(但是可能不知道返回的null是第一个值还是结束了返回的默认值)</p><h4 id="take-takelast"><a class="markdownIt-Anchor" href="#take-takelast"></a> Take() TakeLast()</h4><h4 id="lastasync-lastordefaultasync"><a class="markdownIt-Anchor" href="#lastasync-lastordefaultasync"></a> LastAsync() LastOrDefaultAsync()</h4><h4 id="skip-skiplast"><a class="markdownIt-Anchor" href="#skip-skiplast"></a> <strong>Skip() SkipLast()</strong></h4><p>由于在<code>OnComplete()</code>前不知道是不是最后一个,导致从接收到最后一个元素到将其转发给订阅者之间可能会有显著延迟.</p><h4 id="singleasync-singleordefaultasync"><a class="markdownIt-Anchor" href="#singleasync-singleordefaultasync"></a> <strong>SingleAsync() SingleOrDefaultAsync()</strong></h4><p>要求源有且只有一个元素,否则<code>OnError()</code>​</p><h3 id="时间过滤"><a class="markdownIt-Anchor" href="#时间过滤"></a> 时间过滤</h3><h4 id="skipwhile-takewhile"><a class="markdownIt-Anchor" href="#skipwhile-takewhile"></a> SkipWhile() TakeWhile()</h4><h4 id="skipuntil-takeuntil"><a class="markdownIt-Anchor" href="#skipuntil-takeuntil"></a> SkipUntil() TakeUntil()</h4><h4 id="distinct-distinctuntilchanged"><a class="markdownIt-Anchor" href="#distinct-distinctuntilchanged"></a> <strong>Distinct()</strong>  <strong>DistinctUntilChanged</strong>()</h4><p>Distinct 是另一个标准的 LINQ 运算符。它可以从序列中移除重复项。</p><p>这个用来检测状态什么的,牛逼的.</p><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">uint exampleMmsi &#x3D; 235009890;IObservable&lt;IAisMessageType1to3&gt; statusChanges &#x3D; receiverHost.Messages    .Where(v &#x3D;&gt; v.Mmsi &#x3D;&#x3D; exampleMmsi)    .OfType&lt;IAisMessageType1to3&gt;()    .DistinctUntilChanged(m &#x3D;&gt; m.NavigationStatus)    .Skip(1);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>TIPS:</p><ul><li>​<code>Where()</code> 总是会把源的终止通知（<code>OnComplete()</code> 或 <code>OnError()</code>）原样传递下去</li><li>大多数 Rx 运算符本身既不是热的，也不是冷的，它们依赖于其源</li></ul></blockquote><p>‍</p><p>‍</p><p>‍</p><h2 id="序列处理"><a class="markdownIt-Anchor" href="#序列处理"></a> 序列处理</h2><table><thead><tr><th>方法</th><th>输入序列</th><th>输出序列</th><th>特点</th><th>典型用途</th></tr></thead><tbody><tr><td>​<code>Select</code>​</td><td>IEnumerable</td><td>IEnumerable</td><td>一对一映射，投影每个元素</td><td>类型转换、计算新值</td></tr><tr><td>​<code>SelectMany</code>​</td><td>IEnumerable</td><td>IEnumerable</td><td>一对多映射，扁平化集合</td><td>展平嵌套集合</td></tr><tr><td>​<code>Cast</code>​</td><td>IEnumerable</td><td>IEnumerable</td><td>类型转换</td><td>将非泛型集合转换为泛型序列</td></tr></tbody></table><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">var data &#x3D; new List&lt;string[]&gt; &#123; new[] &#123;&quot;a&quot;,&quot;b&quot;&#125;, new[] &#123;&quot;c&quot;&#125; &#125;;&#x2F;&#x2F; Selectvar select &#x3D; data.Select(x &#x3D;&gt; x);&#x2F;&#x2F; select &#x3D; &#123; &#123;&quot;a&quot;,&quot;b&quot;&#125;, &#123;&quot;c&quot;&#125; &#125;  &#x2F;&#x2F; 二维&#x2F;&#x2F; SelectManyvar selectMany &#x3D; data.SelectMany(x &#x3D;&gt; x);&#x2F;&#x2F; selectMany &#x3D; &#123; &quot;a&quot;, &quot;b&quot;, &quot;c&quot; &#125;  &#x2F;&#x2F; 一维&#x2F;&#x2F; CastArrayList list &#x3D; new ArrayList() &#123; 1, 2, 3 &#125;;var cast &#x3D; list.Cast&lt;int&gt;(); &#x2F;&#x2F; cast &#x3D; &#123; 1, 2, 3 &#125; 转为 IEnumerable&lt;int&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="聚合"><a class="markdownIt-Anchor" href="#聚合"></a> 聚合</h2><table><thead><tr><th>方法</th><th>输出</th><th>何时发射结果</th><th>典型用途</th></tr></thead><tbody><tr><td>​<code>Aggregate</code>​</td><td>单个最终值</td><td>序列完成后</td><td>汇总、最终统计</td></tr><tr><td>​<code>Scan</code>​</td><td>累积结果序列</td><td>每接收一个元素就发射</td><td>实时累积、动态指标</td></tr></tbody></table><p>‍</p><h2 id="分区"><a class="markdownIt-Anchor" href="#分区"></a> 分区</h2><table><thead><tr><th>操作</th><th>功能描述</th><th>输出类型</th><th>典型用途</th></tr></thead><tbody><tr><td>​<code>GroupBy</code>​</td><td>根据指定键将元素分组，每个组生成独立子流</td><td>​<code>IObservable&lt;IGroupedObservable&lt;TKey, TElement&gt;&gt;</code>​</td><td>分类数据、分组处理（如按用户、传感器等）</td></tr><tr><td>​<code>Buffer</code>​</td><td>将元素按固定大小或时间间隔收集成列表</td><td>​<code>IObservable&lt;IList&lt;T&gt;&gt;</code>​</td><td>批量处理数据、时间窗口统计</td></tr></tbody></table><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">var dataStream &#x3D; new[]&#123;    (&quot;A&quot;, 1), (&quot;B&quot;, 2), (&quot;A&quot;, 3), (&quot;B&quot;, 4), (&quot;A&quot;, 5), (&quot;B&quot;, 6)&#125;.ToObservable();Console.WriteLine(&quot;&#x3D;&#x3D;&#x3D; GroupBy 示例 &#x3D;&#x3D;&#x3D;&quot;);&#x2F;&#x2F; 按类别分组var grouped &#x3D; dataStream.GroupBy(d &#x3D;&gt; d.Item1);grouped.Subscribe(group &#x3D;&gt;&#123;    Console.WriteLine($&quot;Group &#123;group.Key&#125;:&quot;);    group.Subscribe(d &#x3D;&gt; Console.WriteLine($&quot;  Value: &#123;d.Item2&#125;&quot;));&#125;);Console.WriteLine(&quot;\n&#x3D;&#x3D;&#x3D; Buffer 示例 &#x3D;&#x3D;&#x3D;&quot;);&#x2F;&#x2F; 每 2 个元素收集成一个批次var buffered &#x3D; dataStream.Buffer(2);buffered.Subscribe(batch &#x3D;&gt;&#123;    Console.WriteLine(&quot;Batch: &quot; + string.Join(&quot;, &quot;, batch.Select(b &#x3D;&gt; b.Item2)));&#125;);&#x3D;&#x3D;&#x3D; GroupBy 示例 &#x3D;&#x3D;&#x3D;Group A:  Value: 1  Value: 3  Value: 5Group B:  Value: 2  Value: 4  Value: 6&#x3D;&#x3D;&#x3D; Buffer 示例 &#x3D;&#x3D;&#x3D;Batch: 1, 2Batch: 3, 4Batch: 5, 6<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><h2 id="合并"><a class="markdownIt-Anchor" href="#合并"></a> 合并</h2><table><thead><tr><th>操作</th><th>功能描述</th><th>简单示例代码</th></tr></thead><tbody><tr><td>​<code>Concat</code>​</td><td>先输出第一个序列，再输出第二个序列</td><td>​<code>Observable.Range(1,2).Concat(Observable.Range(3,2)).Subscribe(Console.WriteLine);</code>​</td></tr><tr><td>​<code>Merge</code>​</td><td>将多个序列合并输出，元素交替发射</td><td>​<code>Observable.Range(1,2).Merge(Observable.Range(3,2)).Subscribe(Console.WriteLine);</code>​</td></tr><tr><td>​<code>Zip</code>​</td><td>按顺序配对多个序列的元素，生成元组或投影结果</td><td>​<code>Observable.Range(1,3).Zip(Observable.Range(10,3), (a,b) =&gt; a+b).Subscribe(Console.WriteLine);</code>​</td></tr><tr><td>​<code>CombineLatest</code>​</td><td>任意序列发射新元素时，组合所有序列的最新元素</td><td>​<code>Observable.Interval(TimeSpan.FromSeconds(1)).CombineLatest(Observable.Interval(TimeSpan.FromSeconds(2)), (x,y)=&gt;x+y).Subscribe(Console.WriteLine);</code>​</td></tr></tbody></table><p>‍</p><h1 id="part3-开始务实"><a class="markdownIt-Anchor" href="#part3-开始务实"></a> Part3 - 开始务实</h1><h2 id="调度与线程"><a class="markdownIt-Anchor" href="#调度与线程"></a> 调度与线程</h2><blockquote><p>因为规定:如果源调用了 <code>OnNext</code>，它必须等待该调用返回后，才能再次调用 <code>OnNext</code> 或 <code>OnError</code>/<code>OnCompleted</code>.所以即使每次调用可能来自不同的线程，这些调用在单个订阅上仍然是严格顺序</p></blockquote><blockquote><p>人话:<br />就算每次被调用的上下文不一样.<br />也可以保证调用是一个一个来的而不是同时来n个</p></blockquote><h3 id="大部分rx-操作符没有固定线程它们会在调用到来的线程上执行任务"><a class="markdownIt-Anchor" href="#大部分rx-操作符没有固定线程它们会在调用到来的线程上执行任务"></a> 大部分Rx 操作符没有固定线程，它们会在调用到来的线程上执行任务</h3><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">source    .Where(x &#x3D;&gt; x.MessageType &#x3D;&#x3D; 3)    .Buffer(10)    .Take(20)    .Subscribe(x &#x3D;&gt; Console.WriteLine(x));调用栈(在同一线程):source 调用：  Where 观察者调用：    Buffer 观察者调用：      Take 观察者调用：        Subscribe 观察者调用 lambda<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>但是使用了调度器的操作符如 <code>Delay()</code>不会在调用到来的线程上执行</p></blockquote><h2 id="span-id20250915205602-fm7k3zm-styledisplay-nonespan调度器"><a class="markdownIt-Anchor" href="#span-id20250915205602-fm7k3zm-styledisplay-nonespan调度器"></a> <span id="20250915205602-fm7k3zm" style="display: none;"></span>调度器</h2><blockquote><p>调度器主要有三个职责：</p><ol><li>决定执行工作的上下文（例如，在哪个线程上执行）</li><li>决定何时执行工作（例如立即执行，或延迟执行）</li><li>跟踪时间</li></ol></blockquote><h3 id="接口定义-3"><a class="markdownIt-Anchor" href="#接口定义-3"></a> 接口定义</h3><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">public interface IScheduler&#123;    DateTimeOffset Now &#123; get; &#125; &#x2F;&#x2F; 当前调度器的“当前时间”        IDisposable Schedule&lt;TState&gt;( &#x2F;&#x2F; 立即执行一个任务        TState state,         Func&lt;IScheduler, TState, IDisposable&gt; action);    IDisposable Schedule&lt;TState&gt;( &#x2F;&#x2F; 延迟dueTime后执行任务        TState state,         TimeSpan dueTime,         Func&lt;IScheduler, TState, IDisposable&gt; action);    IDisposable Schedule&lt;TState&gt;( &#x2F;&#x2F; 在绝对时刻执行任务        TState state,         DateTimeOffset dueTime,         Func&lt;IScheduler, TState, IDisposable&gt; action);&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="immediatescheduler"><a class="markdownIt-Anchor" href="#immediatescheduler"></a> ImmediateScheduler</h3><ul><li>直接处理调度,没有其他机制<br />所以当调用接受 <code>TimeSpan</code> 的 <code>Schedule</code> 重载方法时,<code>ImmediateScheduler</code> 会直接<code>Sleep()</code>​</li><li>使用调用线程进行工作</li></ul><h3 id="currentthreadscheduler"><a class="markdownIt-Anchor" href="#currentthreadscheduler"></a> CurrentThreadScheduler</h3><ul><li>升级之处在于: 如何处理新的调度请求<br />调度时产生一个新的task,当前线程有空再处理</li><li>使用调用线程进行工作</li></ul><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">Observable    .Range(1, 5)    .SelectMany(i &#x3D;&gt; Observable.Range(i * 10, 5))    .Subscribe(        m &#x3D;&gt; Console.WriteLine($&quot;Received &#123;m&#125; on thread: &#123;Environment.CurrentManagedThreadId&#125;&quot;));&#x2F;&#x2F; 这里输出顺序不严格<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="eventloopscheduler"><a class="markdownIt-Anchor" href="#eventloopscheduler"></a> EventLoopScheduler</h3><ul><li>与<code>CurrentThreadScheduler</code>不同之处在于: 在自己的独立线程上进行工作</li></ul><h3 id="defaultscheduler"><a class="markdownIt-Anchor" href="#defaultscheduler"></a> DefaultScheduler</h3><ul><li><strong>所有基于时间</strong>的操作符使用的调度器</li><li>在<strong>CLR的线程池</strong>中进行工作</li></ul><h3 id="newthreadscheduler"><a class="markdownIt-Anchor" href="#newthreadscheduler"></a> NewThreadScheduler</h3><ul><li><p>每次调度都会创建一个新线程</p></li><li><p>希望执行一些<strong>长时间运行的工作</strong>时比较有效<br />因为<strong>CLR的线程池是</strong>为<strong>短执行时间</strong>优化的</p></li><li><p>每次订阅都会新建一个线程（比如 Thread 8）。</p></li><li><p>但 <strong>同一个订阅中的所有元素都在那个线程上执行</strong>。</p><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">Observable    .Range(1, 5, NewThreadScheduler.Default)    .Subscribe(static x &#x3D;&gt;        Console.WriteLine($&quot;OnNext &#123;x&#125; (Thread &#123;Environment.CurrentManagedThreadId&#125;)&quot;)    );Observable    .Range(1, 5, NewThreadScheduler.Default)    .Subscribe(static x &#x3D;&gt;        Console.WriteLine($&quot;OnNext &#123;x&#125; (Thread &#123;Environment.CurrentManagedThreadId&#125;)&quot;)    );Observable    .Range(1, 5, NewThreadScheduler.Default)    .Subscribe(static x &#x3D;&gt;        Console.WriteLine($&quot;OnNext &#123;x&#125; (Thread &#123;Environment.CurrentManagedThreadId&#125;)&quot;)    );&#x2F;&#x2F;输出:OnNext 1 (Thread 11)OnNext 2 (Thread 11)OnNext 3 (Thread 11)OnNext 4 (Thread 11)OnNext 5 (Thread 11)OnNext 1 (Thread 12)OnNext 2 (Thread 12)OnNext 3 (Thread 12)OnNext 4 (Thread 12)OnNext 5 (Thread 12)OnNext 1 (Thread 13)OnNext 2 (Thread 13)OnNext 3 (Thread 13)OnNext 4 (Thread 13)OnNext 5 (Thread 13)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="taskpoolscheduler"><a class="markdownIt-Anchor" href="#taskpoolscheduler"></a> TaskPoolScheduler</h3><ul><li>通过Task 线程池来执行工作</li><li>CLR线程池是为了旧代码兼容,尝试用 <strong>TaskPoolScheduler</strong> 替代，特别是在有大量线程池工作时，可能会带来性能收益。</li></ul><blockquote><p>TODO: CLR线程池和TPL线程池</p></blockquote><hr /><table><thead><tr><th>Scheduler</th><th>线程来源 / 执行上下文</th><th>特点 / 使用场景</th><th>备注</th></tr></thead><tbody><tr><td><strong>ImmediateScheduler</strong></td><td>调用线程</td><td>- 调度时直接执行，没有额外机制- <code>Schedule(TimeSpan)</code> 会导致线程 <code>Sleep()</code>​</td><td>非常原始，几乎不用</td></tr><tr><td><strong>CurrentThreadScheduler</strong></td><td>调用线程（但任务排队，延后执行）</td><td>- 调度请求会排队，等当前线程空闲再处理- 防止递归调度导致栈溢出</td><td>输出顺序可能不严格</td></tr><tr><td><strong>EventLoopScheduler</strong></td><td>独立专用线程</td><td>- 所有任务都在它的私有线程上串行执行- 与 CurrentThread 的区别是线程固定独立</td><td>适合希望隔离工作、不影响调用线程的场景</td></tr><tr><td><strong>DefaultScheduler</strong></td><td>CLR 线程池</td><td>- <strong>所有基于时间</strong>的操作符默认使用- 在线程池中并行执行</td><td>线程池针对<strong>短任务</strong>优化</td></tr><tr><td><strong>NewThreadScheduler</strong></td><td>每次订阅新建一个线程</td><td>- 每次订阅都会新开线程（线程 8、11、12…）- 同一订阅中的元素都在同一个线程上</td><td>适合 ​<strong>长时间运行任务</strong>，避免占用线程池</td></tr><tr><td><strong>TaskPoolScheduler</strong></td><td>TPL Task 线程池（基于 <code>Task</code> 的实现）</td><td>- 使用 Task 并行库的线程池- 对大量线程池任务性能可能更好</td><td>推荐用它替代 <code>DefaultScheduler</code>（旧 CLR 线程池）</td></tr></tbody></table><p>‍</p><h2 id="subscribeon-vs-observeon"><a class="markdownIt-Anchor" href="#subscribeon-vs-observeon"></a> SubscribeOn vs. ObserveOn</h2><blockquote><p>建议直接看这个<a href="https://github.com/Vanish0314/Rxxxxxxx/tree/VaniRx">项目</a></p></blockquote><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">Console.WriteLine($&quot;[T:&#123;Environment.CurrentManagedThreadId&#125;] Main thread&quot;);Observable    .Interval(TimeSpan.FromSeconds(1))    .SubscribeOn(new EventLoopScheduler((start) &#x3D;&gt;    &#123;        Thread t &#x3D; new(start) &#123; IsBackground &#x3D; false &#125;;        Console.WriteLine($&quot;[T:&#123;t.ManagedThreadId&#125;] Created thread for EventLoopScheduler&quot;);        return t;    &#125;))    .Subscribe(tick &#x3D;&gt;           Console.WriteLine(            $&quot;[T:&#123;Environment.CurrentManagedThreadId&#125;] &#123;DateTime.Now&#125;: Tick &#123;tick&#125;&quot;));Console.WriteLine($&quot;[T:&#123;Environment.CurrentManagedThreadId&#125;] &#123;DateTime.Now&#125;: Main thread exiting&quot;);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">输出示例:[T:1] Main thread[T:12] Created thread for EventLoopScheduler[T:1] 21&#x2F;07&#x2F;2023 14:57:21: Main thread exiting[T:6] 21&#x2F;07&#x2F;2023 14:57:22: Tick 0[T:6] 21&#x2F;07&#x2F;2023 14:57:23: Tick 1[T:6] 21&#x2F;07&#x2F;2023 14:57:24: Tick 2···<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="原因"><a class="markdownIt-Anchor" href="#原因"></a> 原因:</h3><ul><li>订阅事件使用EventLoopScheduler,在其创建的线程上执行订阅.</li><li>EventLoopScheduler调度Interval调度器.</li><li>Interval使用<strong>默认调度器</strong>,在线程池中执行调用,所以Tick 在6</li><li>所以如果指定Interval使用ImmediateScheduler的话,Interval就会在EventLoopScheduler的线程上执行.</li></ul><h3 id="在发射元素时rx-提供的大多数数据源可以分为三类"><a class="markdownIt-Anchor" href="#在发射元素时rx-提供的大多数数据源可以分为三类"></a> 在发射元素时，Rx 提供的大多数数据源可以分为三类：</h3><ol><li>​<strong>响应上游数据源输入的操作符</strong>（如 <code>Where</code>、<code>Select</code> 或 <code>GroupBy</code>），通常在自身 <code>OnNext</code> 中调用观察者方法。它们调用观察者的上下文与数据源调用 <code>OnNext</code> 的<strong>上下文相同</strong>。</li><li>​<strong>迭代或基于时间生成元素的操作符</strong>，会使用调度器（显式提供的或<strong>默认调度器</strong>）。</li><li><strong>任意上下文生成元素的源</strong>，例如异步方法中使用 <code>await</code> 且指定 <code>ConfigureAwait(false)</code>，在 <code>await</code> 完成后可能在<strong>任意线程上调用</strong> <code>OnNext</code>。</li></ol><h3 id="避免失去对onnext执行上下文的掌控-observeon"><a class="markdownIt-Anchor" href="#避免失去对onnext执行上下文的掌控-observeon"></a> 避免失去对OnNext执行上下文的掌控 - ObserveOn</h3><blockquote><p>比如Unity,你绝对不希望你包含Unity API的函数在非主线程被调用,因为log都没有</p></blockquote><pre class="line-numbers language-c#" data-language="c#"><code class="language-c#">Observable    .Interval(TimeSpan.FromSeconds(1))    .SelectMany(tick &#x3D;&gt; Observable.Return(tick, NewThreadScheduler.Default))    .Subscribe(tick &#x3D;&gt;       Console.WriteLine($&quot;&#123;DateTime.Now&#125;-&#123;Environment.CurrentManagedThreadId&#125;: Tick &#123;tick&#125;&quot;));OUTPUT:Main thread: 121&#x2F;07&#x2F;2023 15:19:56-12: Tick 021&#x2F;07&#x2F;2023 15:19:57-13: Tick 121&#x2F;07&#x2F;2023 15:19:58-14: Tick 221&#x2F;07&#x2F;2023 15:19:59-15: Tick 3...Observable    .Interval(TimeSpan.FromSeconds(1))    .SelectMany(tick &#x3D;&gt; Observable.Return(tick, NewThreadScheduler.Default))    .ObserveOn(new EventLoopScheduler())    .Subscribe(tick &#x3D;&gt;       Console.WriteLine($&quot;&#123;DateTime.Now&#125;-&#123;Environment.CurrentManagedThreadId&#125;: Tick &#123;tick&#125;&quot;));OUTPUT:Main thread: 121&#x2F;07&#x2F;2023 15:24:23-13: Tick 021&#x2F;07&#x2F;2023 15:24:24-13: Tick 121&#x2F;07&#x2F;2023 15:24:25-13: Tick 221&#x2F;07&#x2F;2023 15:24:26-13: Tick 3...<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="结合subscribeon-observeon"><a class="markdownIt-Anchor" href="#结合subscribeon-observeon"></a> 结合<strong>SubscribeOn</strong> <strong>ObserveOn</strong>​</h3><blockquote><ul><li>你可以用 <strong>SubscribeOn</strong> 确保繁重工作不在 主 线程上完成，</li><li>然后用 <strong>ObserveOn</strong> 确保通知回到正确的线程上。</li></ul></blockquote><p>‍</p><h2 id="调度器高级功能"><a class="markdownIt-Anchor" href="#调度器高级功能"></a> 调度器高级功能</h2><blockquote><p>如果你需要编写一个 <strong>自己决定何时产生元素的 observable 源</strong>,那你可能就需要使用这些高级功能。</p></blockquote><h3 id="tstate"><a class="markdownIt-Anchor" href="#tstate"></a> TState</h3><blockquote><p>调度器原封不动传给回调函数,以给回调函数提供上下文.</p><p>虽然更简单的方式是lambda捕获变量,但Rx基本不这么做</p></blockquote><p>​<code>Tstate</code> 参数的设计用途：提供每个工作项所需的状态，从而避免在每次迭代中都捕获变量，减少分配开销。</p><p>TIPS:  ****  <a href="%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B/%E8%B5%84%E6%96%99/INTRODUCTION%20TO%20RX/%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%20-%20%E8%B0%83%E5%BA%A6%E4%B8%8E%E7%BA%BF%E7%A8%8B.md#20250917214049-h17yrzv">就算不需要传入state,也请传入this而不是null,防止隐式调用this</a></p><h3 id="取消调度"><a class="markdownIt-Anchor" href="#取消调度"></a> 取消调度</h3><p>对齐订阅的取消.以Interval为例的无限循环调度.如果没人订阅了,那Interval就该取消调度了.</p><p>‍</p><h2 id="基于时间的序列"><a class="markdownIt-Anchor" href="#基于时间的序列"></a> 基于时间的序列</h2><blockquote><p><strong>时间在 Rx 中始终只是尽力而为（best effort）需要更加严格准确的时间点需要更高级的操作</strong></p></blockquote><h3 id="timestamp-timeinterval"><a class="markdownIt-Anchor" href="#timestamp-timeinterval"></a> Timestamp &amp; TimeInterval</h3><ul><li><strong>Timestamped</strong> 给元素一个到达时间</li><li><strong>TimeInterval</strong> 给元素附加一个 <strong>TimeSpan</strong>（即与上一个元素(第一个元素是和订阅的时间间隔)的时间间隔）</li></ul><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">‍Observable<span class="token punctuation">.</span><span class="token function">Interval</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>          <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>          <span class="token punctuation">.</span><span class="token function">TimeInterval</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token punctuation">.</span><span class="token function">Dump</span><span class="token punctuation">(</span><span class="token string">"TimeInterval"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="delay"><a class="markdownIt-Anchor" href="#delay"></a> Delay</h3><p>值得注意的是, 被延迟的不是订阅本身, 而是<strong>转发</strong>到最终订阅者的过程</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token class-name">IObservable<span class="token punctuation">&lt;</span>Timestamped<span class="token punctuation">&lt;</span><span class="token keyword">long</span><span class="token punctuation">></span><span class="token punctuation">></span></span> source <span class="token operator">=</span> Observable    <span class="token punctuation">.</span><span class="token function">Interval</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">Timestamp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">IObservable<span class="token punctuation">&lt;</span>Timestamped<span class="token punctuation">&lt;</span><span class="token keyword">long</span><span class="token punctuation">></span><span class="token punctuation">></span></span> delay <span class="token operator">=</span> source<span class="token punctuation">.</span><span class="token function">Delay</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>delay<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span><span class="token keyword">value</span> <span class="token operator">=></span>    Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>     <span class="token interpolation-string"><span class="token string">$"Item </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp"><span class="token keyword">value</span><span class="token punctuation">.</span>Value</span><span class="token punctuation">&#125;</span></span><span class="token string"> with timestamp </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp"><span class="token keyword">value</span><span class="token punctuation">.</span>Timestamp</span><span class="token punctuation">&#125;</span></span><span class="token string"> received at </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">DateTimeOffset<span class="token punctuation">.</span>Now</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>   <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"delay Completed"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>所以以上代码输出会发现时间戳与订阅接受时间相差2s</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">Item <span class="token number">0</span> with timestamp <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">20</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span> received at <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">22</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span>Item <span class="token number">1</span> with timestamp <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">21</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span> received at <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">23</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span>Item <span class="token number">2</span> with timestamp <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">22</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span> received at <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">24</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span>Item <span class="token number">3</span> with timestamp <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">23</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span> received at <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">25</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span>Item <span class="token number">4</span> with timestamp <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">24</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span> received at <span class="token number">09</span><span class="token operator">/</span><span class="token number">11</span><span class="token operator">/</span><span class="token number">2023</span> <span class="token number">17</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">:</span><span class="token number">26</span> <span class="token operator">+</span><span class="token number">00</span><span class="token punctuation">:</span><span class="token number">00</span>delay Completed<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="sample采样"><a class="markdownIt-Anchor" href="#sample采样"></a> Sample（采样）</h3><p><strong>Sample 方法</strong> 会按照指定的时间间隔输出值.每次输出时,它都会报告源序列中最后产生的那个值.</p><ul><li><strong>Sample 并不会做任何插值</strong>，它只会返回源中最后一个产生的值。</li><li>如果采样间隔比源产生值的间隔还短，<strong>Sample</strong> 会重复返回同一个值。</li></ul><h3 id="throttle节流"><a class="markdownIt-Anchor" href="#throttle节流"></a> Throttle（节流）</h3><table><thead><tr><th>运算符</th><th>行为</th></tr></thead><tbody><tr><td><strong>Throttle</strong></td><td>保留<strong>安静期后的最后一个值</strong>（防抖的效果）</td></tr><tr><td><strong>Sample</strong></td><td>每隔固定时间取最近一个值（不管有没有事件）</td></tr></tbody></table><h3 id="timeout超时"><a class="markdownIt-Anchor" href="#timeout超时"></a> Timeout（超时）</h3><p>提供一个 <code>TimeSpan</code>，且在该时间段内没有任何值产生，则序列会失败并抛出 <code>TimeoutException</code>​</p><p>​<code>Timeout</code> 还支持在超时时返回<strong>备用序列</strong></p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token comment">// 如果超过 dueTime，则切换到 other 序列</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Timeout</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>    <span class="token keyword">this</span> <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> source<span class="token punctuation">,</span>     <span class="token class-name">TimeSpan</span> dueTime<span class="token punctuation">,</span>     <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> other<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Timeout</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>    <span class="token keyword">this</span> <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> source<span class="token punctuation">,</span>     <span class="token class-name">TimeSpan</span> dueTime<span class="token punctuation">,</span>     <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> other<span class="token punctuation">,</span>     <span class="token class-name">IScheduler</span> scheduler<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Timeout</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>    <span class="token keyword">this</span> <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> source<span class="token punctuation">,</span>     <span class="token class-name">DateTimeOffset</span> dueTime<span class="token punctuation">,</span>     <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> other<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span>  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Timeout</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>    <span class="token keyword">this</span> <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> source<span class="token punctuation">,</span>     <span class="token class-name">DateTimeOffset</span> dueTime<span class="token punctuation">,</span>     <span class="token class-name">IObservable<span class="token punctuation">&lt;</span>TSource<span class="token punctuation">></span></span> other<span class="token punctuation">,</span>     <span class="token class-name">IScheduler</span> scheduler<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><p>‍</p><h2 id="集成rx"><a class="markdownIt-Anchor" href="#集成rx"></a> 集成Rx</h2><blockquote><p>通常最好将所有 Rx 逻辑集中处理，这样与外部世界的交互只需两次：一次输入，一次输出。</p></blockquote><h3 id="async-await"><a class="markdownIt-Anchor" href="#async-await"></a> async &amp; await</h3><ul><li>可以对任何 IObservable&lt;T&gt; 使用 C# 的 await 关键字</li></ul><h3 id="foreachasync"><a class="markdownIt-Anchor" href="#foreachasync"></a> ForEachAsync</h3><h3 id="toenumerable"><a class="markdownIt-Anchor" href="#toenumerable"></a> ToEnumerable</h3><ul><li>源可观察序列将在你开始枚举序列时订阅（即延迟订阅）</li><li>foreach 调用枚举器的 <code>MoveNext</code> 会阻塞，直到源产生一个元素</li><li>如果源报告错误，该错误会被抛出</li></ul><blockquote><p>这意味着在使用 ToEnumerable 时，技术上有可能在同一个线程上同时消费和生成项目，但这依赖于生产者始终领先。这是一种危险的方法，因为如果 foreach 循环赶上了生产者，就会发生死锁</p></blockquote><h3 id="转为单一集合"><a class="markdownIt-Anchor" href="#转为单一集合"></a> 转为单一集合</h3><ul><li><p>等调用了OnComplete后才会转换为单一集合</p></li><li><p>如果源在生成值后出现错误，你将无法接收到任何这些值</p></li></ul><p>APIs:</p><ul><li>ToArray</li><li>ToList</li><li>ToDictionary</li><li>ToLookup</li></ul><h3 id="totask"><a class="markdownIt-Anchor" href="#totask"></a> ToTask</h3><ul><li><p>当任务完成时，该任务的结果就是序列的最终输出</p></li><li><p>如果源序列完成但未产生任何元素，则任务将进入 faulted 状态，并抛出 <code>InvalidOperationException</code>​</p></li><li><p>如果在 observable 序列完成前传入一个取消令牌，它会取消对源的订阅，并将任务置为已取消状态</p></li><li><p>源序列调用 <code>OnError</code>，Rx 会使用提供的异常将任务置于 faulted 状态</p></li></ul><h3 id="toevent"><a class="markdownIt-Anchor" href="#toevent"></a> ToEvent</h3><h4 id="最简单的方式"><a class="markdownIt-Anchor" href="#最简单的方式"></a> 最简单的方式</h4><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> source <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token function">Interval</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> source<span class="token punctuation">.</span><span class="token function">ToEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> result<span class="token punctuation">.</span>OnNext <span class="token operator">+=</span> val <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>​<code>ToEvent</code> 方法返回 <code>IEventSource&lt;T&gt;</code>，它只有一个成员：<code>OnNext</code> 事件。</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventSource<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">&#123;</span>     <span class="token keyword">event</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> OnNext<span class="token punctuation">;</span> <span class="token punctuation">&#125;</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="转换成net事件"><a class="markdownIt-Anchor" href="#转换成net事件"></a> 转换成.Net事件</h4><ul><li>​<code>ToEventPattern</code>​</li></ul><h4 id="为什么应该直接使用rx"><a class="markdownIt-Anchor" href="#为什么应该直接使用rx"></a> <a href="#20250817153337-jeg08gz">为什么应该直接使用Rx</a></h4><p>事件有以下局限性：</p><ul><li>难以组合</li><li>不能作为参数传递或存储在字段中</li><li>难以随时间轻松查询</li><li>没有标准的错误报告模式</li><li>没有标准的序列结束指示模式</li><li>对并发或多线程应用几乎没有帮助</li></ul><h3 id="do-注入副作用"><a class="markdownIt-Anchor" href="#do-注入副作用"></a> ​<code>Do</code> - 注入副作用</h3><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token class-name">IObservable<span class="token punctuation">&lt;</span><span class="token keyword">long</span><span class="token punctuation">></span></span> source <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token function">Interval</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">IObservable<span class="token punctuation">&lt;</span><span class="token keyword">long</span><span class="token punctuation">></span></span> loggedSource <span class="token operator">=</span> source<span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span>    i <span class="token operator">=></span> <span class="token function">Log</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">,</span>    ex <span class="token operator">=></span> <span class="token function">Log</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Log</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>loggedSource<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>    Console<span class="token punctuation">.</span>WriteLine<span class="token punctuation">,</span>    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"completed"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="asobservable-封装"><a class="markdownIt-Anchor" href="#asobservable-封装"></a> <code>AsObservable</code> - 封装</h3><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UltraLeakyLetterRepo</span><span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token return-type class-name">ReplaySubject<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> Letters <span class="token punctuation">&#123;</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token function">UltraLeakyLetterRepo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        Letters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReplaySubject<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"A"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"B"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"C"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这段代码最大的问题: <strong>使用者可以调用</strong> <strong>​<code>OnNext/OnError/OnCompleted</code>​</strong>​</p><p>你可以更改为:</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ObscuredLeakinessLetterRepo</span><span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token return-type class-name">IObservable<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> Letters <span class="token punctuation">&#123;</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token function">ObscuredLeakinessLetterRepo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token class-name"><span class="token keyword">var</span></span> letters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReplaySubject<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"A"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"B"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        letters<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"C"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>Letters <span class="token operator">=</span> letters<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是这里<code>Letters</code>的实际类型仍然是<code>ReplaySubject</code>​</p><p>导致你可以使用以下代码捣乱</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> repo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ObscuredLeakinessLetterRepo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">IObservable<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> good <span class="token operator">=</span> repo<span class="token punctuation">.</span><span class="token function">GetLetters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    good<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>Console<span class="token punctuation">.</span>WriteLine<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 调皮行为</span><span class="token keyword">if</span> <span class="token punctuation">(</span>good <span class="token keyword">is</span> <span class="token class-name">ISubject<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> evil<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 调皮，"1" 不是字母！</span>    evil<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">else</span><span class="token punctuation">&#123;</span>    Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"could not sabotage"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因此最好的方式应该是:</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">this</span><span class="token punctuation">.</span>Letters <span class="token operator">=</span> letters<span class="token punctuation">.</span><span class="token function">AsObservable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>‍</p><h2 id="错误处理"><a class="markdownIt-Anchor" href="#错误处理"></a> 错误处理</h2><h3 id="cathc"><a class="markdownIt-Anchor" href="#cathc"></a> <code>Cathc()</code>​</h3><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Reactive<span class="token punctuation">.</span>Linq</span><span class="token punctuation">;</span><span class="token comment">// 模拟一个可能抛出异常的 Observable</span><span class="token class-name"><span class="token keyword">var</span></span> source <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Create</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>observer <span class="token operator">=></span><span class="token punctuation">&#123;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnError</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"发生错误！"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 不会被执行</span>    <span class="token keyword">return</span> System<span class="token punctuation">.</span>Reactive<span class="token punctuation">.</span>Disposables<span class="token punctuation">.</span>Disposable<span class="token punctuation">.</span>Empty<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 使用 Catch 捕获异常</span><span class="token class-name"><span class="token keyword">var</span></span> handled <span class="token operator">=</span> source<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Catch</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">,</span> Exception<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ex <span class="token operator">=></span><span class="token punctuation">&#123;</span>    Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"捕获异常: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">ex<span class="token punctuation">.</span>Message</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 提供备用 Observable</span>    <span class="token keyword">return</span> Observable<span class="token punctuation">.</span><span class="token function">Return</span><span class="token punctuation">(</span><span class="token number">999</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>handled<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>    x <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"接收到: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">x</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    ex <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"流终止: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">ex<span class="token punctuation">.</span>Message</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"流完成"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name"><span class="token keyword">var</span></span> first <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Throw</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"第一个流出错"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name"><span class="token keyword">var</span></span> second <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token function">Return</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> first<span class="token punctuation">.</span><span class="token function">Catch</span><span class="token punctuation">(</span>second<span class="token punctuation">)</span><span class="token punctuation">;</span>result<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>    x <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"接收到: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">x</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    ex <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"流终止: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">ex<span class="token punctuation">.</span>Message</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"流完成"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>输出:</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">接收到<span class="token punctuation">:</span> <span class="token number">1</span>接收到<span class="token punctuation">:</span> <span class="token number">2</span>接收到<span class="token punctuation">:</span> <span class="token number">3</span>捕获异常<span class="token punctuation">:</span> 发生错误！接收到<span class="token punctuation">:</span> <span class="token number">999</span>流完成接收到<span class="token punctuation">:</span> <span class="token number">42</span>流完成<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="finally"><a class="markdownIt-Anchor" href="#finally"></a> <code>Finally()</code>​</h3><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> source <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Create</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>observer <span class="token operator">=></span><span class="token punctuation">&#123;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnNext</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    observer<span class="token punctuation">.</span><span class="token function">OnError</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"出错了！"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> System<span class="token punctuation">.</span>Reactive<span class="token punctuation">.</span>Disposables<span class="token punctuation">.</span>Disposable<span class="token punctuation">.</span>Empty<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name"><span class="token keyword">var</span></span> handled <span class="token operator">=</span> source<span class="token punctuation">.</span><span class="token function">Finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"流结束，执行 Finally 操作！"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>handled<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>    x <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"接收到: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">x</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    ex <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"流发生异常: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">ex<span class="token punctuation">.</span>Message</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"流正常完成"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>输出</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">接收到<span class="token punctuation">:</span> <span class="token number">1</span>接收到<span class="token punctuation">:</span> <span class="token number">2</span>流发生异常<span class="token punctuation">:</span> 出错了！流结束，执行 Finally 操作！<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="using"><a class="markdownIt-Anchor" href="#using"></a> Using</h3><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">MyResource</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">)</span> <span class="token punctuation">:</span> IDisposable<span class="token punctuation">&#123;</span>    <span class="token keyword">private</span> <span class="token class-name"><span class="token keyword">string</span></span> _name <span class="token operator">=</span> name<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Use</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">_name</span><span class="token punctuation">&#125;</span></span><span class="token string"> 使用中: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp"><span class="token keyword">value</span></span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">_name</span><span class="token punctuation">&#125;</span></span><span class="token string"> 已释放"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">Program</span><span class="token punctuation">&#123;</span>    <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token class-name"><span class="token keyword">var</span></span> observable <span class="token operator">=</span> Observable<span class="token punctuation">.</span><span class="token function">Using</span><span class="token punctuation">(</span>            <span class="token named-parameter punctuation">resourceFactory</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MyResource</span><span class="token punctuation">(</span><span class="token string">"资源A"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token named-parameter punctuation">observableFactory</span><span class="token punctuation">:</span> res <span class="token operator">=></span> Observable<span class="token punctuation">.</span><span class="token function">Range</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span>x <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token punctuation">)</span><span class="token punctuation">;</span>        observable<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>            x <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"接收到: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">x</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>            ex <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"异常: </span><span class="token interpolation"><span class="token punctuation">&#123;</span><span class="token expression language-csharp">ex<span class="token punctuation">.</span>Message</span><span class="token punctuation">&#125;</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"流完成"</span><span class="token punctuation">)</span>        <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>输出</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">资源A 使用中<span class="token punctuation">:</span> <span class="token number">1</span>接收到<span class="token punctuation">:</span> <span class="token number">1</span>资源A 使用中<span class="token punctuation">:</span> <span class="token number">2</span>接收到<span class="token punctuation">:</span> <span class="token number">2</span>资源A 使用中<span class="token punctuation">:</span> <span class="token number">3</span>接收到<span class="token punctuation">:</span> <span class="token number">3</span>流完成资源A 已释放<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>‍</p><p>‍</p><h2 id="测试"><a class="markdownIt-Anchor" href="#测试"></a> 测试</h2><h3 id="testscheduler"><a class="markdownIt-Anchor" href="#testscheduler"></a> TestScheduler</h3><p>​<code>TestScheduler</code>定义了允许我们控制和监控虚拟时间的方法</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestScheduler</span> <span class="token punctuation">:</span> <span class="token comment">// ...</span><span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsEnabled <span class="token punctuation">&#123;</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token return-type class-name">TAbsolute</span> Clock <span class="token punctuation">&#123;</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AdvanceTo</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">long</span></span> time<span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AdvanceBy</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">long</span></span> time<span class="token punctuation">)</span>        <span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>TestScheduler 使用 <strong>TimeSpan.Ticks</strong> 作为时间单位。如果你想让时间前进 1 秒，可以调用：</p><pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">scheduler<span class="token punctuation">.</span><span class="token function">AdvanceBy</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Ticks<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>1 个 tick 对应 100ns，因此 1 秒等于 10,000,000 tick。</p><ul><li><p>​<code>AdvanceTo(long)</code> 方法将虚拟时间设置为指定的 tick 数量</p></li><li><p>​<code>AdvanceBy(long)</code> 方法允许我们将时钟向前移动指定时间量</p></li></ul><p>‍</p><h1 id="part4-最佳实践"><a class="markdownIt-Anchor" href="#part4-最佳实践"></a> Part4 - 最佳实践</h1><ol><li><p><strong>返回序列的成员永远不应返回 null</strong><br />适用于 <strong>IEnumerable</strong> 和 <strong>IObservable</strong> 序列。应返回空序列而非 null。</p></li><li><p><strong>仅在需要提前取消订阅时才调用 Dispose</strong></p></li><li><p><strong>始终提供 OnError 处理程序</strong></p></li><li><p><strong>避免阻塞操作符</strong><br />如 ​<strong>First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ForEach</strong>。<br />可使用非阻塞替代方案，例如 ​<strong>FirstAsync</strong>。</p></li><li><p><strong>避免在 IObservable 与 IEnumerable 之间频繁转换</strong></p></li><li><p><strong>优先使用延迟求值而非立即求值</strong></p></li><li><p><strong>将大型查询拆分成多个部分</strong><br />大型查询的关键标志包括：</p><ul><li>嵌套查询</li><li>查询表达式语法超过 10 行</li><li>使用 <strong>into</strong> 关键字</li></ul></li><li><p><strong>为 Observable 命名规范</strong><br />避免使用模糊变量名，如 <strong>query, q, xs, ys, subject</strong> 等。</p></li><li><p><strong>避免副作用</strong><br />如果无法避免，不要将副作用隐藏在函数式操作符（如 <strong>Select</strong> 或 ​<strong>Where</strong>）的回调中。应使用 <strong>Do</strong> 操作符明确表示副作用。</p></li><li><p><strong>尽可能使用 Observable.Create 定义新的 Rx 源</strong><br />避免直接使用 Subject，除非确实需要。</p></li><li><p><strong>避免自己实现 IObservable 接口</strong><br />使用 ​<strong>Observable.Create</strong>（或必要时使用 Subject）。</p></li><li><p><strong>避免自己实现 IObserver 接口</strong><br />优先使用 <strong>Subscribe</strong> 扩展方法的重载。</p></li><li><p><strong>应用程序应定义并管理并发模型</strong></p></li><li><p><strong>如需调度延迟工作，请使用调度器（Schedulers）</strong></p></li><li><p><strong>SubscribeOn 和 ObserveOn 应始终紧接 Subscribe 方法</strong><br />避免夹在其他操作符中，例如：</p> <pre class="line-numbers language-csharp" data-language="csharp"><code class="language-csharp">source<span class="token punctuation">.</span><span class="token function">SubscribeOn</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>Foo<span class="token punctuation">)</span> <span class="token comment">// 不推荐</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li></ol><h1 id="reference"><a class="markdownIt-Anchor" href="#reference"></a> Reference</h1><ul><li><a href="https://introtorx.com/">Introduction to Rx</a></li><li><a href="https://github.com/Vanish0314/Rxxxxxxx">Rxxxxxxx</a></li><li><a href="https://github.com/Vanish0314/VaniRx">VaniRx</a></li></ul>]]></content>
    
    
    <summary type="html">本文系统介绍Rx.NET响应式编程核心：IObservable/IObserver基础、Subject类型、冷/热源处理、调度器与线程模型、时间操作符及错误处理，并附最佳实践指南，助.NET开发者安全落地Rx。（79字）</summary>
    
    
    
    <category term="编程理论" scheme="http://vanishing.cc/categories/%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/"/>
    
    <category term=".NET" scheme="http://vanishing.cc/categories/%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/NET/"/>
    
    
    <category term=".NET" scheme="http://vanishing.cc/tags/NET/"/>
    
    <category term="C#" scheme="http://vanishing.cc/tags/C/"/>
    
    <category term="Rx" scheme="http://vanishing.cc/tags/Rx/"/>
    
    <category term="Rx.NET" scheme="http://vanishing.cc/tags/Rx-NET/"/>
    
    <category term="Reactive-Extensions" scheme="http://vanishing.cc/tags/Reactive-Extensions/"/>
    
    <category term="Reactive-Programming" scheme="http://vanishing.cc/tags/Reactive-Programming/"/>
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第七章 函子与单子</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B8%83%E7%AB%A0%20%E5%87%BD%E5%AD%90%E4%B8%8E%E5%8D%95%E5%AD%90/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B8%83%E7%AB%A0%20%E5%87%BD%E5%AD%90%E4%B8%8E%E5%8D%95%E5%AD%90/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.744Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第七章-函子与单子"><a class="markdownIt-Anchor" href="#第七章-函子与单子"></a> 第七章 函子与单子</h1><blockquote><ul><li><strong>函子（Functor）</strong> ：对容器中的<strong>值</strong>(<code>Map</code>)</li><li><strong>应用函子（Applicative Functor）</strong> ：将<strong>包裹在容器中的函数</strong>(<code>Apply</code>)<strong>包裹在容器中的值</strong></li><li><strong>单子（Monad）</strong> ：<strong>链式操作</strong></li></ul></blockquote><h1 id="函子"><a class="markdownIt-Anchor" href="#函子"></a> 函子</h1><blockquote><p>一个函数,输入输出类型相同的函数</p></blockquote><h2 id="函子需要遵从的定律"><a class="markdownIt-Anchor" href="#函子需要遵从的定律"></a> 函子需要遵从的定律</h2><ul><li>恒等律<br />相同输出-&gt;相同输出</li><li>结合律<br />组合两个函子map box = 两个函子分别map box</li></ul><h2 id="函子的好处"><a class="markdownIt-Anchor" href="#函子的好处"></a> 函子的好处</h2><ul><li>错误处理</li><li><strong>链式操作</strong></li><li><strong>好看</strong></li><li><strong>鲁棒</strong></li></ul><p>‍</p><h1 id="应用函子applicative-functors"><a class="markdownIt-Anchor" href="#应用函子applicative-functors"></a> 应用函子（Applicative Functors）</h1><h2 id="四大定律"><a class="markdownIt-Anchor" href="#四大定律"></a> 四大定律</h2><ul><li>同态律（Homomorphism law）</li><li>交换律（Interchange law）</li><li>组合律</li></ul><h3 id="同态律"><a class="markdownIt-Anchor" href="#同态律"></a> 同态律</h3><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi><mi>m</mi><mi>a</mi><mi>p</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">)</mo><mo>=</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">pure(x).map(f)=pure(f(x))</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mord">.</span><span class="mord mathnormal">m</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span></span></p><h3 id="交换律"><a class="markdownIt-Anchor" href="#交换律"></a> 交换律</h3><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi>l</mi><mi>y</mi><mo stretchy="false">(</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>=</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">pure(x).apply(pure(f))=pure(f(x))</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mopen">(</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span></span></p><h3 id="结合律"><a class="markdownIt-Anchor" href="#结合律"></a> 结合律</h3><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi>l</mi><mi>y</mi><mo stretchy="false">(</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>g</mi><mtext>∘</mtext><mi>f</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>=</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi>l</mi><mi>y</mi><mo stretchy="false">(</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi>l</mi><mi>y</mi><mo stretchy="false">(</mo><mi>p</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>g</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">pure(x).apply(pure(g∘f))=pure(x).apply(pure(f)).apply(pure(g))</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mopen">(</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord">∘</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mopen">(</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mclose">)</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mopen">(</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span></span></p><blockquote><p>pure: 把一个值装进盒子中</p><p>apply:把一个函数装进盒子中</p><p>(g∘f): g(f)</p></blockquote><h3 id="代码解释"><a class="markdownIt-Anchor" href="#代码解释"></a> 代码解释</h3><h4 id="同态律-2"><a class="markdownIt-Anchor" href="#同态律-2"></a> 同态律</h4><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定义 pure 函数</span></span><br><span class="line"><span class="function">Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt; <span class="title">Pure</span>(<span class="params"><span class="built_in">int</span> x</span>)</span> =&gt; Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt;.Success(x);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义普通函数</span></span><br><span class="line">Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; f = x =&gt; x + <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左边：pure(x).map(f)</span></span><br><span class="line"><span class="keyword">var</span> left = Pure(<span class="number">5</span>).Map(f).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 右边：pure(f(x))</span></span><br><span class="line"><span class="keyword">var</span> right = Pure(f(<span class="number">5</span>)).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证</span></span><br><span class="line">(left.IsSuccess &amp;&amp; right.IsSuccess &amp;&amp; left.Value == right.Value).Dump(); </span><br><span class="line"><span class="comment">// 输出：True</span></span><br></pre></td></tr></table></figure><p>交换律</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// pure(x)</span></span><br><span class="line"><span class="function">Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt; <span class="title">Pure</span>(<span class="params"><span class="built_in">int</span> x</span>)</span> =&gt; Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt;.Success(x);</span><br><span class="line"><span class="comment">// pure(f)</span></span><br><span class="line">Result&lt;Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt;, <span class="built_in">string</span>&gt; PureF(Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; func) =&gt; Result&lt;Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt;, <span class="built_in">string</span>&gt;.Success(func);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义函数</span></span><br><span class="line">Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; f = x =&gt; x * <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左边：pure(x).apply(pure(f))</span></span><br><span class="line"><span class="keyword">var</span> left = Pure(<span class="number">7</span>).Apply(PureF(f)).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 右边：pure(f(x))</span></span><br><span class="line"><span class="keyword">var</span> right = Pure(f(<span class="number">7</span>)).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证</span></span><br><span class="line">(left.IsSuccess &amp;&amp; right.IsSuccess &amp;&amp; left.Value == right.Value).Dump();</span><br><span class="line"><span class="comment">// 输出：True</span></span><br></pre></td></tr></table></figure><p>结合律</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// pure(x)</span></span><br><span class="line"><span class="function">Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt; <span class="title">Pure</span>(<span class="params"><span class="built_in">int</span> x</span>)</span> =&gt; Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt;.Success(x);</span><br><span class="line"><span class="comment">// pure(f)</span></span><br><span class="line">Result&lt;Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt;, <span class="built_in">string</span>&gt; PureF(Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; func) =&gt; Result&lt;Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt;, <span class="built_in">string</span>&gt;.Success(func);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义两个函数</span></span><br><span class="line">Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; f = x =&gt; x + <span class="number">2</span>;</span><br><span class="line">Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; g = x =&gt; x * <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数组合：g ∘ f</span></span><br><span class="line">Func&lt;<span class="built_in">int</span>, <span class="built_in">int</span>&gt; composed = x =&gt; g(f(x));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左边：pure(x).apply(pure(g∘f))</span></span><br><span class="line"><span class="keyword">var</span> left = Pure(<span class="number">4</span>)</span><br><span class="line">.Apply(PureF(composed)).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 右边：pure(x).apply(pure(f)).apply(pure(g))</span></span><br><span class="line"><span class="keyword">var</span> right = Pure(<span class="number">4</span>)</span><br><span class="line">.Apply(PureF(f))</span><br><span class="line">.Apply(PureF(g)).Dump();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证</span></span><br><span class="line">(left.IsSuccess &amp;&amp; right.IsSuccess &amp;&amp; left.Value == right.Value).Dump();</span><br><span class="line"><span class="comment">// 输出：True</span></span><br></pre></td></tr></table></figure><p>‍</p><h2 id="代码示例"><a class="markdownIt-Anchor" href="#代码示例"></a> 代码示例</h2><h3 id="恒等律"><a class="markdownIt-Anchor" href="#恒等律"></a> 恒等律</h3><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function">Book <span class="title">AddPages</span>(<span class="params">Book book, <span class="built_in">int</span> pages</span>)</span> =&gt; <span class="keyword">new</span> Book &#123; Title = book.Title, Pages = book.Pages + pages &#125;;</span><br><span class="line"><span class="function">Book <span class="title">AppendSubtitle</span>(<span class="params">Book book, <span class="built_in">string</span> subtitle</span>)</span> =&gt; <span class="keyword">new</span> Book &#123; Title = <span class="string">$&quot;<span class="subst">&#123;book.Title&#125;</span>: <span class="subst">&#123;subtitle&#125;</span>&quot;</span>, Pages = book.Pages &#125;;</span><br><span class="line"></span><br><span class="line">List&lt;Book&gt; books = <span class="keyword">new</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">new</span> Book &#123; Title = <span class="string">&quot;C# Basics&quot;</span>, Pages = <span class="number">100</span> &#125;,</span><br><span class="line">    <span class="keyword">new</span> Book &#123; Title = <span class="string">&quot;Advanced C#&quot;</span>, Pages = <span class="number">200</span> &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 先对每本书应用 AddPages，然后应用 AppendSubtitle</span></span><br><span class="line"><span class="keyword">var</span> sequentialApplicationResult = books</span><br><span class="line">    .Select(book =&gt; AddPages(book, <span class="number">50</span>))</span><br><span class="line">    .Select(book =&gt; AppendSubtitle(book, <span class="string">&quot;Updated Edition&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 AddPages 和 AppendSubtitle 组合应用于每本书</span></span><br><span class="line"><span class="keyword">var</span> combinedApplicationResult = books</span><br><span class="line">    .Select(book =&gt; AppendSubtitle(AddPages(book, <span class="number">50</span>), <span class="string">&quot;Updated Edition&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 打印结果</span></span><br><span class="line">Console.WriteLine(<span class="string">&quot;books.Select(AddPages).Select(AppendSubtitle): &quot;</span> +</span><br><span class="line">    <span class="built_in">string</span>.Join(<span class="string">&quot;, &quot;</span>, sequentialApplicationResult.Select(b =&gt; b.Title)));</span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="string">&quot;books.Select(book =&gt; AppendSubtitle(AddPages(book, 50))): &quot;</span> +</span><br><span class="line">    <span class="built_in">string</span>.Join(<span class="string">&quot;, &quot;</span>, combinedApplicationResult.Select(b =&gt; b.Title)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出：</span></span><br><span class="line"><span class="comment">// books.Select(AddPages).Select(AppendSubtitle): C# Basics: Updated Edition, Advanced C#: Updated Edition</span></span><br><span class="line"><span class="comment">// books.Select(book =&gt; AppendSubtitle(AddPages(book, 50))): C# Basics: Updated Edition, Advanced C#: Updated Edition</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>‍</p><h3 id="拓展result类型为函子"><a class="markdownIt-Anchor" href="#拓展result类型为函子"></a> 拓展Result类型为函子</h3><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Result</span>&lt;<span class="title">TValue</span>, <span class="title">TError</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> TValue _value;</span><br><span class="line">    <span class="keyword">private</span> TError _error;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">bool</span> IsSuccess &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Result</span>(<span class="params">TValue <span class="keyword">value</span>, TError error, <span class="built_in">bool</span> isSuccess</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _value = <span class="keyword">value</span>;</span><br><span class="line">        _error = error;</span><br><span class="line">        IsSuccess = isSuccess;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> TValue Value</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (!IsSuccess) </span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;无法从失败结果中获取值。&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> _value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> TError Error</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (IsSuccess) </span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;无法从成功结果中获取错误。&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> _error;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Result&lt;TValue, TError&gt; <span class="title">Success</span>(<span class="params">TValue <span class="keyword">value</span></span>)</span> =&gt; </span><br><span class="line">        <span class="keyword">new</span> Result&lt;TValue, TError&gt;(<span class="keyword">value</span>, <span class="literal">default</span>, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Result&lt;TValue, TError&gt; <span class="title">Failure</span>(<span class="params">TError error</span>)</span> =&gt; </span><br><span class="line">        <span class="keyword">new</span> Result&lt;TValue, TError&gt;(<span class="literal">default</span>, error, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Result</span>&lt;<span class="title">TResult</span>, <span class="title">TError</span>&gt; <span class="title">Map</span>&lt;<span class="title">TResult</span>&gt;(<span class="params">Func&lt;TValue, TResult&gt; mapper</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> IsSuccess</span><br><span class="line">            ? Result&lt;TResult, TError&gt;.Success(mapper(_value!))</span><br><span class="line">            : Result&lt;TResult, TError&gt;.Failure(_error!);</span><br><span class="line">    &#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Result</span>&lt;<span class="title">TResult</span>, <span class="title">TError</span>&gt; <span class="title">Apply</span>&lt;<span class="title">TResult</span>&gt;(<span class="params">Result&lt;Func&lt;TValue, TResult&gt;, TError&gt; resultFunc</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (resultFunc.IsSuccess &amp;&amp; <span class="keyword">this</span>.IsSuccess)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> func = resultFunc.Value;    <span class="comment">// 取出函数</span></span><br><span class="line">        <span class="keyword">var</span> <span class="keyword">value</span> = <span class="keyword">this</span>.Value;          <span class="comment">// 取出当前值</span></span><br><span class="line">        <span class="keyword">var</span> result = func(<span class="keyword">value</span>);        <span class="comment">// 应用函数到值</span></span><br><span class="line">        <span class="keyword">return</span> Result&lt;TResult, TError&gt;.Success(result);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 有一边失败，返回失败（优先返回函数容器的错误，否则返回自己的错误）</span></span><br><span class="line">        <span class="keyword">var</span> error = resultFunc.IsSuccess ? <span class="keyword">this</span>._error : resultFunc.Error;</span><br><span class="line">        <span class="keyword">return</span> Result&lt;TResult, TError&gt;.Failure(error);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用示例"><a class="markdownIt-Anchor" href="#使用示例"></a> 使用示例</h3><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function">Book <span class="title">AddPages</span>(<span class="params">Book book, <span class="built_in">int</span> pages</span>)</span> =&gt; <span class="keyword">new</span> Book &#123; Title = book.Title, Pages = book.Pages + pages &#125;;</span><br><span class="line"><span class="function">Book <span class="title">AppendSubtitle</span>(<span class="params">Book book, <span class="built_in">string</span> subtitle</span>)</span> =&gt; <span class="keyword">new</span> Book &#123; Title = <span class="string">$&quot;<span class="subst">&#123;book.Title&#125;</span>: <span class="subst">&#123;subtitle&#125;</span>&quot;</span>, Pages = book.Pages &#125;;</span><br><span class="line">Func&lt;Book, Book&gt; identity = book =&gt; book;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> success = Result&lt;Book, <span class="built_in">string</span>&gt;.Success(<span class="keyword">new</span> Book &#123; Title = <span class="string">&quot;C# Basics&quot;</span>, Pages = <span class="number">100</span> &#125;);</span><br><span class="line"><span class="keyword">var</span> error = Result&lt;Book, <span class="built_in">string</span>&gt;.Failure(<span class="string">&quot;Error message&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 恒等律（Identity law）</span></span><br><span class="line"><span class="keyword">var</span> successAfterIdentity = success.Map(identity);</span><br><span class="line"><span class="comment">// successAfterIdentity 应该持有 &quot;C# Basics&quot;，100 页</span></span><br><span class="line"><span class="keyword">var</span> errorAfterIdentity = error.Map(identity);</span><br><span class="line"><span class="comment">// errorAfterIdentity 应该保持 &quot;Error message&quot; 错误</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 组合律（Composition law）</span></span><br><span class="line">Func&lt;Book, Book&gt; composedFunction = book =&gt; </span><br><span class="line">    AppendSubtitle(AddPages(book, <span class="number">50</span>), <span class="string">&quot;Updated Edition&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> success = Result&lt;Book, <span class="built_in">string</span>&gt;.Success(<span class="keyword">new</span> Book &#123; Title = <span class="string">&quot;C# Basics&quot;</span>, Pages = <span class="number">100</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 直接应用组合函数</span></span><br><span class="line"><span class="keyword">var</span> directComposition = success.Map(composedFunction);</span><br><span class="line"><span class="comment">// directComposition 应该持有值 &quot;C# Basics: Updated Edition&quot;，150 页</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 逐步应用函数</span></span><br><span class="line"><span class="keyword">var</span> stepwiseComposition = success</span><br><span class="line">    .Map(book =&gt; AddPages(book, <span class="number">50</span>))</span><br><span class="line">    .Map(book =&gt; AppendSubtitle(book, <span class="string">&quot;Updated Edition&quot;</span>));</span><br><span class="line"><span class="comment">// stepwiseComposition 也应该持有值 &quot;C# Basics: Updated Edition&quot;，150 页</span></span><br></pre></td></tr></table></figure><p>‍</p><p>‍</p><p>‍</p><h1 id="单子-monads"><a class="markdownIt-Anchor" href="#单子-monads"></a> 单子 (Monads)</h1><h2 id="单子核心-bind"><a class="markdownIt-Anchor" href="#单子核心-bind"></a> 单子核心 - Bind</h2><p>​<code>Bind</code>: 返回一个扁平的结果,防止Result&lt;Result&lt;…&gt;&gt;</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Result</span>&lt;<span class="title">TResult</span>, <span class="title">TError</span>&gt; <span class="title">Bind</span>&lt;<span class="title">TResult</span>&gt;</span></span><br><span class="line"><span class="function">(<span class="params">Func&lt;TValue, Result&lt;TResult, TError&gt;&gt; func</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> IsSuccess ? func(_value!) </span><br><span class="line"> : Result&lt;TResult, TError&gt;.Failure(_error!);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用示例:</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function">Result&lt;Manuscript, <span class="built_in">string</span>&gt; <span class="title">FetchManuscript</span>(<span class="params"><span class="built_in">int</span> manuscriptId</span>)</span> &#123; ... &#125;</span><br><span class="line"><span class="function">Result&lt;EditedManuscript, <span class="built_in">string</span>&gt; <span class="title">EditManuscript</span>(<span class="params">Manuscript manuscript</span>)</span> &#123; ... &#125;</span><br><span class="line"><span class="function">Result&lt;FormattedManuscript, <span class="built_in">string</span>&gt; <span class="title">FormatManuscript</span>(<span class="params">EditedManuscript edited</span>)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> manuscriptId = <span class="number">101</span>;</span><br><span class="line"><span class="keyword">var</span> publishingPipeline = FetchManuscript(manuscriptId)</span><br><span class="line">    .Bind(EditManuscript)</span><br><span class="line">    .Bind(FormatManuscript);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="单子定律"><a class="markdownIt-Anchor" href="#单子定律"></a> 单子定律</h2><h3 id="left-identity-左相等"><a class="markdownIt-Anchor" href="#left-identity-左相等"></a> Left Identity (左相等)</h3><p>包装一个函数Bind = 直接应用函数</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">Func&lt;<span class="built_in">int</span>, Result&lt;<span class="built_in">double</span>, <span class="built_in">string</span>&gt;&gt; calculateRoyalties = </span><br><span class="line">sales =&gt; <span class="keyword">new</span> Result&lt;<span class="built_in">double</span>, <span class="built_in">string</span>&gt;(sales * <span class="number">0.15</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">int</span> bookSales = <span class="number">100</span>;</span><br><span class="line"><span class="keyword">var</span> leftIdentity = Result&lt;<span class="built_in">int</span>, <span class="built_in">string</span>&gt;.Success(bookSales).Bind(calculateRoyalties);</span><br><span class="line"><span class="keyword">var</span> directApplication = calculateRoyalties(bookSales);</span><br><span class="line"><span class="comment">// leftIdentity 应该等于 directApplication</span></span><br></pre></td></tr></table></figure><h3 id="right-identity右相等"><a class="markdownIt-Anchor" href="#right-identity右相等"></a> Right Identity(右相等)</h3><p>包装一个值 = Bind里包装一个值</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> manuscriptResult = Result&lt;Manuscript, <span class="built_in">string</span>&gt;.Success(<span class="keyword">new</span> Manuscript());</span><br><span class="line"><span class="keyword">var</span> rightIdentity = manuscriptResult.Bind(manuscript =&gt; Result&lt;Manuscript, <span class="built_in">string</span>&gt;.Success(manuscript));</span><br><span class="line"><span class="comment">// rightIdentity 应该等于 manuscriptResult</span></span><br></pre></td></tr></table></figure><h3 id="结合律-2"><a class="markdownIt-Anchor" href="#结合律-2"></a> 结合律</h3><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> associativity1 = FetchManuscript(manuscriptId)</span><br><span class="line">.Bind(EditManuscript)</span><br><span class="line">.Bind(FormatManuscript);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> associativity2 = FetchManuscript(manuscriptId)</span><br><span class="line">.Bind(manuscript =&gt; </span><br><span class="line">EditManuscript(manuscript)</span><br><span class="line">.Bind(FormatManuscript)</span><br><span class="line">);</span><br><span class="line"><span class="comment">// associativity1 应该等于 associativity2</span></span><br></pre></td></tr></table></figure><p>‍</p><h1 id="exercises"><a class="markdownIt-Anchor" href="#exercises"></a> Exercises</h1><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第七章-函子与单子&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第七章-函子与单子&quot;&gt;&lt;/a&gt; 第七章 函子与单子&lt;/h1&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;函子（Functor）&lt;/strong&gt; ：对容</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第三章 纯函数与副作用</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B8%89%E7%AB%A0%20%E7%BA%AF%E5%87%BD%E6%95%B0%E4%B8%8E%E5%89%AF%E4%BD%9C%E7%94%A8/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B8%89%E7%AB%A0%20%E7%BA%AF%E5%87%BD%E6%95%B0%E4%B8%8E%E5%89%AF%E4%BD%9C%E7%94%A8/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.749Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第三章-纯函数与副作用"><a class="markdownIt-Anchor" href="#第三章-纯函数与副作用"></a> 第三章 纯函数与副作用</h1><h1 id="span-id20250424190437-a4zywcp-styledisplay-nonespan纯函数"><a class="markdownIt-Anchor" href="#span-id20250424190437-a4zywcp-styledisplay-nonespan纯函数"></a> <span id="20250424190437-a4zywcp" style="display: none;"></span>纯函数</h1><p>‍</p><p>纯函数:</p><ul><li><p>输出确定(相同输入-&gt;相同输出)</p></li><li><p>没有副作用</p></li><li><p>c#实现: immutability, readonly, const, static, [Pure]</p></li></ul><p>‍</p><p>示例</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;<span class="built_in">string</span>&gt; <span class="title">GetTitlesOfGenre</span>(<span class="params">List&lt;Book&gt; books, <span class="built_in">string</span> genre</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> books</span><br><span class="line">.Where(b =&gt; b.Genre == genre)</span><br><span class="line">.Select(b =&gt; b.Title)</span><br><span class="line">.ToList();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><p>纯函数的优点</p><ul><li>容易测试(不需要模拟环境,只要给输入就行)</li><li>可复用性高</li><li>容易调试(有问题只有可能是逻辑问题)</li></ul><p>‍</p><p>示例</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Dictionary&lt;TowerType, <span class="built_in">double</span>&gt; _damageModifiers = <span class="keyword">new</span>()</span><br><span class="line">&#123;</span><br><span class="line">&#123;TowerType.Cannon, <span class="number">0.8</span>&#125;, <span class="comment">// Takes 20% less damage from cannon towers</span></span><br><span class="line">&#123;TowerType.Laser, <span class="number">0.9</span>&#125; <span class="comment">// Takes 10% less damage from laser towers</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 非纯函数</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">double</span> <span class="title">CalculateDamageFromTower</span>(<span class="params">Tower tower</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> tower.BaseDamage * _damageModifiers[tower.Type];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//纯函数</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">double</span> <span class="title">CalculateDamageFromTower</span>(<span class="params">Tower tower,</span></span></span><br><span class="line"><span class="params"><span class="function">Dictionary&lt;TowerType, <span class="built_in">double</span>&gt; damageModifiers</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> tower.BaseDamage * damageModifiers[tower.Type];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h1 id="span-id20250424191224-4vhwzbd-styledisplay-nonespan副作用"><a class="markdownIt-Anchor" href="#span-id20250424191224-4vhwzbd-styledisplay-nonespan副作用"></a> <span id="20250424191224-4vhwzbd" style="display: none;"></span>副作用</h1><p>‍</p><p>常见副作用例子:</p><ul><li>修改全局或静态变量</li><li>改变函数参数的原始值</li><li>执行输入/输出操作</li><li>抛出异常</li></ul><p>‍</p><p>副作用后果:</p><ul><li>行为不再单一</li><li>更难调试</li><li>并发问题: 如果多个线程同时修改共享状态…</li></ul><p>‍</p><p>控制与减少副作用的方法</p><ol><li><p>Immutability</p><ul><li>使用 record和 with​</li><li>使用<code>readonly</code>和<code>const</code>​</li></ul></li><li><p>隔离副作用</p><p>无法避免副作用时，关键在于将它们<strong>隔离开来</strong>。例如，如果一个函数必须写入文件，那它应该只做这件事。而所有其他逻辑应尽可能放在纯函数中处理。</p><p>这样做的目的是让副作用变得<strong>可控、可见且易于管理</strong>。</p></li></ol><h1 id="prue"><a class="markdownIt-Anchor" href="#prue"></a> <code>[Prue]</code>​</h1><p>‍</p><p><span id="20250424193311-fe6hsrx" style="display: none;"></span>​<code>[Pure]</code>​</p><ul><li>声明性的标签，用来指示某个函数是纯的</li><li>​<code>Pure</code> 特性主要用于<strong>代码契约和静态分析工具</strong>。</li><li><strong>运行时和编译器并不会强制</strong>函数保持纯净，<code>Pure</code> 也不会改变方法本身的行为。</li></ul><p>‍</p><p>使用 <code>Pure</code> 属性时的注意事项：</p><ul><li>它<strong>不强制</strong>函数纯净：你即使标记了，也可能写出带副作用的代码。</li><li><strong>不能用于</strong> <strong>​<code>void</code>​</strong> <strong>方法</strong>：纯函数必须有返回值。</li><li>它<strong>不影响运行时行为</strong>：它只是一个标记，不会改变函数在执行时的实际表现</li></ul><p>‍</p><p>为什么要使用 <code>Pure</code> 属性？</p><ul><li><strong>可读性</strong></li><li><strong>支持工具检测</strong>：一些静态分析工具（比如 Code Contracts）可以利用该属性发现潜在问题。</li><li><strong>潜在优化</strong>：虽然 C# 编译器目前不对纯函数做特殊优化，但在其他语言或未来的场景中，这类标记可以被编译器用于进一步优化。</li></ul><p>‍</p><h1 id="problems-solves"><a class="markdownIt-Anchor" href="#problems-solves"></a> Problems &amp; Solves</h1>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第三章-纯函数与副作用&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第三章-纯函数与副作用&quot;&gt;&lt;/a&gt; 第三章 纯函数与副作用&lt;/h1&gt;
&lt;h1 id=&quot;span-id20250424190437-a4zywcp-styledispl</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第五章 错误处理</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%BA%94%E7%AB%A0%20%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%BA%94%E7%AB%A0%20%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.761Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第五章-错误处理"><a class="markdownIt-Anchor" href="#第五章-错误处理"></a> 第五章 错误处理</h1><blockquote><p>传统派: 相信后人的智慧</p><p>函数式: 承认每一步都会失败</p><p>‍</p><p><strong>传统异常处理是“出问题 -&gt;</strong>  <strong>跳出去 -&gt;</strong>  <strong>另想办法”</strong></p><p><strong>函数式错误处理是“每步都承认有可能失败，提前规划好”</strong></p></blockquote><h1 id="传统派-try-catch"><a class="markdownIt-Anchor" href="#传统派-try-catch"></a> 传统派 - try catch</h1><p>具体移步-&gt; c#错误处理</p><p>‍</p><h1 id="result-类型"><a class="markdownIt-Anchor" href="#result-类型"></a> Result 类型</h1><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 传统派</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Product <span class="title">GetProduct</span>(<span class="params"><span class="built_in">int</span> id</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> product = _productRepository.Get(id);</span><br><span class="line">    <span class="keyword">if</span> (product <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> ProductNotFoundException(<span class="string">$&quot;找不到ID为 <span class="subst">&#123;id&#125;</span> 的产品。&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> product;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Result</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Result&lt;Product, <span class="built_in">string</span>&gt; <span class="title">GetProduct</span>(<span class="params"><span class="built_in">int</span> id</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> product = _productRepository.Get(id);</span><br><span class="line">    <span class="keyword">if</span> (product <span class="keyword">is</span> <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Result&lt;Product, <span class="built_in">string</span>&gt;.Fail(<span class="string">$&quot;找不到ID为 <span class="subst">&#123;id&#125;</span> 的产品。&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result&lt;Product, <span class="built_in">string</span>&gt;.Success(product);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><p>一个通用的Result类型示例</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Result</span>&lt;<span class="title">T</span>, <span class="title">E</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> T _value;</span><br><span class="line">    <span class="keyword">private</span> E _error;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">bool</span> IsSuccess &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Result</span>(<span class="params">T <span class="keyword">value</span>, E error, <span class="built_in">bool</span> isSuccess</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _value = <span class="keyword">value</span>;</span><br><span class="line">        _error = error;</span><br><span class="line">        IsSuccess = isSuccess;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T Value</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (!IsSuccess)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;无法从失败的结果中获取 Value。&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> _value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> E Error</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (IsSuccess)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;无法从成功的结果中获取 Error。&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> _error;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Result&lt;T, E&gt; <span class="title">Success</span>(<span class="params">T <span class="keyword">value</span></span>)</span> =&gt; <span class="keyword">new</span> Result&lt;T, E&gt;(<span class="keyword">value</span>, <span class="literal">default</span>, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Result&lt;T, E&gt; <span class="title">Fail</span>(<span class="params">E error</span>)</span> =&gt; <span class="keyword">new</span> Result&lt;T, E&gt;(<span class="literal">default</span>, error, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Result</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">T</span>, <span class="title">string</span>&gt; <span class="title">Success</span>&lt;<span class="title">T</span>&gt;(<span class="params">T <span class="keyword">value</span></span>)</span> =&gt; <span class="keyword">new</span> Result&lt;T, <span class="built_in">string</span>&gt;(<span class="keyword">value</span>, <span class="literal">default</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">T</span>, <span class="title">string</span>&gt; <span class="title">Fail</span>&lt;<span class="title">T</span>&gt;(<span class="params"><span class="built_in">string</span> error</span>)</span> =&gt; <span class="keyword">new</span> Result&lt;T, <span class="built_in">string</span>&gt;(<span class="literal">default</span>, error, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><p>​<code>Result</code> 类型从根本上改变了我们看待错误的方式：<br />错误不再是突如其来的中断，而是被预期并合理处理的结果</p><p>‍</p><h1 id="面向铁路的编程railway-oriented-programmingrop"><a class="markdownIt-Anchor" href="#面向铁路的编程railway-oriented-programmingrop"></a> 面向铁路的编程（Railway-Oriented Programming，ROP）</h1><p>‍</p><p>ROP 是一种错误处理方式</p><p>ROP有两条轨道: 成功,失败</p><p>程序沿着成功轨道行进,发生错误切换到失败轨道,一路跳过.</p><p>ROP解决try catch将错误天女散花的问题</p><p>‍</p><h2 id="rop-核心-bind"><a class="markdownIt-Anchor" href="#rop-核心-bind"></a> ROP 核心 - Bind</h2><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">Tout</span>, <span class="title">E</span>&gt; <span class="title">Bind</span>&lt;<span class="title">Tin</span>, <span class="title">Tout</span>, <span class="title">E</span>&gt;(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">this</span> Result&lt;Tin, E&gt; input,</span></span></span><br><span class="line"><span class="params"><span class="function">    Func&lt;Tin, Result&lt;Tout, E&gt;&gt; bindFunc</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> input.IsSuccess</span><br><span class="line">        ? bindFunc(input.Value)</span><br><span class="line">        : Result&lt;Tout, E&gt;.Fail(input.Error);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><p>这样,我们就可以搭建处理铁路</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Result&lt;<span class="built_in">bool</span>, <span class="built_in">string</span>&gt; <span class="title">HandleData</span>(<span class="params"><span class="built_in">string</span> input</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> ParseInput(input)</span><br><span class="line">        .Bind(parsedData =&gt; ValidateData(parsedData))</span><br><span class="line">        .Bind(validData =&gt; TransformData(validData))</span><br><span class="line">        .Bind(transformedData =&gt; StoreData(transformedData));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h1 id="bind-拓展"><a class="markdownIt-Anchor" href="#bind-拓展"></a> Bind 拓展</h1><p>‍</p><h2 id="异步支持"><a class="markdownIt-Anchor" href="#异步支持"></a> 异步支持</h2><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">async</span> <span class="title">Task</span>&lt;<span class="title">Result</span>&lt;<span class="title">TOut</span>, <span class="title">E</span>&gt;&gt; <span class="title">BindAsync</span>&lt;<span class="title">TIn</span>, <span class="title">TOut</span>, <span class="title">E</span>&gt;(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">this</span> Result&lt;TIn, E&gt; input,</span></span></span><br><span class="line"><span class="params"><span class="function">    Func&lt;TIn, Task&lt;Result&lt;TOut, E&gt;&gt;&gt; bindFuncAsync</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> input.IsSuccess </span><br><span class="line">        ? <span class="keyword">await</span> bindFuncAsync(input.Value) </span><br><span class="line">        : Result&lt;TOut, E&gt;.Fail(input.Error);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h2 id="错误映射"><a class="markdownIt-Anchor" href="#错误映射"></a> 错误映射</h2><p>我觉得不需要🙅</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">TOut</span>, <span class="title">EOut</span>&gt; <span class="title">Bind</span>&lt;<span class="title">TIn</span>, <span class="title">TOut</span>, <span class="title">EIn</span>, <span class="title">EOut</span>&gt;(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">this</span> Result&lt;TIn, EIn&gt; input,</span></span></span><br><span class="line"><span class="params"><span class="function">    Func&lt;TIn, Result&lt;TOut, EOut&gt;&gt; bindFunc,</span></span></span><br><span class="line"><span class="params"><span class="function">    Func&lt;EIn, EOut&gt; errorMap</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> input.IsSuccess </span><br><span class="line">        ? bindFunc(input.Value) </span><br><span class="line">        : Result&lt;TOut, EOut&gt;.Fail(errorMap(input.Error));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h2 id="tips"><a class="markdownIt-Anchor" href="#tips"></a> Tips</h2><p>可以通过包装传统派代码，来支持ROP.</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">T</span>, <span class="title">Exception</span>&gt; <span class="title">TryExecute</span>&lt;<span class="title">T</span>&gt;(<span class="params">Func&lt;T&gt; action</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">try</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.Success(action());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (Exception ex)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.Fail&lt;T, Exception&gt;(ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">Result</span>&lt;<span class="title">T</span>, <span class="title">E</span>&gt; <span class="title">SafelyExecute</span>&lt;<span class="title">T</span>, <span class="title">E</span>&gt;(<span class="params">Func&lt;T&gt; function, E error</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">try</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.Success(function());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.Fail&lt;T, E&gt;(error);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a class="markdownIt-Anchor" href="#最佳实践"></a> 最佳实践</h2><ol><li>避免使用 null，而是使用 Option</li><li>ROP时,使用委托来进行Log减少副作用</li><li>阻止try catch(使用SafelyExecute)</li><li>尽量回退 (Fallback)而不是Fail</li></ol><p>‍</p><h2 id="千万别"><a class="markdownIt-Anchor" href="#千万别"></a> 千万别</h2><ol><li>混用异常和<code>Result</code>​</li><li>错误信息不明确</li><li>直接取 Result 的值而不检查是否成功</li><li>自定义错误类型细粒度过细</li></ol><p>‍</p><h2 id="exercises"><a class="markdownIt-Anchor" href="#exercises"></a> Exercises</h2><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第五章-错误处理&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第五章-错误处理&quot;&gt;&lt;/a&gt; 第五章 错误处理&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;传统派: 相信后人的智慧&lt;/p&gt;
&lt;p&gt;函数式: 承认每一步都会失败&lt;/p&gt;
&lt;p</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第二章 Expressions &amp;&amp; Statements</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%BA%8C%E7%AB%A0%20Expressions%20&amp;&amp;%20Statements/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%BA%8C%E7%AB%A0%20Expressions%20&amp;&amp;%20Statements/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.757Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第二章-expressions-statements"><a class="markdownIt-Anchor" href="#第二章-expressions-statements"></a> 第二章 Expressions &amp;&amp; Statements</h1><blockquote><p>Expressions: 表达式</p><p>Statements: 语句</p></blockquote><h1 id="表达式和语句的区别"><a class="markdownIt-Anchor" href="#表达式和语句的区别"></a> 表达式和语句的区别</h1><p>表达式: 有返回值</p><p>语句: 执行操作</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Expression</span></span><br><span class="line"><span class="number">20</span></span><br><span class="line">pagesPerChapter</span><br><span class="line">pagesPerChapter * <span class="number">10</span></span><br><span class="line">Math.Max(a, b)</span><br><span class="line"><span class="comment">//Statement</span></span><br><span class="line"><span class="keyword">var</span> pagesPerChapter = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">var</span> totalBookPages = pagesPerChapter * <span class="number">10</span>;</span><br><span class="line">Console.WriteLine(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (x &gt; <span class="number">0</span>) &#123; ... &#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123; ... &#125;</span><br><span class="line"><span class="comment">//每个表达式都可以成为语句(加上分号)</span></span><br><span class="line">x + <span class="number">2</span>; <span class="comment">// 语句形式，但它什么都不做</span></span><br></pre></td></tr></table></figure><p>‍</p><h1 id="将语句转换为表达式的技巧"><a class="markdownIt-Anchor" href="#将语句转换为表达式的技巧"></a> 将语句转换为表达式的技巧</h1><ul><li>用 LINQ 替代集合操作语句</li><li>用三元运算符（?:）代替 if-else</li><li>用递归代替循环（for、while、foreach）</li></ul><p>Yagur Alex - Functional Programming with C - 2024.pdf - p32 - Techniques to convert statements to expressions</p><p>‍</p><p>Linq</p><table><thead><tr><th>功能</th><th>方法名</th><th>示例</th></tr></thead><tbody><tr><td>过滤</td><td>​<code>Where</code>​</td><td>​<code>books.Where(b =&gt; b.Pages &gt; 200)</code>​</td></tr><tr><td>排序</td><td>​<code>OrderBy</code> / <code>ThenBy</code>​</td><td>​<code>books.OrderBy(b =&gt; b.Title)</code>​</td></tr><tr><td>映射</td><td>​<code>Select</code>​</td><td>​<code>books.Select(b =&gt; b.Title.Length)</code>​</td></tr><tr><td>聚合</td><td>​<code>Aggregate</code>​</td><td>​<code>nums.Aggregate((a, b) =&gt; a + b)</code>​</td></tr><tr><td>平铺</td><td>​<code>SelectMany</code>​</td><td>​<code>authors.SelectMany(a =&gt; a.Books)</code>​</td></tr><tr><td>平均</td><td>​<code>Average</code>​</td><td>​<code>books.Average(b =&gt; b.Title.Length)</code>​</td></tr></tbody></table><p>‍</p><h1 id="表达式树"><a class="markdownIt-Anchor" href="#表达式树"></a> 表达式树</h1><p>表达式树提供了在运行时进行操作的能力</p><p>LINQ的底层就是表达式树</p><p>‍</p><p>表达式树是一种数据结构，它以树状格式表示某些代码，其中每个节点都是一个表达式。表达式树是通过lambda 表达式构建的，并允许你将 lambda 表达式中的代码视为数据进行检查</p><p>‍</p><h1 id="tasks-and-solves"><a class="markdownIt-Anchor" href="#tasks-and-solves"></a> Tasks and Solves</h1><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第二章-expressions-statements&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第二章-expressions-statements&quot;&gt;&lt;/a&gt; 第二章 Expressions &amp;amp;&amp;amp; Statemen</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第九章 柯里化</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B9%9D%E7%AB%A0%20%E6%9F%AF%E9%87%8C%E5%8C%96/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E4%B9%9D%E7%AB%A0%20%E6%9F%AF%E9%87%8C%E5%8C%96/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.753Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第九章-柯里化"><a class="markdownIt-Anchor" href="#第九章-柯里化"></a> 第九章 柯里化</h1><h1 id="什么是柯里化"><a class="markdownIt-Anchor" href="#什么是柯里化"></a> 什么是柯里化</h1><blockquote><p>DDDD</p></blockquote><h1 id="柯里化实践"><a class="markdownIt-Anchor" href="#柯里化实践"></a> 柯里化实践</h1><ol><li><p>识别函数:</p><ol><li>有多个参数</li><li>似乎常只使用部分参数</li><li>参数上天然可以分阶段传入</li></ol></li><li><p>定义函数:</p><ol><li>将转化为一系列嵌套的<strong>单参数函数</strong>。每个函数返回另一个函数，直到所有参数都被传入</li></ol></li></ol><p>‍</p><p>柯里化会导致一些繁琐,但在一些情况下,这会非常实用</p><p>如配置</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Func&lt;NotificationType, Func&lt;<span class="built_in">int</span>, Action&lt;<span class="built_in">string</span>&gt;&gt;&gt;</span><br><span class="line">CurryNotificationConfig()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> notificationType =&gt; maxNotificationsPerMinute =&gt; recipientEmail =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;配置 <span class="subst">&#123;notificationType&#125;</span> 通知：收件人 <span class="subst">&#123;recipientEmail&#125;</span>，每分钟最多 <span class="subst">&#123;maxNotificationsPerMinute&#125;</span> 条&quot;</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> configureNotifications = CurryNotificationConfig();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 邮件通知配置</span></span><br><span class="line"><span class="keyword">var</span> configureEmailNotifications = configureNotifications(NotificationType.Email);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为普通用户设置每分钟 10 条</span></span><br><span class="line"><span class="keyword">var</span> configureUserNotifications = configureEmailNotifications(<span class="number">10</span>);</span><br><span class="line">configureUserNotifications(<span class="string">&quot;alice@example.com&quot;</span>);</span><br><span class="line">configureUserNotifications(<span class="string">&quot;bob@example.com&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为版主设置每分钟 50 条</span></span><br><span class="line"><span class="keyword">var</span> configureModeratorNotifications = configureEmailNotifications(<span class="number">50</span>);</span><br><span class="line">configureModeratorNotifications(<span class="string">&quot;moderator1@example.com&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为管理员设置每分钟 100 条</span></span><br><span class="line"><span class="keyword">var</span> configureAdminNotifications = configureEmailNotifications(<span class="number">100</span>);</span><br><span class="line">configureAdminNotifications(<span class="string">&quot;admin1@example.com&quot;</span>);</span><br></pre></td></tr></table></figure><p>按钮</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Func&lt;<span class="built_in">string</span>, Func&lt;EventArgs, <span class="keyword">void</span>&gt;&gt; CurryButtonClickHandler()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> buttonName =&gt; eventArgs =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;按钮 <span class="subst">&#123;buttonName&#125;</span> 被点击！&quot;</span>);</span><br><span class="line">        <span class="comment">// 其他处理逻辑</span></span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> handleButtonClick = CurryButtonClickHandler();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> handleSaveClick = handleButtonClick(<span class="string">&quot;保存&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> handleCancelClick = handleButtonClick(<span class="string">&quot;取消&quot;</span>);</span><br><span class="line"></span><br><span class="line">saveButton.Click += (sender, e) =&gt; handleSaveClick(e);</span><br><span class="line">cancelButton.Click += (sender, e) =&gt; handleCancelClick(e);</span><br></pre></td></tr></table></figure><p>日志</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Func&lt;<span class="built_in">string</span>, Func&lt;<span class="built_in">string</span>, <span class="keyword">void</span>&gt;&gt; CurryLogMessage()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> logLevel =&gt; message =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;logLevel&#125;</span>: <span class="subst">&#123;message&#125;</span>&quot;</span>);</span><br><span class="line">        <span class="comment">// 用指定的日志级别输出消息</span></span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> logMessage = CurryLogMessage();     <span class="comment">// 获取总入口</span></span><br><span class="line"><span class="keyword">var</span> logError = logMessage(<span class="string">&quot;ERROR&quot;</span>);     <span class="comment">// 先固定日志级别为 ERROR</span></span><br><span class="line"><span class="keyword">var</span> logWarning = logMessage(<span class="string">&quot;WARNING&quot;</span>); <span class="comment">// 固定为 WARNING</span></span><br><span class="line"></span><br><span class="line">logError(<span class="string">&quot;发生了一个错误。&quot;</span>);           <span class="comment">// 输出：ERROR: 发生了一个错误。</span></span><br><span class="line">logWarning(<span class="string">&quot;这是一个警告信息。&quot;</span>);       <span class="comment">// 输出：WARNING: 这是一个警告信息。</span></span><br></pre></td></tr></table></figure><p>‍</p><h1 id="优缺点"><a class="markdownIt-Anchor" href="#优缺点"></a> 优缺点</h1><p>优点:</p><ul><li>帅</li></ul><p>缺点:</p><ul><li>复杂度up</li><li>一定性能开销</li><li>调试难度变高</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第九章-柯里化&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第九章-柯里化&quot;&gt;&lt;/a&gt; 第九章 柯里化&lt;/h1&gt;
&lt;h1 id=&quot;什么是柯里化&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#什么是柯里化&quot;&gt;</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第八章 递归与尾递归</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%85%AB%E7%AB%A0%20%E9%80%92%E5%BD%92%E4%B8%8E%E5%B0%BE%E9%80%92%E5%BD%92/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%85%AB%E7%AB%A0%20%E9%80%92%E5%BD%92%E4%B8%8E%E5%B0%BE%E9%80%92%E5%BD%92/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.765Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第八章-递归与尾递归"><a class="markdownIt-Anchor" href="#第八章-递归与尾递归"></a> 第八章 递归与尾递归</h1><h1 id="递归分类"><a class="markdownIt-Anchor" href="#递归分类"></a> 递归分类</h1><ul><li>普通递归: 递归调用·<strong>不是·</strong> 函数的最后一步</li><li>尾递归:     递归调用  ·<strong>是·</strong>   函数的最后一步</li></ul><p>‍</p><p>尾递归可以被编译器优化,以提高性能,防止栈溢出.</p><p>递归应该尽量尾递归</p><h1 id="缓解堆栈溢出风险的策略"><a class="markdownIt-Anchor" href="#缓解堆栈溢出风险的策略"></a> 缓解堆栈溢出风险的策略</h1><ul><li>尾递归</li><li><strong>别递了</strong></li><li><strong>增加堆栈大小</strong>：通过设置 <code>System.Threading.Thread.MaxStackSize</code> 属性</li><li><strong>限制递归深度</strong>：递归实现时追踪递归深度,太深了直接抛异常</li></ul><p>‍</p><h1 id="c递归代码优雅手段"><a class="markdownIt-Anchor" href="#c递归代码优雅手段"></a> C#递归代码优雅手段</h1><h2 id="局部函数"><a class="markdownIt-Anchor" href="#局部函数"></a> 局部函数</h2><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ProcessAndCountVideosInCategory</span>(<span class="params">Category category</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">int</span> videoCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 局部函数：递归统计视频数量</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">CountVideos</span>(<span class="params">Category cat</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">foreach</span> (<span class="keyword">var</span> subcategory <span class="keyword">in</span> cat.Subcategories)</span><br><span class="line">        &#123;</span><br><span class="line">            CountVideos(subcategory); <span class="comment">// 递归调用</span></span><br><span class="line">        &#125;</span><br><span class="line">        videoCount += cat.Videos.Count;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    CountVideos(category); <span class="comment">// 从顶层分类开始递归</span></span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;总视频数：<span class="subst">&#123;videoCount&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="模式匹配"><a class="markdownIt-Anchor" href="#模式匹配"></a> 模式匹配</h2><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ProcessVideoCategory</span>(<span class="params">Category category</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">switch</span> (category)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">case</span> Category c <span class="keyword">when</span> c.HasSubcategories:</span><br><span class="line">            <span class="keyword">foreach</span> (<span class="keyword">var</span> subcategory <span class="keyword">in</span> c.Subcategories)</span><br><span class="line">            &#123;</span><br><span class="line">                ProcessVideoCategory(subcategory); <span class="comment">// 递归调用</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 可以添加更多 case 来处理其他类型或条件</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h1 id="高级递归模式"><a class="markdownIt-Anchor" href="#高级递归模式"></a> 高级递归模式</h1><h2 id="互递归mutual-recursion"><a class="markdownIt-Anchor" href="#互递归mutual-recursion"></a> 互递归（Mutual Recursion）</h2><blockquote><p><strong>两个或多个函数相互调用</strong></p></blockquote><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">EditContent</span>(<span class="params">Manuscript manuscript</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;正在编辑内容：<span class="subst">&#123;manuscript.Title&#125;</span>&quot;</span>);</span><br><span class="line">        manuscript.ContentEdited = <span class="literal">true</span>;</span><br><span class="line">        manuscript.ContentIssues.Clear(); <span class="comment">// 假设内容问题已解决</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 模拟发现格式问题</span></span><br><span class="line">        manuscript.FormatIssues.Add(<span class="string">&quot;章节标题不一致&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (manuscript.FormatIssues.Any())</span><br><span class="line">        &#123;</span><br><span class="line">            EditFormat(manuscript); <span class="comment">// 调用格式编辑</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">EditFormat</span>(<span class="params">Manuscript manuscript</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;正在调整格式：<span class="subst">&#123;manuscript.Title&#125;</span>&quot;</span>);</span><br><span class="line">        manuscript.FormatEdited = <span class="literal">true</span>;</span><br><span class="line">        manuscript.FormatIssues.Clear(); <span class="comment">// 假设格式问题已解决</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 模拟发现内容问题</span></span><br><span class="line">        manuscript.ContentIssues.Add(<span class="string">&quot;第三章内容过长&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (manuscript.ContentIssues.Any())</span><br><span class="line">        &#123;</span><br><span class="line">            EditContent(manuscript); <span class="comment">// 调用内容编辑</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h2 id="记忆化memoization"><a class="markdownIt-Anchor" href="#记忆化memoization"></a> 记忆化（Memoization）</h2><blockquote><p>空间换时间</p></blockquote><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">FibonacciCalculator</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> Dictionary&lt;<span class="built_in">int</span>, <span class="built_in">long</span>&gt; memo = <span class="keyword">new</span>();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">Calculate</span>(<span class="params"><span class="built_in">int</span> n</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">1</span>) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (memo.ContainsKey(n))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[n]; <span class="comment">// 从缓存中返回结果</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">long</span> result = Calculate(n - <span class="number">1</span>) + Calculate(n - <span class="number">2</span>);</span><br><span class="line">        memo[n] = result; <span class="comment">// 缓存计算结果</span></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">n = <span class="number">79</span>：</span><br><span class="line">未优化：调用次数达万亿级</span><br><span class="line">使用记忆化：仅 <span class="number">157</span> 次调用</span><br></pre></td></tr></table></figure><h1 id="性能"><a class="markdownIt-Anchor" href="#性能"></a> 性能</h1><p>人们常说递归性能差,然而cs中你不能相信观念,而得相信profile</p><p>递归性能优于迭代的情况不在少数</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第八章-递归与尾递归&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第八章-递归与尾递归&quot;&gt;&lt;/a&gt; 第八章 递归与尾递归&lt;/h1&gt;
&lt;h1 id=&quot;递归分类&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#递</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第六章 高阶函数与委托</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%85%AD%E7%AB%A0%20%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B8%8E%E5%A7%94%E6%89%98/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%85%AD%E7%AB%A0%20%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B8%8E%E5%A7%94%E6%89%98/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.770Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第六章-高阶函数与委托"><a class="markdownIt-Anchor" href="#第六章-高阶函数与委托"></a> 第六章 高阶函数与委托</h1><h1 id="高阶函数"><a class="markdownIt-Anchor" href="#高阶函数"></a> 高阶函数</h1><p>定义:</p><ul><li>接受函数作为参数</li><li>或 返回函数作为结果</li></ul><p>‍</p><h1 id="背景知识"><a class="markdownIt-Anchor" href="#背景知识"></a> 背景知识</h1><p>‍</p><h1 id="linq"><a class="markdownIt-Anchor" href="#linq"></a> Linq</h1><ul><li>筛选: Where</li><li>map: Select</li><li>聚合: Aggregation</li></ul><p>‍</p><h1 id="代码建议"><a class="markdownIt-Anchor" href="#代码建议"></a> 代码建议</h1><ul><li>确保传递的函数是无状态的</li><li>努力维护不可变性</li></ul><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第六章-高阶函数与委托&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第六章-高阶函数与委托&quot;&gt;&lt;/a&gt; 第六章 高阶函数与委托&lt;/h1&gt;
&lt;h1 id=&quot;高阶函数&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第十章 管道和组合</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%8D%81%E7%AB%A0%20%E7%AE%A1%E9%81%93%E5%92%8C%E7%BB%84%E5%90%88/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%8D%81%E7%AB%A0%20%E7%AE%A1%E9%81%93%E5%92%8C%E7%BB%84%E5%90%88/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.776Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第十章-管道和组合"><a class="markdownIt-Anchor" href="#第十章-管道和组合"></a> 第十章 管道和组合</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">public static T Pipe&lt;T&gt;(this T source, params Func&lt;T, T&gt;[] funcs)</span><br><span class="line">&#123;</span><br><span class="line">    return funcs.Aggregate(source, (current, func) =&gt; func(current));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第十章-管道和组合&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第十章-管道和组合&quot;&gt;&lt;/a&gt; 第十章 管道和组合&lt;/h1&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cla</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程 - 第四章 诚实函数、null 与 Option</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%9B%9B%E7%AB%A0%20%E8%AF%9A%E5%AE%9E%E5%87%BD%E6%95%B0%E3%80%81null%20%E4%B8%8E%20Option/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%20-%20%E7%AC%AC%E5%9B%9B%E7%AB%A0%20%E8%AF%9A%E5%AE%9E%E5%87%BD%E6%95%B0%E3%80%81null%20%E4%B8%8E%20Option/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.782Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第四章-诚实函数-null-与-option"><a class="markdownIt-Anchor" href="#第四章-诚实函数-null-与-option"></a> 第四章 诚实函数、null 与 Option</h1><h1 id="诚实函数"><a class="markdownIt-Anchor" href="#诚实函数"></a> 诚实函数</h1><blockquote><p>诚实函数,我更愿意称其为: 可靠函数</p></blockquote><p>诚实函数:</p><ul><li>代码即文档,名称就是行为</li><li>健壮性强</li></ul><p>‍</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 非诚实函数</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span> <span class="title">Divide</span>(<span class="params"><span class="built_in">int</span> numerator, <span class="built_in">int</span> denominator</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> numerator / denominator;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//诚实函数</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span>? Divide(<span class="built_in">int</span> numerator, <span class="built_in">int</span> denominator)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span>(denominator == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> numerator / denominator;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><h1 id="null"><a class="markdownIt-Anchor" href="#null"></a> null</h1><p>破坏代码健壮性的一大元凶就是null</p><p>我们有一些方法来解决这个问题</p><h1 id="解决null的最终兵器option-类型"><a class="markdownIt-Anchor" href="#解决null的最终兵器option-类型"></a> 解决null的最终兵器：Option 类型</h1><p>‍</p><p>Option 类型的值有两种可能:</p><ul><li>​<code>Some(value)</code>：表示确实有值。</li><li>​<code>None</code>（或 <code>null</code> 的替代）：表示值缺失。</li></ul><p>‍</p><p>Option类型示例</p><figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">Option</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">Some</span> : <span class="title">Option</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> T Value &#123; <span class="keyword">get</span>; &#125;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Some</span>(<span class="params">T <span class="keyword">value</span></span>)</span> =&gt; Value = <span class="keyword">value</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">None</span> : <span class="title">Option</span>&lt;<span class="title">T</span>&gt; &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Option&lt;T&gt; <span class="title">Create</span>(<span class="params">T <span class="keyword">value</span></span>)</span> =&gt;</span><br><span class="line">        <span class="keyword">value</span> == <span class="literal">null</span> ? <span class="keyword">new</span> None() : <span class="keyword">new</span> Some(<span class="keyword">value</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‍</p><p>为什么使用 Option 而不是 null？</p><ol><li>强制处理缺失的值：无法忽视null，编译器会提醒必须处理 <code>None</code>​</li><li>增强语义</li><li>更少的异常</li></ol><p>‍</p><p>真实案例：</p><ul><li>Web API：返回一个用户信息时，用 <code>Option&lt;User&gt;</code> 取代直接返回 <code>User?</code>​</li><li>银行系统</li><li>链式调用：Option 可与 LINQ 风格链式调用很好地结合，简化处理逻辑。</li></ul><p>‍</p><h1 id="永远不会诚实的c"><a class="markdownIt-Anchor" href="#永远不会诚实的c"></a> 永远不会诚实的c#</h1><p>我们可以在编写代码时让函数尽量诚实,但c#在语言层面就让函数永远无法达到所谓&quot;诚实&quot;的真实.</p><ul><li><strong>性能 vs 安全性</strong><br />C# 同时提供了面向性能的低层特性和面向安全性的高层特性。为了满足性能需求，开发者有时不得不偏离“完全诚实”的语言结构，比如使用 <code>unsafe</code> 代码或绕过类型检查。</li><li><strong>异常机制的局限性</strong><br />C# 目前的异常系统几乎让<strong>真正诚实的函数变得不可能实现</strong>。为什么？因为几乎<strong>任何方法都可能抛出</strong> <strong>​<code>OutOfMemoryException</code>​</strong> 或其他运行时异常。<br />即使你完全控制了自己的代码逻辑，也不要忘记：<strong>真正控制执行的是 CLR（Common Language Runtime）</strong> 。它有能力在任何时候干预你的程序运行，抛出你意想不到的异常。</li></ul><p>‍</p><h1 id="exercises"><a class="markdownIt-Anchor" href="#exercises"></a> Exercises</h1>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;第四章-诚实函数-null-与-option&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#第四章-诚实函数-null-与-option&quot;&gt;&lt;/a&gt; 第四章 诚实函数、null 与 Option&lt;/h1&gt;
&lt;h1 id=&quot;诚实函数&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程原则</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E5%8E%9F%E5%88%99/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E5%8E%9F%E5%88%99/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.783Z</updated>
    
    <content type="html"><![CDATA[<h1 id="函数式编程原则"><a class="markdownIt-Anchor" href="#函数式编程原则"></a> 函数式编程原则</h1><h2 id="原则"><a class="markdownIt-Anchor" href="#原则"></a> 原则</h2><ul><li>优先使用<strong>表达式</strong>而不是语句</li><li>使用<strong>高阶函数</strong></li><li>实现<strong>函数组合</strong></li></ul><p>‍</p><h1 id="dos-donts"><a class="markdownIt-Anchor" href="#dos-donts"></a> Dos &amp; Don’ts</h1><ul><li><p>Dos</p><ul><li>尽力编写纯函数</li><li>隔离副作用</li><li>促进数据不可变性</li><li>使用 <a href="%E7%AC%AC%E4%B8%89%E7%AB%A0%20%E7%BA%AF%E5%87%BD%E6%95%B0%E4%B8%8E%E5%89%AF%E4%BD%9C%E7%94%A8.md#20250424193311-fe6hsrx">[Pure]</a></li></ul></li><li><p>Don’ts</p><ul><li>修改输入参数</li><li>忽视 <a href="%E7%AC%AC%E4%B8%89%E7%AB%A0%20%E7%BA%AF%E5%87%BD%E6%95%B0%E4%B8%8E%E5%89%AF%E4%BD%9C%E7%94%A8.md#20250424193311-fe6hsrx">[Pure]</a></li><li>忽略上下文: 盲目避免副作用,但实际上副作用是无可避免的</li></ul></li></ul><p>‍</p><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;函数式编程原则&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#函数式编程原则&quot;&gt;&lt;/a&gt; 函数式编程原则&lt;/h1&gt;
&lt;h2 id=&quot;原则&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#原则&quot;&gt;&lt;/a&gt; 原则&lt;</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程理论</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.784Z</updated>
    
    <content type="html"><![CDATA[<!-- * // ----------------------------------------------------------------------------- * //  Copyright (c) 2025 Vanishing Games. All Rights Reserved. * @Author: VanishXiao * @Date: 2025-09-10 15:37:51 * @LastEditTime: 2025-09-10 15:41:12 * // -------------------------------------------------------------------------------><h1 id="函数式编程理论"><a class="markdownIt-Anchor" href="#函数式编程理论"></a> 函数式编程理论</h1><h1 id="function-programming-with-c"><a class="markdownIt-Anchor" href="#function-programming-with-c"></a> <strong>Function Programming With C#</strong></h1><h2 id="函数式编程读书精粹"><a class="markdownIt-Anchor" href="#函数式编程读书精粹"></a> <a href="%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E7%B2%BE%E7%B2%B9.md">函数式编程读书精粹</a></h2><h2 id="œ第一章-函数式编程基础"><a class="markdownIt-Anchor" href="#œ第一章-函数式编程基础"></a> œ第一章 函数式编程基础</h2><h2 id="第二章-expressions-statements"><a class="markdownIt-Anchor" href="#第二章-expressions-statements"></a> 第二章 Expressions &amp;&amp; Statements</h2><h2 id="第三章-纯函数与副作用"><a class="markdownIt-Anchor" href="#第三章-纯函数与副作用"></a> 第三章 纯函数与副作用</h2><h2 id="第四章-诚实函数-null-与-option"><a class="markdownIt-Anchor" href="#第四章-诚实函数-null-与-option"></a> 第四章 诚实函数、null 与 Option</h2><h2 id="第五章-错误处理"><a class="markdownIt-Anchor" href="#第五章-错误处理"></a> 第五章 错误处理</h2><h2 id="第六章-高阶函数与委托"><a class="markdownIt-Anchor" href="#第六章-高阶函数与委托"></a> 第六章 高阶函数与委托</h2><h2 id="第七章-函子与单子"><a class="markdownIt-Anchor" href="#第七章-函子与单子"></a> 第七章 函子与单子</h2><h2 id="第八章-递归与尾递归"><a class="markdownIt-Anchor" href="#第八章-递归与尾递归"></a> 第八章 递归与尾递归</h2><h2 id="第九章-柯里化"><a class="markdownIt-Anchor" href="#第九章-柯里化"></a> 第九章 柯里化</h2><h1 id="第十章-管道和组合"><a class="markdownIt-Anchor" href="#第十章-管道和组合"></a> 第十章 管道和组合</h1><h1 id="知乎-如何学习函数式编程"><a class="markdownIt-Anchor" href="#知乎-如何学习函数式编程"></a> <a href="https://www.zhihu.com/question/664311077/answer/3598077825">知乎-如何学习函数式编程</a></h1><h1 id="函数式编程原则"><a class="markdownIt-Anchor" href="#函数式编程原则"></a> <a href="%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%90%86%E8%AE%BA/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E5%8E%9F%E5%88%99.md">函数式编程原则</a></h1>]]></content>
    
    
      
      
    <summary type="html">&lt;!--
 * // -----------------------------------------------------------------------------
 * //  Copyright (c) 2025 Vanishing Games. All Righ</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>[读书笔记] 函数式编程读书精粹</title>
    <link href="http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E7%B2%BE%E7%B2%B9/"/>
    <id>http://vanishing.cc/2025/09/10/Programming/Functional/[%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0]%20%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E8%AF%BB%E4%B9%A6%E7%B2%BE%E7%B2%B9/</id>
    <published>2025-09-10T07:44:36.000Z</published>
    <updated>2026-01-07T22:15:54.786Z</updated>
    
    <content type="html"><![CDATA[<h1 id="函数式编程读书精粹"><a class="markdownIt-Anchor" href="#函数式编程读书精粹"></a> 函数式编程读书精粹</h1><p>‍</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;函数式编程读书精粹&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#函数式编程读书精粹&quot;&gt;&lt;/a&gt; 函数式编程读书精粹&lt;/h1&gt;
&lt;p&gt;‍&lt;/p&gt;
</summary>
      
    
    
    
    <category term="Functional Programming" scheme="http://vanishing.cc/categories/Functional-Programming/"/>
    
    
    <category term="函数式编程" scheme="http://vanishing.cc/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
</feed>
